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

Commit ad9fa43d authored by Jared Duke's avatar Jared Duke
Browse files

Add system feature codegen for <unavailable-feature /> defs only

Add a new argument to SystemFeaturesGenerator that allows parsing *only*
the <unavailable-feature /> defs from provided feature XML files. This
is a safer incremental step, as any feature marked <unavailable /> by
any config file can never be overridden or updated (via mainline).

Test: atest systemfeatures-gen-golden-tests systemfeatures-gen-tests
Flag: build.RELEASE_USE_SYSTEM_FEATURE_XML_FOR_UNAVAILABLE_FEATURES
Bug: 203143243
Change-Id: I2c78e884c6f37c5adaacb0b19a4f7ba198449dac
parent b8afec82
Loading
Loading
Loading
Loading
+4 −1
Original line number Diff line number Diff line
@@ -91,7 +91,8 @@ genrule {
        "$(location systemfeatures-gen-tool) com.android.systemfeatures.RwFeatures --readonly=false --feature=WATCH:1 --feature=WIFI:0 --feature=VULKAN:UNAVAILABLE --feature=AUTO: > $(location RwFeatures.java) && " +
        "$(location systemfeatures-gen-tool) com.android.systemfeatures.RoFeatures --readonly=true --feature=WATCH:1 --feature=WIFI:0 --feature=VULKAN:UNAVAILABLE --feature=AUTO: --feature-apis=WATCH,PC > $(location RoFeatures.java) && " +
        "$(location systemfeatures-gen-tool) com.android.systemfeatures.RwFeaturesFromXml --readonly=false --feature-xml-files=$(location tests/data/features-1.xml),$(location tests/data/features-2.xml) > $(location RwFeaturesFromXml.java) && " +
        "$(location systemfeatures-gen-tool) com.android.systemfeatures.RoFeaturesFromXml --readonly=true --feature-xml-files=$(location tests/data/features-1.xml),$(location tests/data/features-2.xml) > $(location RoFeaturesFromXml.java)",
        "$(location systemfeatures-gen-tool) com.android.systemfeatures.RoFeaturesFromXml --readonly=true --feature-xml-files=$(location tests/data/features-1.xml),$(location tests/data/features-2.xml) > $(location RoFeaturesFromXml.java) && " +
        "$(location systemfeatures-gen-tool) com.android.systemfeatures.RoUnavailableFeaturesFromXml --readonly=true --unavailable-feature-xml-files=$(location tests/data/features-1.xml),$(location tests/data/features-2.xml) > $(location RoUnavailableFeaturesFromXml.java)",
    out: [
        "RwNoFeatures.java",
        "RoNoFeatures.java",
@@ -99,6 +100,7 @@ genrule {
        "RoFeatures.java",
        "RwFeaturesFromXml.java",
        "RoFeaturesFromXml.java",
        "RoUnavailableFeaturesFromXml.java",
    ],
    tools: ["systemfeatures-gen-tool"],
}
@@ -144,6 +146,7 @@ genrule {
        "tests/gen/RoFeatures.java.gen",
        "tests/gen/RwFeaturesFromXml.java.gen",
        "tests/gen/RoFeaturesFromXml.java.gen",
        "tests/gen/RoUnavailableFeaturesFromXml.java.gen",
    ],
}

