Loading tools/systemfeatures/src/com/android/systemfeatures/SystemFeaturesMetadataProcessor.kt +73 −23 Original line number Diff line number Diff line Loading @@ -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 Loading @@ -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 /* Loading @@ -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() { Loading @@ -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 } Loading @@ -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 { Loading @@ -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") } } tools/systemfeatures/tests/src/SystemFeaturesMetadataProcessorTest.java +19 −0 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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); } } Loading
tools/systemfeatures/src/com/android/systemfeatures/SystemFeaturesMetadataProcessor.kt +73 −23 Original line number Diff line number Diff line Loading @@ -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 Loading @@ -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 /* Loading @@ -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() { Loading @@ -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 } Loading @@ -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 { Loading @@ -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") } }
tools/systemfeatures/tests/src/SystemFeaturesMetadataProcessorTest.java +19 −0 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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); } }