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