+30 −7
Original line number Diff line number Diff line
@@ -71,6 +71,7 @@ import org.w3c.dom.Node
object SystemFeaturesGenerator {
    private const val FEATURE_ARG = "--feature="
    private const val FEATURE_XML_FILES_ARG = "--feature-xml-files="
    private const val UNAVAILABLE_FEATURE_XML_FILES_ARG = "--unavailable-feature-xml-files="
    private const val FEATURE_APIS_ARG = "--feature-apis="
    private const val READONLY_ARG = "--readonly="
    private const val METADATA_ONLY_ARG = "--metadata-only="
@@ -103,7 +104,13 @@ object SystemFeaturesGenerator {
        println("  --feature-xml-files=\$XML_FILE_1,\$XML_FILE_2")
        println("                           A comma-separated list of XML permission feature files")
        println("                           to parse and add to the generated query APIs. The file")
        println("                           format matches that used by SystemConfig parsing.")
        println("                           format matches that used by SystemConfig parsing. This")
        println("                           parses <feature /> and <unavailable-feature /> defs.")
        println("  --unavailable-feature-xml-files=\$XML_FILE_1,\$XML_FILE_2")
        println("                           A comma-separated list of XML permission feature files")
        println("                           to parse and add to the generated query APIs. The file")
        println("                           format matches that used by SystemConfig parsing. This")
        println("                           parses *only* <unavailable-feature /> defs.")
        println("  --metadata-only=true|false Whether to simply output metadata about the")
        println("                             generated API surface.")
    }
@@ -155,6 +162,14 @@ object SystemFeaturesGenerator {
                        parseFeatureXmlFiles(arg.substring(FEATURE_XML_FILES_ARG.length).split(","))
                    )
                }
                arg.startsWith(UNAVAILABLE_FEATURE_XML_FILES_ARG) -> {
                    featureArgs.addAll(
                        parseFeatureXmlFiles(
                            arg.substring(UNAVAILABLE_FEATURE_XML_FILES_ARG.length).split(","),
                            parseOnlyUnavailableFeatures = true,
                        )
                    )
                }
                else -> outputClassName = ClassName.bestGuess(arg)
            }
        }
