Initial sdkparcelables
Adds a tool that can convert an SDK stubs jar into a framework.aidl file by parsing the jar with ASM to find classes that implement android.os.Parcelable directly or indirectly. Bug: 70046217 Test: java -cp out/host/linux-x86/framework/sdk_parcelables_test.jar org.junit.runner.JUnitCore com.android.sdk_parcelables.ParcelableDetectorTest Change-Id: Idc804896b8860352633a85168748af1b08777205
This commit is contained in:
22
tools/sdkparcelables/Android.bp
Normal file
22
tools/sdkparcelables/Android.bp
Normal file
@@ -0,0 +1,22 @@
|
||||
java_binary_host {
|
||||
name: "sdkparcelables",
|
||||
manifest: "manifest.txt",
|
||||
srcs: [
|
||||
"src/**/*.kt",
|
||||
],
|
||||
static_libs: [
|
||||
"asm-6.0",
|
||||
],
|
||||
}
|
||||
|
||||
java_library_host {
|
||||
name: "sdkparcelables_test",
|
||||
manifest: "manifest.txt",
|
||||
srcs: [
|
||||
"tests/**/*.kt",
|
||||
],
|
||||
static_libs: [
|
||||
"sdkparcelables",
|
||||
"junit",
|
||||
],
|
||||
}
|
||||
1
tools/sdkparcelables/manifest.txt
Normal file
1
tools/sdkparcelables/manifest.txt
Normal file
@@ -0,0 +1 @@
|
||||
Main-class: com.android.sdkparcelables.MainKt
|
||||
@@ -0,0 +1,28 @@
|
||||
package com.android.sdkparcelables
|
||||
|
||||
import org.objectweb.asm.ClassVisitor
|
||||
import java.util.*
|
||||
|
||||
data class Ancestors(val superName: String?, val interfaces: List<String>?)
|
||||
|
||||
/** A class that implements an ASM ClassVisitor that collects super class and
|
||||
* implemented interfaces for each class that it visits.
|
||||
*/
|
||||
class AncestorCollector(api: Int, dest: ClassVisitor?) : ClassVisitor(api, dest) {
|
||||
private val _ancestors = LinkedHashMap<String, Ancestors>()
|
||||
|
||||
val ancestors: Map<String, Ancestors>
|
||||
get() = _ancestors
|
||||
|
||||
override fun visit(version: Int, access: Int, name: String?, signature: String?,
|
||||
superName: String?, interfaces: Array<out String>?) {
|
||||
name!!
|
||||
|
||||
val old = _ancestors.put(name, Ancestors(superName, interfaces?.toList()))
|
||||
if (old != null) {
|
||||
throw RuntimeException("class $name already found")
|
||||
}
|
||||
|
||||
super.visit(version, access, name, signature, superName, interfaces)
|
||||
}
|
||||
}
|
||||
56
tools/sdkparcelables/src/com/android/sdkparcelables/Main.kt
Normal file
56
tools/sdkparcelables/src/com/android/sdkparcelables/Main.kt
Normal file
@@ -0,0 +1,56 @@
|
||||
package com.android.sdkparcelables
|
||||
|
||||
import org.objectweb.asm.ClassReader
|
||||
import org.objectweb.asm.Opcodes
|
||||
import java.io.File
|
||||
import java.io.IOException
|
||||
import java.util.zip.ZipFile
|
||||
|
||||
fun main(args: Array<String>) {
|
||||
if (args.size != 2) {
|
||||
usage()
|
||||
}
|
||||
|
||||
val zipFileName = args[0]
|
||||
val aidlFileName = args[1]
|
||||
|
||||
val zipFile: ZipFile
|
||||
|
||||
try {
|
||||
zipFile = ZipFile(zipFileName)
|
||||
} catch (e: IOException) {
|
||||
System.err.println("error reading input jar: ${e.message}")
|
||||
kotlin.system.exitProcess(2)
|
||||
}
|
||||
|
||||
val ancestorCollector = AncestorCollector(Opcodes.ASM6, null)
|
||||
|
||||
for (entry in zipFile.entries()) {
|
||||
if (entry.name.endsWith(".class")) {
|
||||
val reader = ClassReader(zipFile.getInputStream(entry))
|
||||
reader.accept(ancestorCollector,
|
||||
ClassReader.SKIP_CODE or ClassReader.SKIP_DEBUG or ClassReader.SKIP_FRAMES)
|
||||
}
|
||||
}
|
||||
|
||||
val parcelables = ParcelableDetector.ancestorsToParcelables(ancestorCollector.ancestors)
|
||||
|
||||
try {
|
||||
val outFile = File(aidlFileName)
|
||||
val outWriter = outFile.bufferedWriter()
|
||||
for (parcelable in parcelables) {
|
||||
outWriter.write("parcelable ")
|
||||
outWriter.write(parcelable.replace('/', '.').replace('$', '.'))
|
||||
outWriter.write(";\n")
|
||||
}
|
||||
outWriter.flush()
|
||||
} catch (e: IOException) {
|
||||
System.err.println("error writing output aidl: ${e.message}")
|
||||
kotlin.system.exitProcess(2)
|
||||
}
|
||||
}
|
||||
|
||||
fun usage() {
|
||||
System.err.println("Usage: <input jar> <output aidl>")
|
||||
kotlin.system.exitProcess(1)
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
package com.android.sdkparcelables
|
||||
|
||||
/** A class that uses an ancestor map to find all classes that
|
||||
* implement android.os.Parcelable, including indirectly through
|
||||
* super classes or super interfaces.
|
||||
*/
|
||||
class ParcelableDetector {
|
||||
companion object {
|
||||
fun ancestorsToParcelables(ancestors: Map<String, Ancestors>): List<String> {
|
||||
val impl = Impl(ancestors)
|
||||
impl.build()
|
||||
return impl.parcelables
|
||||
}
|
||||
}
|
||||
|
||||
private class Impl(val ancestors: Map<String, Ancestors>) {
|
||||
val isParcelableCache = HashMap<String, Boolean>()
|
||||
val parcelables = ArrayList<String>()
|
||||
|
||||
fun build() {
|
||||
val classList = ancestors.keys
|
||||
classList.filterTo(parcelables, this::isParcelable)
|
||||
parcelables.sort()
|
||||
}
|
||||
|
||||
private fun isParcelable(c: String?): Boolean {
|
||||
if (c == null) {
|
||||
return false
|
||||
}
|
||||
|
||||
if (c == "android/os/Parcelable") {
|
||||
return true
|
||||
}
|
||||
|
||||
val old = isParcelableCache[c]
|
||||
if (old != null) {
|
||||
return old
|
||||
}
|
||||
|
||||
val cAncestors = ancestors[c] ?:
|
||||
throw RuntimeException("class $c missing ancestor information")
|
||||
|
||||
val seq = (cAncestors.interfaces?.asSequence() ?: emptySequence()) +
|
||||
cAncestors.superName
|
||||
|
||||
val ancestorIsParcelable = seq.any(this::isParcelable)
|
||||
|
||||
isParcelableCache[c] = ancestorIsParcelable
|
||||
return ancestorIsParcelable
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
package com.android.sdkparcelables
|
||||
|
||||
import junit.framework.TestCase.assertEquals
|
||||
import org.junit.Test
|
||||
|
||||
class ParcelableDetectorTest {
|
||||
@Test
|
||||
fun `detect implements`() {
|
||||
val ancestorMap = mapOf(
|
||||
testAncestors("android/test/Parcelable",null, "android/os/Parcelable"),
|
||||
testAncestors("android/os/Parcelable", null))
|
||||
|
||||
val parcelables = ParcelableDetector.ancestorsToParcelables(ancestorMap)
|
||||
|
||||
assertEquals(parcelables, listOf("android/os/Parcelable", "android/test/Parcelable"))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `detect implements in reverse order`() {
|
||||
val ancestorMap = mapOf(
|
||||
testAncestors("android/os/Parcelable", null),
|
||||
testAncestors("android/test/Parcelable",null, "android/os/Parcelable"))
|
||||
|
||||
val parcelables = ParcelableDetector.ancestorsToParcelables(ancestorMap)
|
||||
|
||||
assertEquals(parcelables, listOf("android/os/Parcelable", "android/test/Parcelable"))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `detect super implements`() {
|
||||
val ancestorMap = mapOf(
|
||||
testAncestors("android/test/SuperParcelable",null, "android/os/Parcelable"),
|
||||
testAncestors("android/test/Parcelable","android/test/SuperParcelable"),
|
||||
testAncestors("android/os/Parcelable", null))
|
||||
|
||||
val parcelables = ParcelableDetector.ancestorsToParcelables(ancestorMap)
|
||||
|
||||
assertEquals(parcelables, listOf("android/os/Parcelable", "android/test/Parcelable", "android/test/SuperParcelable"))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `detect super interface`() {
|
||||
val ancestorMap = mapOf(
|
||||
testAncestors("android/test/IParcelable",null, "android/os/Parcelable"),
|
||||
testAncestors("android/test/Parcelable",null, "android/test/IParcelable"),
|
||||
testAncestors("android/os/Parcelable", null))
|
||||
|
||||
val parcelables = ParcelableDetector.ancestorsToParcelables(ancestorMap)
|
||||
|
||||
assertEquals(parcelables, listOf("android/os/Parcelable", "android/test/IParcelable", "android/test/Parcelable"))
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private fun testAncestors(name: String, superName: String?, vararg interfaces: String): Pair<String, Ancestors> {
|
||||
return Pair(name, Ancestors(superName, interfaces.toList()))
|
||||
}
|
||||
Reference in New Issue
Block a user