Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 149008d8 authored by Colin Cross's avatar Colin Cross
Browse files

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
parent 4e54d609
Loading
Loading
Loading
Loading
+22 −0
Original line number Diff line number Diff line
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 −0
Original line number Diff line number Diff line
Main-class: com.android.sdkparcelables.MainKt
+28 −0
Original line number Diff line number Diff line
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)
    }
}
 No newline at end of file
+56 −0
Original line number Diff line number Diff line
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)
}
 No newline at end of file
+52 −0
Original line number Diff line number Diff line
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
        }
    }
}
Loading