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 }