diff --git a/Android.mk b/Android.mk index a19f2d90d9bbe..0e363d2491c8b 100644 --- a/Android.mk +++ b/Android.mk @@ -252,12 +252,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 # ============================================================ @@ -554,8 +568,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)) @@ -591,8 +603,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)) @@ -629,8 +639,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)) @@ -660,9 +668,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 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())) +}