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

Commit 04c0e02c authored by Jared Duke's avatar Jared Duke
Browse files

Add SystemFeaturesMetadata.maybeGetSdkFeatureIndex

Add a simple index lookup method for feature names. This returns:
  * [0, SDK_FEATURE_INDEX) for PackageManager-defined system features
  * -1 otherwise

This will be used in a follow-up to efficiently compute a dense array
of cached system feature versions that can be used for fast lookup in
client processes.

Bug: 375000483
Test: atest SystemFeaturesMetadataProcessorTest
Flag: EXEMPT currently unused
Change-Id: I145c08f7eb58b832534f62950357a18f7b4e3651
parent 93b436df
Loading
Loading
Loading
Loading
+73 −23
Original line number Diff line number Diff line
@@ -17,8 +17,10 @@
package com.android.systemfeatures

import android.annotation.SdkConstant
import com.squareup.javapoet.ClassName
import com.squareup.javapoet.FieldSpec
import com.squareup.javapoet.JavaFile
import com.squareup.javapoet.MethodSpec
import com.squareup.javapoet.TypeSpec
import java.io.IOException
import javax.annotation.processing.AbstractProcessor
@@ -27,6 +29,7 @@ import javax.annotation.processing.RoundEnvironment
import javax.lang.model.SourceVersion
import javax.lang.model.element.Modifier
import javax.lang.model.element.TypeElement
import javax.lang.model.element.VariableElement
import javax.tools.Diagnostic

/*
@@ -35,7 +38,16 @@ import javax.tools.Diagnostic
 * <p>The output is a single class file, `com.android.internal.pm.SystemFeaturesMetadata`, with
 * properties computed from feature constant definitions in the PackageManager class. This
 * class is only produced if the processed environment includes PackageManager; all other
 * invocations are ignored.
 * invocations are ignored. The generated API is as follows:
 *
 * <pre>
 * package android.content.pm;
 * public final class SystemFeaturesMetadata {
 *     public static final int SDK_FEATURE_COUNT;
 *     // @return [0, SDK_FEATURE_COUNT) if an SDK-defined system feature, -1 otherwise.
 *     public static int maybeGetSdkFeatureIndex(String featureName);
 * }
 * </pre>
 */
class SystemFeaturesMetadataProcessor : AbstractProcessor() {

@@ -56,19 +68,31 @@ class SystemFeaturesMetadataProcessor : AbstractProcessor() {
            return false
        }

