From 149008d892ce76e83f6d95db09cdbb787293337b Mon Sep 17 00:00:00 2001 From: Colin Cross Date: Fri, 15 Dec 2017 11:07:53 -0800 Subject: [PATCH 1/2] 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 --- tools/sdkparcelables/Android.bp | 22 +++++++ tools/sdkparcelables/manifest.txt | 1 + .../sdkparcelables/AncestorCollector.kt | 28 +++++++++ .../src/com/android/sdkparcelables/Main.kt | 56 ++++++++++++++++++ .../sdkparcelables/ParcelableDetector.kt | 52 +++++++++++++++++ .../sdkparcelables/ParcelableDetectorTest.kt | 57 +++++++++++++++++++ 6 files changed, 216 insertions(+) create mode 100644 tools/sdkparcelables/Android.bp create mode 100644 tools/sdkparcelables/manifest.txt create mode 100644 tools/sdkparcelables/src/com/android/sdkparcelables/AncestorCollector.kt create mode 100644 tools/sdkparcelables/src/com/android/sdkparcelables/Main.kt create mode 100644 tools/sdkparcelables/src/com/android/sdkparcelables/ParcelableDetector.kt create mode 100644 tools/sdkparcelables/tests/com/android/sdkparcelables/ParcelableDetectorTest.kt diff --git a/tools/sdkparcelables/Android.bp b/tools/sdkparcelables/Android.bp new file mode 100644 index 0000000000000..00fb8aa20b18c --- /dev/null +++ b/tools/sdkparcelables/Android.bp @@ -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", + ], +} diff --git a/tools/sdkparcelables/manifest.txt b/tools/sdkparcelables/manifest.txt new file mode 100644 index 0000000000000..cd5420ce0bf4a --- /dev/null +++ b/tools/sdkparcelables/manifest.txt @@ -0,0 +1 @@ +Main-class: com.android.sdkparcelables.MainKt diff --git a/tools/sdkparcelables/src/com/android/sdkparcelables/AncestorCollector.kt b/tools/sdkparcelables/src/com/android/sdkparcelables/AncestorCollector.kt new file mode 100644 index 0000000000000..f278aec8eb6ff --- /dev/null +++ b/tools/sdkparcelables/src/com/android/sdkparcelables/AncestorCollector.kt @@ -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?) + +/** 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() + + val ancestors: Map + get() = _ancestors + + override fun visit(version: Int, access: Int, name: String?, signature: String?, + superName: String?, interfaces: Array?) { + 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 diff --git a/tools/sdkparcelables/src/com/android/sdkparcelables/Main.kt b/tools/sdkparcelables/src/com/android/sdkparcelables/Main.kt new file mode 100644 index 0000000000000..3e9d92cd978f7 --- /dev/null +++ b/tools/sdkparcelables/src/com/android/sdkparcelables/Main.kt @@ -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) { + 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: ") + kotlin.system.exitProcess(1) +} \ No newline at end of file diff --git a/tools/sdkparcelables/src/com/android/sdkparcelables/ParcelableDetector.kt b/tools/sdkparcelables/src/com/android/sdkparcelables/ParcelableDetector.kt new file mode 100644 index 0000000000000..620f798daf488 --- /dev/null +++ b/tools/sdkparcelables/src/com/android/sdkparcelables/ParcelableDetector.kt @@ -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): List { + val impl = Impl(ancestors) + impl.build() + return impl.parcelables + } + } + + private class Impl(val ancestors: Map) { + val isParcelableCache = HashMap() + val parcelables = ArrayList() + + 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 + } + } +} diff --git a/tools/sdkparcelables/tests/com/android/sdkparcelables/ParcelableDetectorTest.kt b/tools/sdkparcelables/tests/com/android/sdkparcelables/ParcelableDetectorTest.kt new file mode 100644 index 0000000000000..edfc8259a7389 --- /dev/null +++ b/tools/sdkparcelables/tests/com/android/sdkparcelables/ParcelableDetectorTest.kt @@ -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 { + return Pair(name, Ancestors(superName, interfaces.toList())) +} From 77ebd08d17f75912de984eae88fadba357d41f7f Mon Sep 17 00:00:00 2001 From: Colin Cross Date: Fri, 15 Dec 2017 17:30:33 -0800 Subject: [PATCH 2/2] Use sdkparcelables to generate framework.aidl Use sdkparcelables to extract public parcelables from the public, test, and system stubs jars, and combine them into framework.aidl. Bug: 70046217 Test: m checkbuild Change-Id: I9de0ffe9dffbd80c8cb785b0624959c52c3200a1 --- Android.mk | 33 +++++++++++++++++++-------------- 1 file changed, 19 insertions(+), 14 deletions(-) diff --git a/Android.mk b/Android.mk index 83cdd739ab6b1..5438bfab2d5e2 100644 --- a/Android.mk +++ b/Android.mk @@ -244,12 +244,26 @@ aidl_files := \ system/netd/server/binder/android/net/UidRange.aidl \ frameworks/base/telephony/java/android/telephony/PcoData.aidl \ +aidl_parcelables := +define stubs-to-aidl-parcelables + gen := $(TARGET_OUT_COMMON_INTERMEDIATES)/$1.aidl + aidl_parcelables += $$(gen) + $$(gen): $(call java-lib-header-files,$1) | $(HOST_OUT_EXECUTABLES)/sdkparcelables + @echo Extract SDK parcelables: $$@ + rm -f $$@ + $(HOST_OUT_EXECUTABLES)/sdkparcelables $$< $$@ +endef + +$(foreach stubs,android_stubs_current android_test_stubs_current android_system_stubs_current,\ + $(eval $(call stubs-to-aidl-parcelables,$(stubs)))) + gen := $(TARGET_OUT_COMMON_INTERMEDIATES)/framework.aidl -$(gen): PRIVATE_SRC_FILES := $(aidl_files) -ALL_SDK_FILES += $(gen) -$(gen): $(aidl_files) | $(AIDL) - @echo Aidl Preprocess: $@ - $(hide) $(AIDL) --preprocess $@ $(PRIVATE_SRC_FILES) +.KATI_RESTAT: $(gen) +$(gen): $(aidl_parcelables) + @echo Combining SDK parcelables: $@ + rm -f $@.tmp + cat $^ | sort -u > $@.tmp + $(call commit-change-for-toc,$@) # the documentation # ============================================================ @@ -487,8 +501,6 @@ LOCAL_UNINSTALLABLE_MODULE := true include $(BUILD_DROIDDOC) -# $(gen), i.e. framework.aidl, is also needed while building against the current stub. -$(full_target): $(gen) $(INTERNAL_PLATFORM_API_FILE): $(full_target) $(call dist-for-goals,sdk,$(INTERNAL_PLATFORM_API_FILE)) @@ -524,8 +536,6 @@ LOCAL_UNINSTALLABLE_MODULE := true include $(BUILD_DROIDDOC) -# $(gen), i.e. framework.aidl, is also needed while building against the current stub. -$(full_target): $(gen) $(INTERNAL_PLATFORM_SYSTEM_API_FILE): $(full_target) $(call dist-for-goals,sdk,$(INTERNAL_PLATFORM_SYSTEM_API_FILE)) @@ -562,8 +572,6 @@ LOCAL_UNINSTALLABLE_MODULE := true include $(BUILD_DROIDDOC) -# $(gen), i.e. framework.aidl, is also needed while building against the current stub. -$(full_target): $(gen) $(INTERNAL_PLATFORM_TEST_API_FILE): $(full_target) $(call dist-for-goals,sdk,$(INTERNAL_PLATFORM_TEST_API_FILE)) @@ -593,9 +601,6 @@ LOCAL_UNINSTALLABLE_MODULE := true include $(BUILD_DROIDDOC) -# $(gen), i.e. framework.aidl, is also needed while building against the current stub. -$(full_target): $(gen) - # Run this for checkbuild checkbuild: doc-comment-check-docs # Check comment when you are updating the API