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