        // We're only interested in feature constants defined in PackageManager.
        var featureCount = 0
        roundEnv.getElementsAnnotatedWith(SdkConstant::class.java).forEach {
            if (
        // Collect all FEATURE-annotated fields from PackageManager, and
        //  1) Use the field values to de-duplicate, as there can be multiple FEATURE_* fields that
        //     map to the same feature string name value.
        //  2) Ensure they're sorted to ensure consistency and determinism between builds.
        val featureVarNames =
            roundEnv
                .getElementsAnnotatedWith(SdkConstant::class.java)
                .asSequence()
                .filter {
                    it.enclosingElement == packageManagerType &&
                        it.getAnnotation(SdkConstant::class.java).value ==
                            SdkConstant.SdkConstantType.FEATURE
            ) {
                featureCount++
                }
                .mapNotNull { element ->
                    (element as? VariableElement)?.let { varElement ->
                        varElement.getConstantValue()?.toString() to
                            varElement.simpleName.toString()
                    }
                }
                .toMap()
                .values
                .sorted()
                .toList()

        if (featureCount == 0) {
        if (featureVarNames.isEmpty()) {
            // This is fine, and happens for any environment that doesn't include PackageManager.
            return false
        }
@@ -77,16 +101,8 @@ class SystemFeaturesMetadataProcessor : AbstractProcessor() {
            TypeSpec.classBuilder("SystemFeaturesMetadata")
                .addModifiers(Modifier.PUBLIC, Modifier.FINAL)
                .addJavadoc("@hide")
                .addField(
                    FieldSpec.builder(Int::class.java, "SDK_FEATURE_COUNT")
                        .addModifiers(Modifier.PUBLIC, Modifier.STATIC, Modifier.FINAL)
                        .addJavadoc(
                            "The number of `@SdkConstant` features defined in PackageManager."
                        )
                        .addJavadoc("@hide")
                        .initializer("\$L", featureCount)
                        .build()
                )
                .addField(buildFeatureCount(featureVarNames))
                .addMethod(buildFeatureIndexLookup(featureVarNames))
                .build()

        try {
@@ -104,7 +120,41 @@ class SystemFeaturesMetadataProcessor : AbstractProcessor() {
        return true
    }

    private fun buildFeatureCount(featureVarNames: Collection<String>): FieldSpec {
        return FieldSpec.builder(Int::class.java, "SDK_FEATURE_COUNT")
            .addModifiers(Modifier.PUBLIC, Modifier.STATIC, Modifier.FINAL)
            .addJavadoc(
                "# of {@link android.annotation.SdkConstant}` features defined in PackageManager."
            )
            .addJavadoc("\n\n@hide")
            .initializer("\$L", featureVarNames.size)
            .build()
    }

    private fun buildFeatureIndexLookup(featureVarNames: Collection<String>): MethodSpec {
        val methodBuilder =
            MethodSpec.methodBuilder("maybeGetSdkFeatureIndex")
                .addModifiers(Modifier.PUBLIC, Modifier.STATIC)
                .addJavadoc("@return an index in [0, SDK_FEATURE_COUNT) for features defined ")
                .addJavadoc("in PackageManager, else -1.")
                .addJavadoc("\n\n@hide")
                .returns(Int::class.java)
                .addParameter(String::class.java, "featureName")
        methodBuilder.beginControlFlow("switch (featureName)")
        featureVarNames.forEachIndexed { index, featureVarName ->
            methodBuilder
                .addCode("case \$T.\$N: ", PACKAGEMANAGER_CLASS, featureVarName)
                .addStatement("return \$L", index)
        }
        methodBuilder
            .addCode("default: ")
            .addStatement("return -1")
            .endControlFlow()
        return methodBuilder.build()
    }

    companion object {
        private val SDK_CONSTANT_ANNOTATION_NAME = SdkConstant::class.qualifiedName
        private val PACKAGEMANAGER_CLASS = ClassName.get("android.content.pm", "PackageManager")
    }
}
+19 −0
Original line number Diff line number Diff line
@@ -16,10 +16,16 @@

package com.android.systemfeatures;

import static com.android.internal.pm.SystemFeaturesMetadata.maybeGetSdkFeatureIndex;

import static com.google.common.truth.Truth.assertThat;

import android.content.pm.PackageManager;

import com.android.internal.pm.SystemFeaturesMetadata;

import com.google.common.collect.Range;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
@@ -33,4 +39,17 @@ public class SystemFeaturesMetadataProcessorTest {
        // It defines 5 annotated features, and any/all other constants should be ignored.
        assertThat(SystemFeaturesMetadata.SDK_FEATURE_COUNT).isEqualTo(5);
    }

    @Test
    public void testSdkFeatureIndex() {
        // Only SDK-defined features return valid indices.
        final Range validIndexRange = Range.closedOpen(0, SystemFeaturesMetadata.SDK_FEATURE_COUNT);
        assertThat(maybeGetSdkFeatureIndex(PackageManager.FEATURE_PC)).isIn(validIndexRange);
        assertThat(maybeGetSdkFeatureIndex(PackageManager.FEATURE_VULKAN)).isIn(validIndexRange);
        assertThat(maybeGetSdkFeatureIndex(PackageManager.FEATURE_NOT_ANNOTATED)).isEqualTo(-1);
        assertThat(maybeGetSdkFeatureIndex(PackageManager.NOT_FEATURE)).isEqualTo(-1);
        assertThat(maybeGetSdkFeatureIndex("foo")).isEqualTo(-1);
        assertThat(maybeGetSdkFeatureIndex("0")).isEqualTo(-1);
        assertThat(maybeGetSdkFeatureIndex("")).isEqualTo(-1);
    }
}