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