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