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