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