From c3bc24f33b09ea8a841663570949efb4738ac000 Mon Sep 17 00:00:00 2001 From: Jeff DeCew Date: Wed, 24 Apr 2024 14:43:27 +0000 Subject: [PATCH] Generate CustomFeatureFlags * Creates a new general-purpose CustomFeatureFlags class * Simplifies FakeFeatureFlagsImpl to be based on that * This allows teams to fake FeatureFlags without having to make changes per flag. * This allows SetFlagsRule to inject an instance of FeatureFlags that would detect if a flag has been read. Bug: 336768870 Flag: none Test: presubmit Change-Id: Id3c2530d484fa5584c46d11381fcfc0ab294f33f NOTE FOR REVIEWERS - original patch and result patch are not identical. PLEASE REVIEW CAREFULLY. Diffs between the patches: "CustomFeatureFlags.java", > + include_str!("../../templates/CustomFeatureFlags.java.template"), > + )?; > + template.add_template( > - ["Flags.java", "FeatureFlags.java", "FeatureFlagsImpl.java", "FakeFeatureFlagsImpl.java"] > - .iter() > - .map(|file| { > - Ok(OutputFile { > - contents: template.render(file, &context)?.into(), > - path: path.join(file), > - }) > - }) > - .collect::>>() > + [ > + "Flags.java", > + "FeatureFlags.java", > + "FeatureFlagsImpl.java", > + "CustomFeatureFlags.java", > + "FakeFeatureFlagsImpl.java", > + ] > + .iter() > + .map(|file| { > + Ok(OutputFile { contents: template.render(file, &context)?.into(), path: path.join(file) }) > + }) > + .collect::>>() > - const EXPECTED_FAKEFEATUREFLAGSIMPL_CONTENT: &str = r#" > + const EXPECTED_CUSTOMFEATUREFLAGS_CONTENT: &str = r#" > + > - import java.util.HashMap; > - import java.util.Map; > + import java.util.List; > + import java.util.function.BiPredicate; > + import java.util.function.Predicate; > + > - public class FakeFeatureFlagsImpl implements FeatureFlags { > - public FakeFeatureFlagsImpl() { > - resetAll(); > + public class CustomFeatureFlags implements FeatureFlags { > + > + private BiPredicate> mGetValueImpl; > + > + public CustomFeatureFlags(BiPredicate> getValueImpl) { > + mGetValueImpl = getValueImpl; > + > - return getValue(Flags.FLAG_DISABLED_RO); > + return getValue(Flags.FLAG_DISABLED_RO, > + FeatureFlags::disabledRo); > - return getValue(Flags.FLAG_DISABLED_RW); > + return getValue(Flags.FLAG_DISABLED_RW, > + FeatureFlags::disabledRw); > - return getValue(Flags.FLAG_DISABLED_RW_EXPORTED); > + return getValue(Flags.FLAG_DISABLED_RW_EXPORTED, > + FeatureFlags::disabledRwExported); > - return getValue(Flags.FLAG_DISABLED_RW_IN_OTHER_NAMESPACE); > + return getValue(Flags.FLAG_DISABLED_RW_IN_OTHER_NAMESPACE, > + FeatureFlags::disabledRwInOtherNamespace); > - return getValue(Flags.FLAG_ENABLED_FIXED_RO); > + return getValue(Flags.FLAG_ENABLED_FIXED_RO, > + FeatureFlags::enabledFixedRo); > - return getValue(Flags.FLAG_ENABLED_FIXED_RO_EXPORTED); > + return getValue(Flags.FLAG_ENABLED_FIXED_RO_EXPORTED, > + FeatureFlags::enabledFixedRoExported); > - return getValue(Flags.FLAG_ENABLED_RO); > + return getValue(Flags.FLAG_ENABLED_RO, > + FeatureFlags::enabledRo); > - return getValue(Flags.FLAG_ENABLED_RO_EXPORTED); > + return getValue(Flags.FLAG_ENABLED_RO_EXPORTED, > + FeatureFlags::enabledRoExported); > - return getValue(Flags.FLAG_ENABLED_RW); > + return getValue(Flags.FLAG_ENABLED_RW, > + FeatureFlags::enabledRw); > - public void setFlag(String flagName, boolean value) { > - if (!this.mFlagMap.containsKey(flagName)) { > - throw new IllegalArgumentException("no such flag " + flagName); > - } > - this.mFlagMap.put(flagName, value); > - } > - public void resetAll() { > - for (Map.Entry entry : mFlagMap.entrySet()) { > - entry.setValue(null); > - } > - } > + > + > - private boolean getValue(String flagName) { > - Boolean value = this.mFlagMap.get(flagName); > - if (value == null) { > - throw new IllegalArgumentException(flagName + " is not set"); > - } > - return value; > + > + protected boolean getValue(String flagName, Predicate getter) { > + return mGetValueImpl.test(flagName, getter); > - private Map mFlagMap = new HashMap<>( > - Map.ofEntries( > - Map.entry(Flags.FLAG_DISABLED_RO, false), > - Map.entry(Flags.FLAG_DISABLED_RW, false), > - Map.entry(Flags.FLAG_DISABLED_RW_EXPORTED, false), > - Map.entry(Flags.FLAG_DISABLED_RW_IN_OTHER_NAMESPACE, false), > - Map.entry(Flags.FLAG_ENABLED_FIXED_RO, false), > - Map.entry(Flags.FLAG_ENABLED_FIXED_RO_EXPORTED, false), > - Map.entry(Flags.FLAG_ENABLED_RO, false), > - Map.entry(Flags.FLAG_ENABLED_RO_EXPORTED, false), > - Map.entry(Flags.FLAG_ENABLED_RW, false) > - ) > - ); > + > + public List getFlagNames() { > + return Arrays.asList( > + Flags.FLAG_DISABLED_RO, > + Flags.FLAG_DISABLED_RW, > + Flags.FLAG_DISABLED_RW_EXPORTED, > + Flags.FLAG_DISABLED_RW_IN_OTHER_NAMESPACE, > + Flags.FLAG_ENABLED_FIXED_RO, > + Flags.FLAG_ENABLED_FIXED_RO_EXPORTED, > + Flags.FLAG_ENABLED_RO, > + Flags.FLAG_ENABLED_RO_EXPORTED, > + Flags.FLAG_ENABLED_RW > + ); > + } > + > + const EXPECTED_FAKEFEATUREFLAGSIMPL_CONTENT: &str = r#" > + package com.android.aconfig.test; > + > + import java.util.HashMap; > + import java.util.Map; > + import java.util.function.Predicate; > + > + /** @hide */ > + public class FakeFeatureFlagsImpl extends CustomFeatureFlags { > + private Map mFlagMap = new HashMap<>(); > + > + public FakeFeatureFlagsImpl() { > + super(null); > + // Initialize the map with null values > + for (String flagName : getFlagNames()) { > + mFlagMap.put(flagName, null); > + } > + } > + > + @Override > + protected boolean getValue(String flagName, Predicate getter) { > + Boolean value = this.mFlagMap.get(flagName); > + if (value == null) { > + throw new IllegalArgumentException(flagName + " is not set"); > + } > + return value; > + } > + > + public void setFlag(String flagName, boolean value) { > + if (!this.mFlagMap.containsKey(flagName)) { > + throw new IllegalArgumentException("no such flag " + flagName); > + } > + this.mFlagMap.put(flagName, value); > + } > + > + public void resetAll() { > + for (Map.Entry entry : mFlagMap.entrySet()) { > + entry.setValue(null); > + } > + } > + } > + "#; > + > + "com/android/aconfig/test/CustomFeatureFlags.java", > + EXPECTED_CUSTOMFEATUREFLAGS_CONTENT, > + ), > + ( > - let expect_fake_feature_flags_impl_content = r#" > + let expect_custom_feature_flags_content = r#" > + > - import java.util.HashMap; > - import java.util.Map; > + import java.util.List; > + import java.util.function.BiPredicate; > + import java.util.function.Predicate; > + > - public class FakeFeatureFlagsImpl implements FeatureFlags { > - public FakeFeatureFlagsImpl() { > - resetAll(); > + public class CustomFeatureFlags implements FeatureFlags { > + > + private BiPredicate> mGetValueImpl; > + > + public CustomFeatureFlags(BiPredicate> getValueImpl) { > + mGetValueImpl = getValueImpl; > + > - return getValue(Flags.FLAG_DISABLED_RW_EXPORTED); > + return getValue(Flags.FLAG_DISABLED_RW_EXPORTED, > + FeatureFlags::disabledRwExported); > - return getValue(Flags.FLAG_ENABLED_FIXED_RO_EXPORTED); > + return getValue(Flags.FLAG_ENABLED_FIXED_RO_EXPORTED, > + FeatureFlags::enabledFixedRoExported); > - return getValue(Flags.FLAG_ENABLED_RO_EXPORTED); > + return getValue(Flags.FLAG_ENABLED_RO_EXPORTED, > + FeatureFlags::enabledRoExported); > - public void setFlag(String flagName, boolean value) { > - if (!this.mFlagMap.containsKey(flagName)) { > - throw new IllegalArgumentException("no such flag " + flagName); > - } > - this.mFlagMap.put(flagName, value); > + > + protected boolean getValue(String flagName, Predicate getter) { > + return mGetValueImpl.test(flagName, getter); > - public void resetAll() { > - for (Map.Entry entry : mFlagMap.entrySet()) { > - entry.setValue(null); > - } > + > + public List getFlagNames() { > + return Arrays.asList( > + Flags.FLAG_DISABLED_RW_EXPORTED, > + Flags.FLAG_ENABLED_FIXED_RO_EXPORTED, > + Flags.FLAG_ENABLED_RO_EXPORTED > + ); > - private boolean getValue(String flagName) { > - Boolean value = this.mFlagMap.get(flagName); > - if (value == null) { > - throw new IllegalArgumentException(flagName + " is not set"); > - } > - return value; > - } > - private Map mFlagMap = new HashMap<>( > - Map.ofEntries( > - Map.entry(Flags.FLAG_DISABLED_RW_EXPORTED, false), > - Map.entry(Flags.FLAG_ENABLED_FIXED_RO_EXPORTED, false), > - Map.entry(Flags.FLAG_ENABLED_RO_EXPORTED, false) > - ) > - ); > + > + "com/android/aconfig/test/CustomFeatureFlags.java", > + expect_custom_feature_flags_content, > + ), > + ( > - expect_fake_feature_flags_impl_content, > + EXPECTED_FAKEFEATUREFLAGSIMPL_CONTENT, > + "com/android/aconfig/test/CustomFeatureFlags.java", > + EXPECTED_CUSTOMFEATUREFLAGS_CONTENT, > + ), > + ( > - let expect_fakefeatureflags_content = r#" > + let expect_customfeatureflags_content = r#" > + > - import java.util.HashMap; > - import java.util.Map; > + import java.util.List; > + import java.util.function.BiPredicate; > + import java.util.function.Predicate; > + > - public class FakeFeatureFlagsImpl implements FeatureFlags { > - public FakeFeatureFlagsImpl() { > - resetAll(); > + public class CustomFeatureFlags implements FeatureFlags { > + > + private BiPredicate> mGetValueImpl; > + > + public CustomFeatureFlags(BiPredicate> getValueImpl) { > + mGetValueImpl = getValueImpl; > + > - return getValue(Flags.FLAG_DISABLED_RO); > + return getValue(Flags.FLAG_DISABLED_RO, > + FeatureFlags::disabledRo); > - return getValue(Flags.FLAG_DISABLED_RW); > + return getValue(Flags.FLAG_DISABLED_RW, > + FeatureFlags::disabledRw); > - return getValue(Flags.FLAG_DISABLED_RW_IN_OTHER_NAMESPACE); > + return getValue(Flags.FLAG_DISABLED_RW_IN_OTHER_NAMESPACE, > + FeatureFlags::disabledRwInOtherNamespace); > - return getValue(Flags.FLAG_ENABLED_FIXED_RO); > + return getValue(Flags.FLAG_ENABLED_FIXED_RO, > + FeatureFlags::enabledFixedRo); > - return getValue(Flags.FLAG_ENABLED_RO); > + return getValue(Flags.FLAG_ENABLED_RO, > + FeatureFlags::enabledRo); > - return getValue(Flags.FLAG_ENABLED_RW); > + return getValue(Flags.FLAG_ENABLED_RW, > + FeatureFlags::enabledRw); > - public void setFlag(String flagName, boolean value) { > - if (!this.mFlagMap.containsKey(flagName)) { > - throw new IllegalArgumentException("no such flag " + flagName); > - } > - this.mFlagMap.put(flagName, value); > - } > - public void resetAll() { > - for (Map.Entry entry : mFlagMap.entrySet()) { > - entry.setValue(null); > - } > - } > + > + > - private boolean getValue(String flagName) { > - Boolean value = this.mFlagMap.get(flagName); > - if (value == null) { > - throw new IllegalArgumentException(flagName + " is not set"); > - } > - return value; > + > + protected boolean getValue(String flagName, Predicate getter) { > + return mGetValueImpl.test(flagName, getter); > - private Map mFlagMap = new HashMap<>( > - Map.ofEntries( > - Map.entry(Flags.FLAG_DISABLED_RO, false), > - Map.entry(Flags.FLAG_DISABLED_RW, false), > - Map.entry(Flags.FLAG_DISABLED_RW_IN_OTHER_NAMESPACE, false), > - Map.entry(Flags.FLAG_ENABLED_FIXED_RO, false), > - Map.entry(Flags.FLAG_ENABLED_RO, false), > - Map.entry(Flags.FLAG_ENABLED_RW, false) > - ) > - ); > + > + public List getFlagNames() { > + return Arrays.asList( > + Flags.FLAG_DISABLED_RO, > + Flags.FLAG_DISABLED_RW, > + Flags.FLAG_DISABLED_RW_IN_OTHER_NAMESPACE, > + Flags.FLAG_ENABLED_FIXED_RO, > + Flags.FLAG_ENABLED_RO, > + Flags.FLAG_ENABLED_RW > + ); > + } > + > + > - ("com/android/aconfig/test/FakeFeatureFlagsImpl.java", expect_fakefeatureflags_content), > + ("com/android/aconfig/test/CustomFeatureFlags.java", expect_customfeatureflags_content), > + ( > + "com/android/aconfig/test/FakeFeatureFlagsImpl.java", > + EXPECTED_FAKEFEATUREFLAGSIMPL_CONTENT, > + ), > --- /dev/null > +++ tools/aconfig/aconfig/templates/CustomFeatureFlags.java.template > +package {package_name}; > + > +{{ if not library_exported- }} > +// TODO(b/303773055): Remove the annotation after access issue is resolved. > +import android.compat.annotation.UnsupportedAppUsage; > +{{ -endif }} > +import java.util.Arrays; > +import java.util.HashSet; > +import java.util.List; > +import java.util.Set; > +import java.util.function.BiPredicate; > +import java.util.function.Predicate; > + > +/** @hide */ > +public class CustomFeatureFlags implements FeatureFlags \{ > + > + private BiPredicate> mGetValueImpl; > + > + public CustomFeatureFlags(BiPredicate> getValueImpl) \{ > + mGetValueImpl = getValueImpl; > + } > + > +{{ -for item in flag_elements}} > + @Override > +{{ if not library_exported }} @UnsupportedAppUsage{{ -endif }} > + public boolean {item.method_name}() \{ > + return getValue(Flags.FLAG_{item.flag_name_constant_suffix}, > + FeatureFlags::{item.method_name}); > + } > +{{ endfor }} > + > +{{ -if not library_exported }} > + public boolean isFlagReadOnlyOptimized(String flagName) \{ > + if (mReadOnlyFlagsSet.contains(flagName) && > + isOptimizationEnabled()) \{ > + return true; > + } > + return false; > + } > + > + @com.android.aconfig.annotations.AssumeTrueForR8 > + private boolean isOptimizationEnabled() \{ > + return false; > + } > +{{ -endif }} > + > + protected boolean getValue(String flagName, Predicate getter) \{ > + return mGetValueImpl.test(flagName, getter); > + } > + > + public List getFlagNames() \{ > + return Arrays.asList( > + {{ -for item in flag_elements }} > + Flags.FLAG_{item.flag_name_constant_suffix} > + {{ -if not @last }},{{ endif }} > + {{ -endfor }} > + ); > + } > + > + private Set mReadOnlyFlagsSet = new HashSet<>( > + Arrays.asList( > + {{ -for item in flag_elements }} > + {{ -if not item.is_read_write }} > + Flags.FLAG_{item.flag_name_constant_suffix}, > + {{ -endif }} > + {{ -endfor }} > + ""{# The empty string here is to resolve the ending comma #} > + ) > + ); > +} > --- tools/aconfig/aconfig/templates/FakeFeatureFlagsImpl.java.template > +++ tools/aconfig/aconfig/templates/FakeFeatureFlagsImpl.java.template > -{{ if not library_exported- }} > -// TODO(b/303773055): Remove the annotation after access issue is resolved. > -import android.compat.annotation.UnsupportedAppUsage; > -{{ -endif }} > -import java.util.Arrays; > + > -import java.util.HashSet; > -import java.util.Set; > +import java.util.function.Predicate; > -public class FakeFeatureFlagsImpl implements FeatureFlags \{ > +public class FakeFeatureFlagsImpl extends CustomFeatureFlags \{ > + private Map mFlagMap = new HashMap<>(); > + > - resetAll(); > + super(null); > + // Initialize the map with null values > + for (String flagName : getFlagNames()) \{ > + mFlagMap.put(flagName, null); > + } > -{{ for item in flag_elements}} > -{{ if not library_exported }} @UnsupportedAppUsage{{ -endif }} > - public boolean {item.method_name}() \{ > - return getValue(Flags.FLAG_{item.flag_name_constant_suffix}); > + protected boolean getValue(String flagName, Predicate getter) \{ > + Boolean value = this.mFlagMap.get(flagName); > + if (value == null) \{ > + throw new IllegalArgumentException(flagName + " is not set"); > + } > + return value; > -{{ endfor}} > + > -{{ if not library_exported }} > - public boolean isFlagReadOnlyOptimized(String flagName) \{ > - if (mReadOnlyFlagsSet.contains(flagName) && > - isOptimizationEnabled()) \{ > - return true; > - } > - return false; > - } > - > - @com.android.aconfig.annotations.AssumeTrueForR8 > - private boolean isOptimizationEnabled() \{ > - return false; > - } > -{{ -endif }} > - private boolean getValue(String flagName) \{ > - Boolean value = this.mFlagMap.get(flagName); > - if (value == null) \{ > - throw new IllegalArgumentException(flagName + " is not set"); > - } > - return value; > - } > - > - > - private Map mFlagMap = new HashMap<>( > - Map.ofEntries( > - {{ -for item in flag_elements }} > - Map.entry(Flags.FLAG_{item.flag_name_constant_suffix}, false) > - {{ -if not @last }},{{ endif }} > - {{ -endfor }} > - ) > - ); > - > - private Set mReadOnlyFlagsSet = new HashSet<>( > - Arrays.asList( > - {{ -for item in flag_elements }} > - {{ -if not item.is_read_write }} > - Flags.FLAG_{item.flag_name_constant_suffix}, > - {{ -endif }} > - {{ -endfor }} > - ""{# The empty string here is to resolve the ending comma #} > - ) > - ); Original patch: diff --git a/tools/aconfig/aconfig/src/codegen/java.rs b/tools/aconfig/aconfig/src/codegen/java.rs old mode 100644 new mode 100644 --- a/tools/aconfig/aconfig/src/codegen/java.rs +++ b/tools/aconfig/aconfig/src/codegen/java.rs @@ -63,21 +63,28 @@ "FeatureFlags.java", include_str!("../../templates/FeatureFlags.java.template"), )?; + template.add_template( + "CustomFeatureFlags.java", + include_str!("../../templates/CustomFeatureFlags.java.template"), + )?; template.add_template( "FakeFeatureFlagsImpl.java", include_str!("../../templates/FakeFeatureFlagsImpl.java.template"), )?; let path: PathBuf = package.split('.').collect(); - ["Flags.java", "FeatureFlags.java", "FeatureFlagsImpl.java", "FakeFeatureFlagsImpl.java"] - .iter() - .map(|file| { - Ok(OutputFile { - contents: template.render(file, &context)?.into(), - path: path.join(file), - }) - }) - .co [[[Original patch trimmed due to size. Decoded string size: 26318. Decoded string SHA1: 7db34b7baf0a0bbaa1cff48b6ccab9c65408e743.]]] Result patch: diff --git a/tools/aconfig/aconfig/src/codegen/java.rs b/tools/aconfig/aconfig/src/codegen/java.rs index 18a4be5..9abc892 100644 --- a/tools/aconfig/aconfig/src/codegen/java.rs +++ b/tools/aconfig/aconfig/src/codegen/java.rs @@ -64,20 +64,27 @@ include_str!("../../templates/FeatureFlags.java.template"), )?; template.add_template( + "CustomFeatureFlags.java", + include_str!("../../templates/CustomFeatureFlags.java.template"), + )?; + template.add_template( "FakeFeatureFlagsImpl.java", include_str!("../../templates/FakeFeatureFlagsImpl.java.template"), )?; let path: PathBuf = package.split('.').collect(); - ["Flags.java", "FeatureFlags.java", "FeatureFlagsImpl.java", "FakeFeatureFlagsImpl.java"] - .iter() - .map(|file| { - Ok(OutputFile { - contents: template.render(file, &context)?.into(), - path: path.join(file), - }) - }) - .collect::>> [[[Result patch trimmed due to size. Decoded string size: 26308. Decoded string SHA1: bb07ee948a630a6bc4cfbbf2a8df178f3e6d3103.]]] Change-Id: I94184633303b9d76f7943451fb3b2c93d071ec1c --- tools/aconfig/aconfig/src/codegen/java.rs | 328 +++++++++++------- .../CustomFeatureFlags.java.template | 70 ++++ .../FakeFeatureFlagsImpl.java.template | 73 +--- 3 files changed, 288 insertions(+), 183 deletions(-) create mode 100644 tools/aconfig/aconfig/templates/CustomFeatureFlags.java.template diff --git a/tools/aconfig/aconfig/src/codegen/java.rs b/tools/aconfig/aconfig/src/codegen/java.rs index 18a4be5094..9abc892908 100644 --- a/tools/aconfig/aconfig/src/codegen/java.rs +++ b/tools/aconfig/aconfig/src/codegen/java.rs @@ -63,21 +63,28 @@ where "FeatureFlags.java", include_str!("../../templates/FeatureFlags.java.template"), )?; + template.add_template( + "CustomFeatureFlags.java", + include_str!("../../templates/CustomFeatureFlags.java.template"), + )?; template.add_template( "FakeFeatureFlagsImpl.java", include_str!("../../templates/FakeFeatureFlagsImpl.java.template"), )?; let path: PathBuf = package.split('.').collect(); - ["Flags.java", "FeatureFlags.java", "FeatureFlagsImpl.java", "FakeFeatureFlagsImpl.java"] - .iter() - .map(|file| { - Ok(OutputFile { - contents: template.render(file, &context)?.into(), - path: path.join(file), - }) - }) - .collect::>>() + [ + "Flags.java", + "FeatureFlags.java", + "FeatureFlagsImpl.java", + "CustomFeatureFlags.java", + "FakeFeatureFlagsImpl.java", + ] + .iter() + .map(|file| { + Ok(OutputFile { contents: template.render(file, &context)?.into(), path: path.join(file) }) + }) + .collect::>>() } fn gen_flags_by_namespace(flags: &[FlagElement]) -> Vec { @@ -292,76 +299,82 @@ mod tests { } "#; - const EXPECTED_FAKEFEATUREFLAGSIMPL_CONTENT: &str = r#" + const EXPECTED_CUSTOMFEATUREFLAGS_CONTENT: &str = r#" package com.android.aconfig.test; + // TODO(b/303773055): Remove the annotation after access issue is resolved. import android.compat.annotation.UnsupportedAppUsage; import java.util.Arrays; - import java.util.HashMap; import java.util.HashSet; - import java.util.Map; + import java.util.List; import java.util.Set; + import java.util.function.BiPredicate; + import java.util.function.Predicate; + /** @hide */ - public class FakeFeatureFlagsImpl implements FeatureFlags { - public FakeFeatureFlagsImpl() { - resetAll(); + public class CustomFeatureFlags implements FeatureFlags { + + private BiPredicate> mGetValueImpl; + + public CustomFeatureFlags(BiPredicate> getValueImpl) { + mGetValueImpl = getValueImpl; } + @Override @UnsupportedAppUsage public boolean disabledRo() { - return getValue(Flags.FLAG_DISABLED_RO); + return getValue(Flags.FLAG_DISABLED_RO, + FeatureFlags::disabledRo); } @Override @UnsupportedAppUsage public boolean disabledRw() { - return getValue(Flags.FLAG_DISABLED_RW); + return getValue(Flags.FLAG_DISABLED_RW, + FeatureFlags::disabledRw); } @Override @UnsupportedAppUsage public boolean disabledRwExported() { - return getValue(Flags.FLAG_DISABLED_RW_EXPORTED); + return getValue(Flags.FLAG_DISABLED_RW_EXPORTED, + FeatureFlags::disabledRwExported); } @Override @UnsupportedAppUsage public boolean disabledRwInOtherNamespace() { - return getValue(Flags.FLAG_DISABLED_RW_IN_OTHER_NAMESPACE); + return getValue(Flags.FLAG_DISABLED_RW_IN_OTHER_NAMESPACE, + FeatureFlags::disabledRwInOtherNamespace); } @Override @UnsupportedAppUsage public boolean enabledFixedRo() { - return getValue(Flags.FLAG_ENABLED_FIXED_RO); + return getValue(Flags.FLAG_ENABLED_FIXED_RO, + FeatureFlags::enabledFixedRo); } @Override @UnsupportedAppUsage public boolean enabledFixedRoExported() { - return getValue(Flags.FLAG_ENABLED_FIXED_RO_EXPORTED); + return getValue(Flags.FLAG_ENABLED_FIXED_RO_EXPORTED, + FeatureFlags::enabledFixedRoExported); } @Override @UnsupportedAppUsage public boolean enabledRo() { - return getValue(Flags.FLAG_ENABLED_RO); + return getValue(Flags.FLAG_ENABLED_RO, + FeatureFlags::enabledRo); } @Override @UnsupportedAppUsage public boolean enabledRoExported() { - return getValue(Flags.FLAG_ENABLED_RO_EXPORTED); + return getValue(Flags.FLAG_ENABLED_RO_EXPORTED, + FeatureFlags::enabledRoExported); } @Override @UnsupportedAppUsage public boolean enabledRw() { - return getValue(Flags.FLAG_ENABLED_RW); - } - public void setFlag(String flagName, boolean value) { - if (!this.mFlagMap.containsKey(flagName)) { - throw new IllegalArgumentException("no such flag " + flagName); - } - this.mFlagMap.put(flagName, value); - } - public void resetAll() { - for (Map.Entry entry : mFlagMap.entrySet()) { - entry.setValue(null); - } + return getValue(Flags.FLAG_ENABLED_RW, + FeatureFlags::enabledRw); } + public boolean isFlagReadOnlyOptimized(String flagName) { if (mReadOnlyFlagsSet.contains(flagName) && isOptimizationEnabled()) { @@ -369,30 +382,30 @@ mod tests { } return false; } + @com.android.aconfig.annotations.AssumeTrueForR8 private boolean isOptimizationEnabled() { return false; } - private boolean getValue(String flagName) { - Boolean value = this.mFlagMap.get(flagName); - if (value == null) { - throw new IllegalArgumentException(flagName + " is not set"); - } - return value; + + protected boolean getValue(String flagName, Predicate getter) { + return mGetValueImpl.test(flagName, getter); } - private Map mFlagMap = new HashMap<>( - Map.ofEntries( - Map.entry(Flags.FLAG_DISABLED_RO, false), - Map.entry(Flags.FLAG_DISABLED_RW, false), - Map.entry(Flags.FLAG_DISABLED_RW_EXPORTED, false), - Map.entry(Flags.FLAG_DISABLED_RW_IN_OTHER_NAMESPACE, false), - Map.entry(Flags.FLAG_ENABLED_FIXED_RO, false), - Map.entry(Flags.FLAG_ENABLED_FIXED_RO_EXPORTED, false), - Map.entry(Flags.FLAG_ENABLED_RO, false), - Map.entry(Flags.FLAG_ENABLED_RO_EXPORTED, false), - Map.entry(Flags.FLAG_ENABLED_RW, false) - ) - ); + + public List getFlagNames() { + return Arrays.asList( + Flags.FLAG_DISABLED_RO, + Flags.FLAG_DISABLED_RW, + Flags.FLAG_DISABLED_RW_EXPORTED, + Flags.FLAG_DISABLED_RW_IN_OTHER_NAMESPACE, + Flags.FLAG_ENABLED_FIXED_RO, + Flags.FLAG_ENABLED_FIXED_RO_EXPORTED, + Flags.FLAG_ENABLED_RO, + Flags.FLAG_ENABLED_RO_EXPORTED, + Flags.FLAG_ENABLED_RW + ); + } + private Set mReadOnlyFlagsSet = new HashSet<>( Arrays.asList( Flags.FLAG_DISABLED_RO, @@ -406,6 +419,49 @@ mod tests { } "#; + const EXPECTED_FAKEFEATUREFLAGSIMPL_CONTENT: &str = r#" + package com.android.aconfig.test; + + import java.util.HashMap; + import java.util.Map; + import java.util.function.Predicate; + + /** @hide */ + public class FakeFeatureFlagsImpl extends CustomFeatureFlags { + private Map mFlagMap = new HashMap<>(); + + public FakeFeatureFlagsImpl() { + super(null); + // Initialize the map with null values + for (String flagName : getFlagNames()) { + mFlagMap.put(flagName, null); + } + } + + @Override + protected boolean getValue(String flagName, Predicate getter) { + Boolean value = this.mFlagMap.get(flagName); + if (value == null) { + throw new IllegalArgumentException(flagName + " is not set"); + } + return value; + } + + public void setFlag(String flagName, boolean value) { + if (!this.mFlagMap.containsKey(flagName)) { + throw new IllegalArgumentException("no such flag " + flagName); + } + this.mFlagMap.put(flagName, value); + } + + public void resetAll() { + for (Map.Entry entry : mFlagMap.entrySet()) { + entry.setValue(null); + } + } + } + "#; + #[test] fn test_generate_java_code_production() { let parsed_flags = crate::test::parse_test_flags(); @@ -548,6 +604,10 @@ mod tests { ("com/android/aconfig/test/Flags.java", expect_flags_content.as_str()), ("com/android/aconfig/test/FeatureFlagsImpl.java", expect_featureflagsimpl_content), ("com/android/aconfig/test/FeatureFlags.java", EXPECTED_FEATUREFLAGS_COMMON_CONTENT), + ( + "com/android/aconfig/test/CustomFeatureFlags.java", + EXPECTED_CUSTOMFEATUREFLAGS_CONTENT, + ), ( "com/android/aconfig/test/FakeFeatureFlagsImpl.java", EXPECTED_FAKEFEATUREFLAGSIMPL_CONTENT, @@ -671,55 +731,53 @@ mod tests { } }"#; - let expect_fake_feature_flags_impl_content = r#" + let expect_custom_feature_flags_content = r#" package com.android.aconfig.test; + import java.util.Arrays; - import java.util.HashMap; import java.util.HashSet; - import java.util.Map; + import java.util.List; import java.util.Set; + import java.util.function.BiPredicate; + import java.util.function.Predicate; + /** @hide */ - public class FakeFeatureFlagsImpl implements FeatureFlags { - public FakeFeatureFlagsImpl() { - resetAll(); + public class CustomFeatureFlags implements FeatureFlags { + + private BiPredicate> mGetValueImpl; + + public CustomFeatureFlags(BiPredicate> getValueImpl) { + mGetValueImpl = getValueImpl; } + @Override public boolean disabledRwExported() { - return getValue(Flags.FLAG_DISABLED_RW_EXPORTED); + return getValue(Flags.FLAG_DISABLED_RW_EXPORTED, + FeatureFlags::disabledRwExported); } @Override public boolean enabledFixedRoExported() { - return getValue(Flags.FLAG_ENABLED_FIXED_RO_EXPORTED); + return getValue(Flags.FLAG_ENABLED_FIXED_RO_EXPORTED, + FeatureFlags::enabledFixedRoExported); } @Override public boolean enabledRoExported() { - return getValue(Flags.FLAG_ENABLED_RO_EXPORTED); + return getValue(Flags.FLAG_ENABLED_RO_EXPORTED, + FeatureFlags::enabledRoExported); } - public void setFlag(String flagName, boolean value) { - if (!this.mFlagMap.containsKey(flagName)) { - throw new IllegalArgumentException("no such flag " + flagName); - } - this.mFlagMap.put(flagName, value); + + protected boolean getValue(String flagName, Predicate getter) { + return mGetValueImpl.test(flagName, getter); } - public void resetAll() { - for (Map.Entry entry : mFlagMap.entrySet()) { - entry.setValue(null); - } + + public List getFlagNames() { + return Arrays.asList( + Flags.FLAG_DISABLED_RW_EXPORTED, + Flags.FLAG_ENABLED_FIXED_RO_EXPORTED, + Flags.FLAG_ENABLED_RO_EXPORTED + ); } - private boolean getValue(String flagName) { - Boolean value = this.mFlagMap.get(flagName); - if (value == null) { - throw new IllegalArgumentException(flagName + " is not set"); - } - return value; - } - private Map mFlagMap = new HashMap<>( - Map.ofEntries( - Map.entry(Flags.FLAG_DISABLED_RW_EXPORTED, false), - Map.entry(Flags.FLAG_ENABLED_FIXED_RO_EXPORTED, false), - Map.entry(Flags.FLAG_ENABLED_RO_EXPORTED, false) - ) - ); + private Set mReadOnlyFlagsSet = new HashSet<>( Arrays.asList( "" @@ -732,9 +790,13 @@ mod tests { ("com/android/aconfig/test/Flags.java", expect_flags_content), ("com/android/aconfig/test/FeatureFlags.java", expect_feature_flags_content), ("com/android/aconfig/test/FeatureFlagsImpl.java", expect_feature_flags_impl_content), + ( + "com/android/aconfig/test/CustomFeatureFlags.java", + expect_custom_feature_flags_content, + ), ( "com/android/aconfig/test/FakeFeatureFlagsImpl.java", - expect_fake_feature_flags_impl_content, + EXPECTED_FAKEFEATUREFLAGSIMPL_CONTENT, ), ]); @@ -853,6 +915,10 @@ mod tests { ("com/android/aconfig/test/Flags.java", expect_flags_content.as_str()), ("com/android/aconfig/test/FeatureFlags.java", EXPECTED_FEATUREFLAGS_COMMON_CONTENT), ("com/android/aconfig/test/FeatureFlagsImpl.java", expect_featureflagsimpl_content), + ( + "com/android/aconfig/test/CustomFeatureFlags.java", + EXPECTED_CUSTOMFEATUREFLAGS_CONTENT, + ), ( "com/android/aconfig/test/FakeFeatureFlagsImpl.java", EXPECTED_FAKEFEATUREFLAGSIMPL_CONTENT, @@ -1020,61 +1086,64 @@ mod tests { private static FeatureFlags FEATURE_FLAGS = new FeatureFlagsImpl(); }"#; - let expect_fakefeatureflags_content = r#" + let expect_customfeatureflags_content = r#" package com.android.aconfig.test; + // TODO(b/303773055): Remove the annotation after access issue is resolved. import android.compat.annotation.UnsupportedAppUsage; import java.util.Arrays; - import java.util.HashMap; import java.util.HashSet; - import java.util.Map; + import java.util.List; import java.util.Set; + import java.util.function.BiPredicate; + import java.util.function.Predicate; + /** @hide */ - public class FakeFeatureFlagsImpl implements FeatureFlags { - public FakeFeatureFlagsImpl() { - resetAll(); + public class CustomFeatureFlags implements FeatureFlags { + + private BiPredicate> mGetValueImpl; + + public CustomFeatureFlags(BiPredicate> getValueImpl) { + mGetValueImpl = getValueImpl; } + @Override @UnsupportedAppUsage public boolean disabledRo() { - return getValue(Flags.FLAG_DISABLED_RO); + return getValue(Flags.FLAG_DISABLED_RO, + FeatureFlags::disabledRo); } @Override @UnsupportedAppUsage public boolean disabledRw() { - return getValue(Flags.FLAG_DISABLED_RW); + return getValue(Flags.FLAG_DISABLED_RW, + FeatureFlags::disabledRw); } @Override @UnsupportedAppUsage public boolean disabledRwInOtherNamespace() { - return getValue(Flags.FLAG_DISABLED_RW_IN_OTHER_NAMESPACE); + return getValue(Flags.FLAG_DISABLED_RW_IN_OTHER_NAMESPACE, + FeatureFlags::disabledRwInOtherNamespace); } @Override @UnsupportedAppUsage public boolean enabledFixedRo() { - return getValue(Flags.FLAG_ENABLED_FIXED_RO); + return getValue(Flags.FLAG_ENABLED_FIXED_RO, + FeatureFlags::enabledFixedRo); } @Override @UnsupportedAppUsage public boolean enabledRo() { - return getValue(Flags.FLAG_ENABLED_RO); + return getValue(Flags.FLAG_ENABLED_RO, + FeatureFlags::enabledRo); } @Override @UnsupportedAppUsage public boolean enabledRw() { - return getValue(Flags.FLAG_ENABLED_RW); - } - public void setFlag(String flagName, boolean value) { - if (!this.mFlagMap.containsKey(flagName)) { - throw new IllegalArgumentException("no such flag " + flagName); - } - this.mFlagMap.put(flagName, value); - } - public void resetAll() { - for (Map.Entry entry : mFlagMap.entrySet()) { - entry.setValue(null); - } + return getValue(Flags.FLAG_ENABLED_RW, + FeatureFlags::enabledRw); } + public boolean isFlagReadOnlyOptimized(String flagName) { if (mReadOnlyFlagsSet.contains(flagName) && isOptimizationEnabled()) { @@ -1082,27 +1151,27 @@ mod tests { } return false; } + @com.android.aconfig.annotations.AssumeTrueForR8 private boolean isOptimizationEnabled() { return false; } - private boolean getValue(String flagName) { - Boolean value = this.mFlagMap.get(flagName); - if (value == null) { - throw new IllegalArgumentException(flagName + " is not set"); - } - return value; + + protected boolean getValue(String flagName, Predicate getter) { + return mGetValueImpl.test(flagName, getter); } - private Map mFlagMap = new HashMap<>( - Map.ofEntries( - Map.entry(Flags.FLAG_DISABLED_RO, false), - Map.entry(Flags.FLAG_DISABLED_RW, false), - Map.entry(Flags.FLAG_DISABLED_RW_IN_OTHER_NAMESPACE, false), - Map.entry(Flags.FLAG_ENABLED_FIXED_RO, false), - Map.entry(Flags.FLAG_ENABLED_RO, false), - Map.entry(Flags.FLAG_ENABLED_RW, false) - ) - ); + + public List getFlagNames() { + return Arrays.asList( + Flags.FLAG_DISABLED_RO, + Flags.FLAG_DISABLED_RW, + Flags.FLAG_DISABLED_RW_IN_OTHER_NAMESPACE, + Flags.FLAG_ENABLED_FIXED_RO, + Flags.FLAG_ENABLED_RO, + Flags.FLAG_ENABLED_RW + ); + } + private Set mReadOnlyFlagsSet = new HashSet<>( Arrays.asList( Flags.FLAG_DISABLED_RO, @@ -1116,11 +1185,16 @@ mod tests { ); } "#; + let mut file_set = HashMap::from([ ("com/android/aconfig/test/Flags.java", expect_flags_content), ("com/android/aconfig/test/FeatureFlagsImpl.java", expect_featureflagsimpl_content), ("com/android/aconfig/test/FeatureFlags.java", expect_featureflags_content), - ("com/android/aconfig/test/FakeFeatureFlagsImpl.java", expect_fakefeatureflags_content), + ("com/android/aconfig/test/CustomFeatureFlags.java", expect_customfeatureflags_content), + ( + "com/android/aconfig/test/FakeFeatureFlagsImpl.java", + EXPECTED_FAKEFEATUREFLAGSIMPL_CONTENT, + ), ]); for file in generated_files { diff --git a/tools/aconfig/aconfig/templates/CustomFeatureFlags.java.template b/tools/aconfig/aconfig/templates/CustomFeatureFlags.java.template new file mode 100644 index 0000000000..b82b9cb827 --- /dev/null +++ b/tools/aconfig/aconfig/templates/CustomFeatureFlags.java.template @@ -0,0 +1,70 @@ +package {package_name}; + +{{ if not library_exported- }} +// TODO(b/303773055): Remove the annotation after access issue is resolved. +import android.compat.annotation.UnsupportedAppUsage; +{{ -endif }} +import java.util.Arrays; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import java.util.function.BiPredicate; +import java.util.function.Predicate; + +/** @hide */ +public class CustomFeatureFlags implements FeatureFlags \{ + + private BiPredicate> mGetValueImpl; + + public CustomFeatureFlags(BiPredicate> getValueImpl) \{ + mGetValueImpl = getValueImpl; + } + +{{ -for item in flag_elements}} + @Override +{{ if not library_exported }} @UnsupportedAppUsage{{ -endif }} + public boolean {item.method_name}() \{ + return getValue(Flags.FLAG_{item.flag_name_constant_suffix}, + FeatureFlags::{item.method_name}); + } +{{ endfor }} + +{{ -if not library_exported }} + public boolean isFlagReadOnlyOptimized(String flagName) \{ + if (mReadOnlyFlagsSet.contains(flagName) && + isOptimizationEnabled()) \{ + return true; + } + return false; + } + + @com.android.aconfig.annotations.AssumeTrueForR8 + private boolean isOptimizationEnabled() \{ + return false; + } +{{ -endif }} + + protected boolean getValue(String flagName, Predicate getter) \{ + return mGetValueImpl.test(flagName, getter); + } + + public List getFlagNames() \{ + return Arrays.asList( + {{ -for item in flag_elements }} + Flags.FLAG_{item.flag_name_constant_suffix} + {{ -if not @last }},{{ endif }} + {{ -endfor }} + ); + } + + private Set mReadOnlyFlagsSet = new HashSet<>( + Arrays.asList( + {{ -for item in flag_elements }} + {{ -if not item.is_read_write }} + Flags.FLAG_{item.flag_name_constant_suffix}, + {{ -endif }} + {{ -endfor }} + ""{# The empty string here is to resolve the ending comma #} + ) + ); +} diff --git a/tools/aconfig/aconfig/templates/FakeFeatureFlagsImpl.java.template b/tools/aconfig/aconfig/templates/FakeFeatureFlagsImpl.java.template index 177e711e77..c20d3c5061 100644 --- a/tools/aconfig/aconfig/templates/FakeFeatureFlagsImpl.java.template +++ b/tools/aconfig/aconfig/templates/FakeFeatureFlagsImpl.java.template @@ -1,27 +1,30 @@ package {package_name}; -{{ if not library_exported- }} -// TODO(b/303773055): Remove the annotation after access issue is resolved. -import android.compat.annotation.UnsupportedAppUsage; -{{ -endif }} -import java.util.Arrays; + import java.util.HashMap; -import java.util.HashSet; import java.util.Map; -import java.util.Set; +import java.util.function.Predicate; /** @hide */ -public class FakeFeatureFlagsImpl implements FeatureFlags \{ +public class FakeFeatureFlagsImpl extends CustomFeatureFlags \{ + private Map mFlagMap = new HashMap<>(); + public FakeFeatureFlagsImpl() \{ - resetAll(); + super(null); + // Initialize the map with null values + for (String flagName : getFlagNames()) \{ + mFlagMap.put(flagName, null); + } } -{{ for item in flag_elements}} @Override -{{ if not library_exported }} @UnsupportedAppUsage{{ -endif }} - public boolean {item.method_name}() \{ - return getValue(Flags.FLAG_{item.flag_name_constant_suffix}); + protected boolean getValue(String flagName, Predicate getter) \{ + Boolean value = this.mFlagMap.get(flagName); + if (value == null) \{ + throw new IllegalArgumentException(flagName + " is not set"); + } + return value; } -{{ endfor}} + public void setFlag(String flagName, boolean value) \{ if (!this.mFlagMap.containsKey(flagName)) \{ throw new IllegalArgumentException("no such flag " + flagName); @@ -34,46 +37,4 @@ public class FakeFeatureFlagsImpl implements FeatureFlags \{ entry.setValue(null); } } -{{ if not library_exported }} - public boolean isFlagReadOnlyOptimized(String flagName) \{ - if (mReadOnlyFlagsSet.contains(flagName) && - isOptimizationEnabled()) \{ - return true; - } - return false; - } - - @com.android.aconfig.annotations.AssumeTrueForR8 - private boolean isOptimizationEnabled() \{ - return false; - } -{{ -endif }} - private boolean getValue(String flagName) \{ - Boolean value = this.mFlagMap.get(flagName); - if (value == null) \{ - throw new IllegalArgumentException(flagName + " is not set"); - } - return value; - } - - - private Map mFlagMap = new HashMap<>( - Map.ofEntries( - {{ -for item in flag_elements }} - Map.entry(Flags.FLAG_{item.flag_name_constant_suffix}, false) - {{ -if not @last }},{{ endif }} - {{ -endfor }} - ) - ); - - private Set mReadOnlyFlagsSet = new HashSet<>( - Arrays.asList( - {{ -for item in flag_elements }} - {{ -if not item.is_read_write }} - Flags.FLAG_{item.flag_name_constant_suffix}, - {{ -endif }} - {{ -endfor }} - ""{# The empty string here is to resolve the ending comma #} - ) - ); }