@@ -259,10 +274,13 @@ object SystemFeaturesGenerator {
    /**
     * Parses a list of feature permission XML file paths into a list of FeatureInfo definitions.
     */
    private fun parseFeatureXmlFiles(filePaths: Collection<String>): Collection<FeatureInfo> =
    private fun parseFeatureXmlFiles(
        filePaths: Collection<String>,
        parseOnlyUnavailableFeatures: Boolean = false,
    ): Collection<FeatureInfo> =
        filePaths.flatMap {
            try {
                parseFeatureXmlFile(File(it))
                parseFeatureXmlFile(File(it), parseOnlyUnavailableFeatures)
            } catch (e: Exception) {
                throw IllegalArgumentException("Error parsing feature XML file: $it", e)
            }
@@ -271,7 +289,10 @@ object SystemFeaturesGenerator {
    /**
     * Parses a feature permission XML file into a (possibly empty) list of FeatureInfo definitions.
     */
    private fun parseFeatureXmlFile(file: File): Collection<FeatureInfo> {
    private fun parseFeatureXmlFile(
        file: File,
        parseOnlyUnavailableFeatures: Boolean = false,
    ): Collection<FeatureInfo> {
        val doc = DocumentBuilderFactory.newInstance().newDocumentBuilder().parse(file)
        doc.documentElement.normalize()

@@ -291,9 +312,11 @@ object SystemFeaturesGenerator {
                .filter { it.nodeType == Node.ELEMENT_NODE }
                .map { it as Element }
                .mapNotNull { element ->
                    when (element.tagName) {
                        "feature" -> parseFeatureElement(element)
                        "unavailable-feature" -> parseUnavailableFeatureElement(element)
                    when {
                        element.tagName == "unavailable-feature" ->
                            parseUnavailableFeatureElement(element)
                        !parseOnlyUnavailableFeatures && element.tagName == "feature" ->
                            parseFeatureElement(element)
                        else -> null
                    }
                }
+66 −0
Original line number Diff line number Diff line
// This file is auto-generated. DO NOT MODIFY.
// Args: com.android.systemfeatures.RoUnavailableFeaturesFromXml \
//            --readonly=true \
//            --unavailable-feature-xml-files=frameworks/base/tools/systemfeatures/tests/data/features-1.xml,frameworks/base/tools/systemfeatures/tests/data/features-2.xml
package com.android.systemfeatures;

import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.Context;
import android.content.pm.FeatureInfo;
import android.content.pm.PackageManager;
import android.util.ArrayMap;
import com.android.aconfig.annotations.AssumeFalseForR8;

/**
 * @hide
 */
public final class RoUnavailableFeaturesFromXml {
    /**
     * Check for FEATURE_PC.
     *
     * @hide
     */
    @AssumeFalseForR8
    public static boolean hasFeaturePc(Context context) {
        return false;
    }

    /**
     * Check for FEATURE_WATCH.
     *
     * @hide
     */
    @AssumeFalseForR8
    public static boolean hasFeatureWatch(Context context) {
        return false;
    }

    private static boolean hasFeatureFallback(Context context, String featureName) {
        return context.getPackageManager().hasSystemFeature(featureName);
    }

    /**
     * @hide
     */
    @Nullable
    public static Boolean maybeHasFeature(String featureName, int version) {
        switch (featureName) {
            case PackageManager.FEATURE_PC: return false;
            case PackageManager.FEATURE_WATCH: return false;
            default: break;
        }
        return null;
    }

    /**
     * Gets features marked as available at compile-time, keyed by name.
     *
     * @hide
     */
    @NonNull
    public static ArrayMap<String, FeatureInfo> getReadOnlySystemEnabledFeatures() {
        ArrayMap<String, FeatureInfo> features = new ArrayMap<>(0);
        return features;
    }
}
+10 −0
Original line number Diff line number Diff line
@@ -108,4 +108,14 @@ public class SystemFeaturesGeneratorApiTest {
        SystemFeaturesGenerator.generate(args, mOut);
        verify(mOut, never()).append(any());
    }

    @Test
    public void testUnavailableFeatureXmlFiles() throws IOException {
        final String[] args = new String[] {
            "com.foo.Features",
            "--unavailable-feature-xml-files=tests/data/features-1.xml,tests/data/features-2.xml",
        };
        SystemFeaturesGenerator.generate(args, mOut);
        verify(mOut, atLeastOnce()).append(any());
    }
}
+33 −0
Original line number Diff line number Diff line
@@ -252,4 +252,37 @@ public class SystemFeaturesGeneratorTest {
        assertThat(compiledFeatures.get(PackageManager.FEATURE_EMBEDDED).version).isEqualTo(1);
        assertThat(compiledFeatures.get(PackageManager.FEATURE_WIFI).version).isEqualTo(0);
    }

    @Test
    public void testReadonlyWithUnavailableFeaturesFromXml() {
        // Always use the build-time feature version for explicit feature queries for unavailable
        // features, never falling back to the runtime query.
        assertThat(RoUnavailableFeaturesFromXml.hasFeaturePc(mContext)).isFalse();
        assertThat(RoUnavailableFeaturesFromXml.hasFeatureWatch(mContext)).isFalse();
        verify(mPackageManager, never()).hasSystemFeature(anyString(), anyInt());

        // When parsing only unavailable feature types from XML, conditional queries should reflect:
        //  * Disabled if the feature was *ever* declared w/ <unavailable-feature />
        //  * Unknown otherwise.

        // <unavailable-feature />
        assertThat(RoUnavailableFeaturesFromXml.maybeHasFeature(PackageManager.FEATURE_PC, 0))
                .isFalse();
        assertThat(RoUnavailableFeaturesFromXml.maybeHasFeature(PackageManager.FEATURE_WATCH, 0))
                .isFalse();

        // For other feature types, conditional queries should report null (unknown).
        assertThat(
                        RoUnavailableFeaturesFromXml.maybeHasFeature(
                                PackageManager.FEATURE_BLUETOOTH, 0))
                .isNull();
        assertThat(RoUnavailableFeaturesFromXml.maybeHasFeature(PackageManager.FEATURE_EMBEDDED, 0))
                .isNull();
        assertThat(RoUnavailableFeaturesFromXml.maybeHasFeature(PackageManager.FEATURE_WIFI, 0))
                .isNull();
        assertThat(RoUnavailableFeaturesFromXml.maybeHasFeature("com.arbitrary.feature", 0))
                .isNull();
        assertThat(RoUnavailableFeaturesFromXml.maybeHasFeature("", 0)).isNull();
        assertThat(RoUnavailableFeaturesFromXml.getReadOnlySystemEnabledFeatures()).isEmpty();
    }
}