1 /* <lambda>null2 * Copyright (C) 2024 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.systemfeatures 18 19 import android.annotation.SdkConstant 20 import com.squareup.javapoet.ClassName 21 import com.squareup.javapoet.FieldSpec 22 import com.squareup.javapoet.JavaFile 23 import com.squareup.javapoet.MethodSpec 24 import com.squareup.javapoet.TypeSpec 25 import java.io.IOException 26 import javax.annotation.processing.AbstractProcessor 27 import javax.annotation.processing.ProcessingEnvironment 28 import javax.annotation.processing.RoundEnvironment 29 import javax.lang.model.SourceVersion 30 import javax.lang.model.element.Modifier 31 import javax.lang.model.element.TypeElement 32 import javax.lang.model.element.VariableElement 33 import javax.tools.Diagnostic 34 35 /* 36 * Simple Java code generator for computing metadata for system features. 37 * 38 * <p>The output is a single class file, `com.android.internal.pm.SystemFeaturesMetadata`, with 39 * properties computed from feature constant definitions in the PackageManager class. This 40 * class is only produced if the processed environment includes PackageManager; all other 41 * invocations are ignored. The generated API is as follows: 42 * 43 * <pre> 44 * package android.content.pm; 45 * public final class SystemFeaturesMetadata { 46 * public static final int SDK_FEATURE_COUNT; 47 * // @return [0, SDK_FEATURE_COUNT) if an SDK-defined system feature, -1 otherwise. 48 * public static int maybeGetSdkFeatureIndex(String featureName); 49 * } 50 * </pre> 51 */ 52 class SystemFeaturesMetadataProcessor : AbstractProcessor() { 53 54 private lateinit var packageManagerType: TypeElement 55 56 override fun getSupportedSourceVersion(): SourceVersion = SourceVersion.latestSupported() 57 58 override fun getSupportedAnnotationTypes() = setOf(SDK_CONSTANT_ANNOTATION_NAME) 59 60 override fun init(processingEnv: ProcessingEnvironment) { 61 super.init(processingEnv) 62 packageManagerType = 63 processingEnv.elementUtils.getTypeElement("android.content.pm.PackageManager")!! 64 } 65 66 override fun process(annotations: Set<TypeElement>, roundEnv: RoundEnvironment): Boolean { 67 if (roundEnv.processingOver()) { 68 return false 69 } 70 71 // Collect all FEATURE-annotated fields from PackageManager, and 72 // 1) Use the field values to de-duplicate, as there can be multiple FEATURE_* fields that 73 // map to the same feature string name value. 74 // 2) Ensure they're sorted to ensure consistency and determinism between builds. 75 val featureVarNames = 76 roundEnv 77 .getElementsAnnotatedWith(SdkConstant::class.java) 78 .asSequence() 79 .filter { 80 it.enclosingElement == packageManagerType && 81 it.getAnnotation(SdkConstant::class.java).value == 82 SdkConstant.SdkConstantType.FEATURE 83 } 84 .mapNotNull { element -> 85 (element as? VariableElement)?.let { varElement -> 86 varElement.getConstantValue()?.toString() to 87 varElement.simpleName.toString() 88 } 89 } 90 .toMap() 91 .values 92 .sorted() 93 .toList() 94 95 if (featureVarNames.isEmpty()) { 96 // This is fine, and happens for any environment that doesn't include PackageManager. 97 return false 98 } 99 100 val systemFeatureMetadata = 101 TypeSpec.classBuilder("SystemFeaturesMetadata") 102 .addModifiers(Modifier.PUBLIC, Modifier.FINAL) 103 .addJavadoc("@hide") 104 .addField(buildFeatureCount(featureVarNames)) 105 .addMethod(buildFeatureIndexLookup(featureVarNames)) 106 .build() 107 108 try { 109 JavaFile.builder("com.android.internal.pm", systemFeatureMetadata) 110 .skipJavaLangImports(true) 111 .build() 112 .writeTo(processingEnv.filer) 113 } catch (e: IOException) { 114 processingEnv.messager.printMessage( 115 Diagnostic.Kind.ERROR, 116 "Failed to write file: ${e.message}", 117 ) 118 } 119 120 return true 121 } 122 123 private fun buildFeatureCount(featureVarNames: Collection<String>): FieldSpec { 124 return FieldSpec.builder(Int::class.java, "SDK_FEATURE_COUNT") 125 .addModifiers(Modifier.PUBLIC, Modifier.STATIC, Modifier.FINAL) 126 .addJavadoc( 127 "# of {@link android.annotation.SdkConstant}` features defined in PackageManager." 128 ) 129 .addJavadoc("\n\n@hide") 130 .initializer("\$L", featureVarNames.size) 131 .build() 132 } 133 134 private fun buildFeatureIndexLookup(featureVarNames: Collection<String>): MethodSpec { 135 val methodBuilder = 136 MethodSpec.methodBuilder("maybeGetSdkFeatureIndex") 137 .addModifiers(Modifier.PUBLIC, Modifier.STATIC) 138 .addJavadoc("@return an index in [0, SDK_FEATURE_COUNT) for features defined ") 139 .addJavadoc("in PackageManager, else -1.") 140 .addJavadoc("\n\n@hide") 141 .returns(Int::class.java) 142 .addParameter(String::class.java, "featureName") 143 methodBuilder.beginControlFlow("switch (featureName)") 144 featureVarNames.forEachIndexed { index, featureVarName -> 145 methodBuilder 146 .addCode("case \$T.\$N: ", PACKAGEMANAGER_CLASS, featureVarName) 147 .addStatement("return \$L", index) 148 } 149 methodBuilder 150 .addCode("default: ") 151 .addStatement("return -1") 152 .endControlFlow() 153 return methodBuilder.build() 154 } 155 156 companion object { 157 private val SDK_CONSTANT_ANNOTATION_NAME = SdkConstant::class.qualifiedName 158 private val PACKAGEMANAGER_CLASS = ClassName.get("android.content.pm", "PackageManager") 159 } 160 } 161