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

Commit 807df5e7 authored by Jared Duke's avatar Jared Duke
Browse files

Support system feature lookups in host tooling

Introduce a host utility that does the following:
  * Scrapes public Metalava API files for PackageManager.FEATURE_ defs
  * Generates a simple utility class that maps from feature var values
    to their respective feature names, e.g.,
      * "android.software.print" -> "FEATURE_PRINTING"

This lookup will enable host parsing of <feature /> and
<unavailable-feature /> permission defs and integration in existing
system feature codegen, *without* needing a strict dependency on the
framework. This breaks any cyclic deps, allowing us to scrape product
permission .xml files and feed that back into build-time system feature
codegen for the framework.

Note: This change only adds the host utility, it doesn't make changes
to existing codegen. A follow-up change will do so, behind a build flag.

Bug: 203143243
Test: atest systemfeatures-gen-tests
Flag: EXEMPT simple tool addition
Change-Id: I210674c85efb068ac0e2d1898df590aab330db2b
parent e835c3b4
Loading
Loading
Loading
Loading
+2 −0
Original line number Diff line number Diff line
@@ -16,6 +16,7 @@ package {
    default_visibility: [
        "//frameworks/base",
        "//frameworks/base/api",
        "//frameworks/base/tools/systemfeatures",
    ],
    // See: http://go/android-license-faq
    // A large-scale-change added 'default_applicable_licenses' to import
@@ -63,6 +64,7 @@ filegroup {
    visibility: [
        "//frameworks/base",
        "//frameworks/base/api",
        "//frameworks/base/tools/systemfeatures",
        "//cts/tests/signature/api",
    ],
}
+51 −3
Original line number Diff line number Diff line
@@ -8,12 +8,13 @@ package {
    default_team: "trendy_team_system_performance",
}

// Code generation for compile-time system feature queries in the framework.
java_library_host {
    name: "systemfeatures-gen-lib",
    srcs: [
        "src/**/*.java",
        "src/**/*.kt",
        ":framework-metalava-annotations",
        ":systemfeatures-lookup-srcs",
        "src/com/android/systemfeatures/SystemFeaturesGenerator.kt",
    ],
    static_libs: [
        "guava",
@@ -27,10 +28,56 @@ java_binary_host {
    static_libs: ["systemfeatures-gen-lib"],
}

// Code generation for system feature name lookups in host tools that cannot
// depend on the framework.
java_library_host {
    name: "systemfeatures-lookup-gen-lib",
    srcs: ["src/com/android/systemfeatures/SystemFeaturesLookupGenerator.kt"],
    static_libs: [
        "javapoet",
        "metalava-signature-reader",
    ],
    visibility: ["//visibility:private"],
}

java_binary_host {
    name: "systemfeatures-lookup-gen-tool",
    main_class: "com.android.systemfeatures.SystemFeaturesLookupGenerator",
    static_libs: ["systemfeatures-lookup-gen-lib"],
    visibility: ["//visibility:private"],
}

genrule {
    name: "systemfeatures-lookup-srcs",
    cmd: "$(location systemfeatures-lookup-gen-tool) $(in) > $(out)",
    out: ["SystemFeaturesLookup.java"],
    srcs: [
        ":non-updatable-current.txt",
        ":non-updatable-module-lib-current.txt",
        ":non-updatable-system-current.txt",
        ":non-updatable-test-current.txt",
    ],
    tools: ["systemfeatures-lookup-gen-tool"],
    visibility: ["//visibility:private"],
}

// Code generation for runtime system feature metadata queries in the framework.
java_library_host {
    name: "systemfeatures-metadata-lib",
    srcs: [
        ":framework-metalava-annotations",
        "src/com/android/systemfeatures/SystemFeaturesMetadataProcessor.kt",
    ],
    static_libs: [
        "guava",
        "javapoet",
    ],
}

java_plugin {
    name: "systemfeatures-metadata-processor",
    processor_class: "com.android.systemfeatures.SystemFeaturesMetadataProcessor",
    static_libs: ["systemfeatures-gen-lib"],
    static_libs: ["systemfeatures-metadata-lib"],
}

genrule {
@@ -66,6 +113,7 @@ java_test_host {
        "objenesis",
        "mockito",
        "systemfeatures-gen-lib",
        "systemfeatures-lookup-gen-lib",
        "truth",
    ],
    plugins: ["systemfeatures-metadata-processor"],
+6 −5
Original line number Diff line number Diff line
@@ -213,11 +213,12 @@ object SystemFeaturesGenerator {

    private fun parseFeatureName(name: String): String =
        when {
            name.startsWith("android") ->
                throw IllegalArgumentException(
                    "Invalid feature name input: \"android\"-namespaced features must be " +
                    "provided as PackageManager.FEATURE_* suffixes, not raw feature strings."
            name.startsWith("android") -> {
                SystemFeaturesLookup.getDeclaredFeatureVarNameFromValue(name)
                    ?: throw IllegalArgumentException(
                        "Unrecognized Android system feature name: $name"
                    )
            }
            name.startsWith("FEATURE_") -> name
            else -> "FEATURE_$name"
        }
+116 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2025 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.android.systemfeatures

import com.android.tools.metalava.model.text.ApiFile
import com.squareup.javapoet.ClassName
import com.squareup.javapoet.JavaFile
import com.squareup.javapoet.MethodSpec
import com.squareup.javapoet.TypeSpec
import java.io.File
import javax.lang.model.element.Modifier

/*
 * Simple Java code generator that takes as input a list of Metalava API files and generates an
 * accessory class that maps from PackageManager system feature variable values to their
 * declared PackageManager variable names. This is needed for host tooling that cannot depend
 * directly on the base framework lib/srcs.
 *
 * <pre>
 * package com.android.systemfeatures;
 * public final class SystemFeaturesLookup {
 *     // Gets the declared system feature var name from its string value.
 *     // Example: "android.software.print" -> "FEATURE_PRINTING"
 *     public static String getDeclaredFeatureVarNameFromValue(String featureVarValue);
 * }
 * </pre>
 */
object SystemFeaturesLookupGenerator {

    /** Main entrypoint for system feature constant lookup codegen. */
    @JvmStatic
    fun main(args: Array<String>) {
        generate(args.asIterable(), System.out)
    }

    /**
     * Simple API entrypoint for system feature lookup codegen.
     *
     * Given a list of Metalava API files, pipes a generated SystemFeaturesLookup class into output.
     */
    @JvmStatic
    fun generate(apiFilePaths: Iterable<String>, output: Appendable) {
        val featuresMap = parse(apiFilePaths)

        val stringClassName = ClassName.get(String::class.java)

        val featureLookupMethod =
            MethodSpec.methodBuilder("getDeclaredFeatureVarNameFromValue")
                .addModifiers(Modifier.PUBLIC, Modifier.STATIC)
                .addAnnotation(ClassName.get("android.annotation", "Nullable"))
                .addJavadoc("Gets the declared system feature var name from its string value.")
                .addJavadoc("\n\nExample: \"android.software.print\" -> \"FEATURE_PRINTING\"")
                .addJavadoc("\n\n@hide")
                .returns(stringClassName)
                .addParameter(stringClassName, "featureVarValue")
                .beginControlFlow("switch (featureVarValue)")
                .apply {
                    featuresMap.forEach { (key, value) ->
                        addStatement("case \$S: return \$S", key, value)
                    }
                }
                .addStatement("default: return null")
                .endControlFlow()
                .build()

        val outputClassName = ClassName.get("com.android.systemfeatures", "SystemFeaturesLookup")
        val systemFeaturesApiLookupClass =
            TypeSpec.classBuilder(outputClassName)
                .addModifiers(Modifier.PUBLIC, Modifier.FINAL)
                .addMethod(featureLookupMethod)
                .addJavadoc("@hide")
                .build()

        JavaFile.builder(outputClassName.packageName(), systemFeaturesApiLookupClass)
            .indent("    ")
            .skipJavaLangImports(true)
            .addFileComment("This file is auto-generated. DO NOT MODIFY.\n")
            .build()
            .writeTo(output)
    }

    /**
     * Given a list of Metalava API files, extracts a mapping from all @SdkConstantType.FEATURE
     * PackageManager values to their declared variable names, e.g.,
     * - "android.hardware.type.automotive" -> "FEATURE_AUTOMOTIVE"
     * - "android.software.print" -> "FEATURE_PRINTING"
     */
    @JvmStatic
    fun parse(apiFilePaths: Iterable<String>): Map<String, String> {
        return ApiFile.parseApi(apiFilePaths.map(::File))
            .findClass("android.content.pm.PackageManager")
            ?.fields()
            ?.filter { field ->
                field.type().isString() &&
                    field.modifiers.isStatic() &&
                    field.modifiers.isFinal() &&
                    field.name().startsWith("FEATURE_") &&
                    field.legacyInitialValue() != null
            }
            ?.associateBy({ it.legacyInitialValue()!!.toString() }, { it.name() }) ?: emptyMap()
    }
}
+10 −0
Original line number Diff line number Diff line
@@ -78,4 +78,14 @@ public class SystemFeaturesGeneratorApiTest {
        SystemFeaturesGenerator.generate(args, mOut);
        verify(mOut, never()).append(any());
    }

    @Test
    public void testValidFeatureNameFromAndroidNamespace() throws IOException {
        final String[] args = new String[] {
            "com.foo.Features",
            "--feature=android.hardware.touchscreen:0",
        };
        SystemFeaturesGenerator.generate(args, mOut);
        verify(mOut, atLeastOnce()).append(any());
    }
}
Loading