Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit c3bc24f3 authored by Jeff DeCew's avatar Jeff DeCew
Browse files

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::<Result<Vec<OutputFile>>>()
> +    [
> +        "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::<Result<Vec<OutputFile>>>()
> -    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<String, Predicate<FeatureFlags>> mGetValueImpl;
> +
> +        public CustomFeatureFlags(BiPredicate<String, Predicate<FeatureFlags>> 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<FeatureFlags> getter) {
> +            return mGetValueImpl.test(flagName, getter);
> -        private Map<String, Boolean> 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<String> 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<String, Boolean> 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<FeatureFlags> 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<String, Predicate<FeatureFlags>> mGetValueImpl;
> +
> +            public CustomFeatureFlags(BiPredicate<String, Predicate<FeatureFlags>> 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<FeatureFlags> getter) {
> +                return mGetValueImpl.test(flagName, getter);
> -            public void resetAll() {
> -                for (Map.Entry entry : mFlagMap.entrySet()) {
> -                    entry.setValue(null);
> -                }
> +
> +            public List<String> 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<String, Boolean> 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<String, Predicate<FeatureFlags>> mGetValueImpl;
> +
> +            public CustomFeatureFlags(BiPredicate<String, Predicate<FeatureFlags>> 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<FeatureFlags> getter) {
> +                return mGetValueImpl.test(flagName, getter);
> -            private Map<String, Boolean> 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<String> 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<String, Predicate<FeatureFlags>> mGetValueImpl;
> +
> +    public CustomFeatureFlags(BiPredicate<String, Predicate<FeatureFlags>> 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<FeatureFlags> getter) \{
> +        return mGetValueImpl.test(flagName, getter);
> +    }
> +
> +    public List<String> getFlagNames() \{
> +        return Arrays.asList(
> +            {{ -for item in flag_elements }}
> +            Flags.FLAG_{item.flag_name_constant_suffix}
> +            {{ -if not @last }},{{ endif }}
> +            {{ -endfor }}
> +        );
> +    }
> +
> +    private Set<String> 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<String, Boolean> 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<FeatureFlags> 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<String, Boolean> 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<String> 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<Vec<OutputFile>>>
[[[Result patch trimmed due to size. Decoded string size: 26308. Decoded string SHA1: bb07ee948a630a6bc4cfbbf2a8df178f3e6d3103.]]]

Change-Id: I94184633303b9d76f7943451fb3b2c93d071ec1c
parent e0c74fbd
Loading
Loading
Loading
Loading
+202 −128
Original line number Diff line number Diff line
@@ -63,19 +63,26 @@ 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"]
    [
        "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),
            })
        Ok(OutputFile { contents: template.render(file, &context)?.into(), path: path.join(file) })
    })
    .collect::<Result<Vec<OutputFile>>>()
}
@@ -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<String, Predicate<FeatureFlags>> mGetValueImpl;

        public CustomFeatureFlags(BiPredicate<String, Predicate<FeatureFlags>> 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<FeatureFlags> getter) {
            return mGetValueImpl.test(flagName, getter);
        }
        private Map<String, Boolean> 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<String> 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<String> 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<String, Boolean> 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<FeatureFlags> 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<String, Predicate<FeatureFlags>> mGetValueImpl;

            public CustomFeatureFlags(BiPredicate<String, Predicate<FeatureFlags>> 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);
            }
            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 getValue(Flags.FLAG_ENABLED_RO_EXPORTED,
                    FeatureFlags::enabledRoExported);
            }
                return value;

            protected boolean getValue(String flagName, Predicate<FeatureFlags> getter) {
                return mGetValueImpl.test(flagName, getter);
            }
            private Map<String, Boolean> 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)
                )

            public List<String> getFlagNames() {
                return Arrays.asList(
                    Flags.FLAG_DISABLED_RW_EXPORTED,
                    Flags.FLAG_ENABLED_FIXED_RO_EXPORTED,
                    Flags.FLAG_ENABLED_RO_EXPORTED
                );
            }

            private Set<String> 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<String, Predicate<FeatureFlags>> mGetValueImpl;

            public CustomFeatureFlags(BiPredicate<String, Predicate<FeatureFlags>> 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<FeatureFlags> getter) {
                return mGetValueImpl.test(flagName, getter);
            }
            private Map<String, Boolean> 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<String> 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<String> 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 {
+70 −0
Original line number Diff line number Diff line
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<String, Predicate<FeatureFlags>> mGetValueImpl;

    public CustomFeatureFlags(BiPredicate<String, Predicate<FeatureFlags>> 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<FeatureFlags> getter) \{
        return mGetValueImpl.test(flagName, getter);
    }

    public List<String> getFlagNames() \{
        return Arrays.asList(
            {{ -for item in flag_elements }}
            Flags.FLAG_{item.flag_name_constant_suffix}
            {{ -if not @last }},{{ endif }}
            {{ -endfor }}
        );
    }

    private Set<String> 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 #}
        )
    );
}
+17 −56
Original line number Diff line number Diff line
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<String, Boolean> 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<FeatureFlags> getter) \{
        Boolean value = this.mFlagMap.get(flagName);
        if (value == null) \{
            throw new IllegalArgumentException(flagName + " is not set");
        }
{{ endfor}}
        return value;
    }

    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<String, Boolean> 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<String> 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 #}
        )
    );
}