1 /*
2  * Copyright (C) 2018 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.tools.metalava
18 
19 import com.android.tools.metalava.model.ClassItem
20 import com.android.tools.metalava.model.Codebase
21 import com.android.tools.metalava.model.FieldItem
22 import java.io.BufferedWriter
23 import java.io.File
24 import java.io.FileWriter
25 import java.io.IOException
26 
27 // Ported from doclava1
28 
29 private const val ANDROID_VIEW_VIEW = "android.view.View"
30 private const val ANDROID_VIEW_VIEW_GROUP = "android.view.ViewGroup"
31 private const val ANDROID_VIEW_VIEW_GROUP_LAYOUT_PARAMS = "android.view.ViewGroup.LayoutParams"
32 private const val SDK_CONSTANT_ANNOTATION = "android.annotation.SdkConstant"
33 private const val SDK_CONSTANT_TYPE_ACTIVITY_ACTION =
34     "android.annotation.SdkConstant.SdkConstantType.ACTIVITY_INTENT_ACTION"
35 private const val SDK_CONSTANT_TYPE_BROADCAST_ACTION =
36     "android.annotation.SdkConstant.SdkConstantType.BROADCAST_INTENT_ACTION"
37 private const val SDK_CONSTANT_TYPE_SERVICE_ACTION =
38     "android.annotation.SdkConstant.SdkConstantType.SERVICE_ACTION"
39 private const val SDK_CONSTANT_TYPE_CATEGORY = "android.annotation.SdkConstant.SdkConstantType.INTENT_CATEGORY"
40 private const val SDK_CONSTANT_TYPE_FEATURE = "android.annotation.SdkConstant.SdkConstantType.FEATURE"
41 private const val SDK_WIDGET_ANNOTATION = "android.annotation.Widget"
42 private const val SDK_LAYOUT_ANNOTATION = "android.annotation.Layout"
43 
44 private const val TYPE_NONE = 0
45 private const val TYPE_WIDGET = 1
46 private const val TYPE_LAYOUT = 2
47 private const val TYPE_LAYOUT_PARAM = 3
48 
49 /**
50  * Writes various SDK metadata files packaged with the SDK, such as
51  * {@code platforms/android-27/data/features.txt}
52  */
53 class SdkFileWriter(val codebase: Codebase, private val outputDir: java.io.File) {
54     /**
55      * Collect the values used by the Dev tools and write them in files packaged with the SDK
56      */
generatenull57     fun generate() {
58         val activityActions = mutableListOf<String>()
59         val broadcastActions = mutableListOf<String>()
60         val serviceActions = mutableListOf<String>()
61         val categories = mutableListOf<String>()
62         val features = mutableListOf<String>()
63         val layouts = mutableListOf<ClassItem>()
64         val widgets = mutableListOf<ClassItem>()
65         val layoutParams = mutableListOf<ClassItem>()
66 
67         val classes = codebase.getPackages().allClasses()
68 
69         // The topmost LayoutParams class - android.view.ViewGroup.LayoutParams
70         var topLayoutParams: ClassItem? = null
71 
72         // Go through all the fields of all the classes, looking SDK stuff.
73         for (clazz in classes) {
74             // first check constant fields for the SdkConstant annotation.
75             val fields = clazz.fields()
76             for (field in fields) {
77                 val value = field.initialValue() ?: continue
78                 val annotations = field.modifiers.annotations()
79                 for (annotation in annotations) {
80                     if (SDK_CONSTANT_ANNOTATION == annotation.qualifiedName()) {
81                         val resolved =
82                             annotation.findAttribute(null)?.leafValues()?.firstOrNull()?.resolve() as? FieldItem
83                                 ?: continue
84                         val type = resolved.containingClass().qualifiedName() + "." + resolved.name()
85                         when {
86                             SDK_CONSTANT_TYPE_ACTIVITY_ACTION == type -> activityActions.add(value.toString())
87                             SDK_CONSTANT_TYPE_BROADCAST_ACTION == type -> broadcastActions.add(value.toString())
88                             SDK_CONSTANT_TYPE_SERVICE_ACTION == type -> serviceActions.add(value.toString())
89                             SDK_CONSTANT_TYPE_CATEGORY == type -> categories.add(value.toString())
90                             SDK_CONSTANT_TYPE_FEATURE == type -> features.add(value.toString())
91                         }
92                     }
93                 }
94             }
95 
96             // Now check the class for @Widget or if its in the android.widget package
97             // (unless the class is hidden or abstract, or non public)
98             if (!clazz.isHiddenOrRemoved() && clazz.isPublic && !clazz.modifiers.isAbstract()) {
99                 var annotated = false
100                 val annotations = clazz.modifiers.annotations()
101                 if (!annotations.isEmpty()) {
102                     for (annotation in annotations) {
103                         if (SDK_WIDGET_ANNOTATION == annotation.qualifiedName()) {
104                             widgets.add(clazz)
105                             annotated = true
106                             break
107                         } else if (SDK_LAYOUT_ANNOTATION == annotation.qualifiedName()) {
108                             layouts.add(clazz)
109                             annotated = true
110                             break
111                         }
112                     }
113                 }
114 
115                 if (!annotated) {
116                     if (topLayoutParams == null && ANDROID_VIEW_VIEW_GROUP_LAYOUT_PARAMS == clazz.qualifiedName()) {
117                         topLayoutParams = clazz
118                     }
119                     // let's check if this is inside android.widget or android.view
120                     if (isIncludedPackage(clazz)) {
121                         // now we check what this class inherits either from android.view.ViewGroup
122                         // or android.view.View, or android.view.ViewGroup.LayoutParams
123                         val type = checkInheritance(clazz)
124                         when (type) {
125                             TYPE_WIDGET -> widgets.add(clazz)
126                             TYPE_LAYOUT -> layouts.add(clazz)
127                             TYPE_LAYOUT_PARAM -> layoutParams.add(clazz)
128                         }
129                     }
130                 }
131             }
132         }
133 
134         // now write the files, whether or not the list are empty.
135         // the SDK built requires those files to be present.
136 
137         activityActions.sort()
138         writeValues("activity_actions.txt", activityActions)
139 
140         broadcastActions.sort()
141         writeValues("broadcast_actions.txt", broadcastActions)
142 
143         serviceActions.sort()
144         writeValues("service_actions.txt", serviceActions)
145 
146         categories.sort()
147         writeValues("categories.txt", categories)
148 
149         features.sort()
150         writeValues("features.txt", features)
151 
152         // Before writing the list of classes, we do some checks, to make sure the layout params
153         // are enclosed by a layout class (and not one that has been declared as a widget)
154         var i = 0
155         while (i < layoutParams.size) {
156             var clazz: ClassItem? = layoutParams[i]
157             val containingClass = clazz?.containingClass()
158             var remove = containingClass == null || layouts.indexOf(containingClass) == -1
159             // Also ensure that super classes of the layout params are in android.widget or android.view.
160             while (!remove && clazz != null) {
161                 clazz = clazz.superClass() ?: break
162                 if (clazz == topLayoutParams) {
163                     break
164                 }
165                 remove = !isIncludedPackage(clazz)
166             }
167             if (remove) {
168                 layoutParams.removeAt(i)
169             } else {
170                 i++
171             }
172         }
173 
174         writeClasses("widgets.txt", widgets, layouts, layoutParams)
175     }
176 
177     /**
178      * Check if the clazz is in package android.view or android.widget
179      */
isIncludedPackagenull180     private fun isIncludedPackage(clazz: ClassItem): Boolean {
181         val pkgName = clazz.containingPackage().qualifiedName()
182         return "android.widget" == pkgName || "android.view" == pkgName
183     }
184 
185     /**
186      * Writes a list of values into a text files.
187      *
188      * @param name the name of the file to write in the SDK directory
189      * @param values the list of values to write.
190      */
writeValuesnull191     private fun writeValues(name: String, values: List<String>) {
192         val pathname = File(outputDir, name)
193         var fw: FileWriter? = null
194         var bw: BufferedWriter? = null
195         try {
196             fw = FileWriter(pathname, false)
197             bw = BufferedWriter(fw)
198 
199             for (value in values) {
200                 bw.append(value).append('\n')
201             }
202         } catch (e: IOException) {
203             // pass for now
204         } finally {
205             try {
206                 bw?.close()
207             } catch (e: IOException) {
208                 // pass for now
209             }
210 
211             try {
212                 fw?.close()
213             } catch (e: IOException) {
214                 // pass for now
215             }
216         }
217     }
218 
219     /**
220      * Writes the widget/layout/layout param classes into a text files.
221      *
222      * @param name the name of the output file.
223      * @param widgets the list of widget classes to write.
224      * @param layouts the list of layout classes to write.
225      * @param layoutParams the list of layout param classes to write.
226      */
writeClassesnull227     private fun writeClasses(
228         name: String,
229         widgets: List<ClassItem>,
230         layouts: List<ClassItem>,
231         layoutParams: List<ClassItem>
232     ) {
233         var fw: FileWriter? = null
234         var bw: BufferedWriter? = null
235         try {
236             val pathname = File(outputDir, name)
237             fw = FileWriter(pathname, false)
238             bw = BufferedWriter(fw)
239 
240             // write the 3 types of classes.
241             for (clazz in widgets) {
242                 writeClass(bw, clazz, 'W')
243             }
244             for (clazz in layoutParams) {
245                 writeClass(bw, clazz, 'P')
246             }
247             for (clazz in layouts) {
248                 writeClass(bw, clazz, 'L')
249             }
250         } catch (ignore: IOException) {
251         } finally {
252             bw?.close()
253             fw?.close()
254         }
255     }
256 
257     /**
258      * Writes a class name and its super class names into a [BufferedWriter].
259      *
260      * @param writer the BufferedWriter to write into
261      * @param clazz the class to write
262      * @param prefix the prefix to put at the beginning of the line.
263      * @throws IOException
264      */
265     @Throws(IOException::class)
writeClassnull266     private fun writeClass(writer: BufferedWriter, clazz: ClassItem, prefix: Char) {
267         writer.append(prefix).append(clazz.qualifiedName())
268         var superClass: ClassItem? = clazz.superClass()
269         while (superClass != null) {
270             writer.append(' ').append(superClass.qualifiedName())
271             superClass = superClass.superClass()
272         }
273         writer.append('\n')
274     }
275 
276     /**
277      * Checks the inheritance of [ClassItem] objects. This method return
278      *
279      *  * [.TYPE_LAYOUT]: if the class extends `android.view.ViewGroup`
280      *  * [.TYPE_WIDGET]: if the class extends `android.view.View`
281      *  * [.TYPE_LAYOUT_PARAM]: if the class extends
282      * `android.view.ViewGroup$LayoutParams`
283      *  * [.TYPE_NONE]: in all other cases
284      *
285      * @param clazz the [ClassItem] to check.
286      */
checkInheritancenull287     private fun checkInheritance(clazz: ClassItem): Int {
288         return when {
289             ANDROID_VIEW_VIEW_GROUP == clazz.qualifiedName() -> TYPE_LAYOUT
290             ANDROID_VIEW_VIEW == clazz.qualifiedName() -> TYPE_WIDGET
291             ANDROID_VIEW_VIEW_GROUP_LAYOUT_PARAMS == clazz.qualifiedName() -> TYPE_LAYOUT_PARAM
292             else -> {
293                 val parent = clazz.superClass()
294                 if (parent != null) {
295                     checkInheritance(parent)
296                 } else TYPE_NONE
297             }
298         }
299     }
300 }