Move package parsing to services.jar
This facilitates exposing the Parsed_ classes as
@SystemApi(client = SYSTEM_SERVER) while keeping everything inside
services.jar rather than having it split across both jars.
This reverts getPackageArchiveInfo to use legacy PackageParser, since
framework.jar can no longer access the moved classes.
Bug: 214038417
Test: presubmit, no logic changes
Change-Id: I152d70fb4f643d32efb012cfb20b0fbc5f88f2d8
diff --git a/apct-tests/perftests/packagemanager/Android.bp b/apct-tests/perftests/packagemanager/Android.bp
index fc70219..81cec91 100644
--- a/apct-tests/perftests/packagemanager/Android.bp
+++ b/apct-tests/perftests/packagemanager/Android.bp
@@ -10,7 +10,10 @@
android_test {
name: "PackageManagerPerfTests",
- srcs: ["src/**/*.java"],
+ srcs: [
+ "src/**/*.java",
+ "src/**/*.kt",
+ ],
static_libs: [
"platform-compat-test-rules",
@@ -21,6 +24,7 @@
"apct-perftests-utils",
"collector-device-lib-platform",
"cts-install-lib-java",
+ "services.core",
],
libs: ["android.test.base"],
diff --git a/apct-tests/perftests/packagemanager/src/android/os/PackageParsingPerfTest.kt b/apct-tests/perftests/packagemanager/src/android/os/PackageParsingPerfTest.kt
new file mode 100644
index 0000000..e873514
--- /dev/null
+++ b/apct-tests/perftests/packagemanager/src/android/os/PackageParsingPerfTest.kt
@@ -0,0 +1,282 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.os
+
+import android.content.pm.PackageParser
+import android.content.pm.PackageParserCacheHelper.ReadHelper
+import android.content.pm.PackageParserCacheHelper.WriteHelper
+import android.content.pm.parsing.result.ParseInput
+import android.content.pm.parsing.result.ParseTypeImpl
+import android.content.res.TypedArray
+import android.perftests.utils.BenchmarkState
+import android.perftests.utils.PerfStatusReporter
+import androidx.test.filters.LargeTest
+import com.android.internal.util.ConcurrentUtils
+import com.android.server.pm.pkg.parsing.ParsingPackageImpl
+import com.android.server.pm.pkg.parsing.ParsingPackageUtils
+import libcore.io.IoUtils
+import org.junit.Rule
+import org.junit.Test
+import org.junit.rules.TemporaryFolder
+import org.junit.runner.RunWith
+import org.junit.runners.Parameterized
+import java.io.File
+import java.io.FileOutputStream
+import java.util.concurrent.ArrayBlockingQueue
+import java.util.concurrent.TimeUnit
+
+@LargeTest
+@RunWith(Parameterized::class)
+public class PackageParsingPerfTest {
+
+ companion object {
+ private const val PARALLEL_QUEUE_CAPACITY = 10
+ private const val PARALLEL_MAX_THREADS = 4
+
+ private const val QUEUE_POLL_TIMEOUT_SECONDS = 5L
+
+ // TODO: Replace this with core version of SYSTEM_PARTITIONS
+ val FOLDERS_TO_TEST = listOf(
+ Environment.getRootDirectory(),
+ Environment.getVendorDirectory(),
+ Environment.getOdmDirectory(),
+ Environment.getOemDirectory(),
+ Environment.getOemDirectory(),
+ Environment.getSystemExtDirectory()
+ )
+
+ @JvmStatic
+ @Parameterized.Parameters(name = "{0}")
+ fun parameters(): Array<Params> {
+ val apks = FOLDERS_TO_TEST
+ .filter(File::exists)
+ .map(File::walkTopDown)
+ .flatMap(Sequence<File>::asIterable)
+ .filter { it.name.endsWith(".apk") }
+
+ return arrayOf(
+ Params(1, apks) { ParallelParser1(it?.let(::PackageCacher1)) },
+ Params(2, apks) { ParallelParser2(it?.let(::PackageCacher2)) }
+ )
+ }
+
+ data class Params(
+ val version: Int,
+ val apks: List<File>,
+ val cacheDirToParser: (File?) -> ParallelParser<*>
+ ) {
+ // For test name formatting
+ override fun toString() = "v$version"
+ }
+ }
+
+ @get:Rule
+ var perfStatusReporter = PerfStatusReporter()
+
+ @get:Rule
+ var testFolder = TemporaryFolder()
+
+ @Parameterized.Parameter(0)
+ lateinit var params: Params
+
+ private val state: BenchmarkState get() = perfStatusReporter.benchmarkState
+ private val apks: List<File> get() = params.apks
+
+ private fun safeParse(parser: ParallelParser<*>, file: File) {
+ try {
+ parser.parse(file)
+ } catch (e: Exception) {
+ // ignore
+ }
+ }
+
+ @Test
+ fun sequentialNoCache() {
+ params.cacheDirToParser(null).use { parser ->
+ while (state.keepRunning()) {
+ apks.forEach {
+ safeParse(parser, it)
+ }
+ }
+ }
+ }
+
+ @Test
+ fun sequentialCached() {
+ params.cacheDirToParser(testFolder.newFolder()).use { parser ->
+ // Fill the cache
+ apks.forEach { safeParse(parser, it) }
+
+ while (state.keepRunning()) {
+ apks.forEach { safeParse(parser, it) }
+ }
+ }
+ }
+
+ @Test
+ fun parallelNoCache() {
+ params.cacheDirToParser(null).use { parser ->
+ while (state.keepRunning()) {
+ apks.forEach { parser.submit(it) }
+ repeat(apks.size) { parser.take() }
+ }
+ }
+ }
+
+ @Test
+ fun parallelCached() {
+ params.cacheDirToParser(testFolder.newFolder()).use { parser ->
+ // Fill the cache
+ apks.forEach { safeParse(parser, it) }
+
+ while (state.keepRunning()) {
+ apks.forEach { parser.submit(it) }
+ repeat(apks.size) { parser.take() }
+ }
+ }
+ }
+
+ abstract class ParallelParser<PackageType : Parcelable>(
+ private val cacher: PackageCacher<PackageType>? = null
+ ) : AutoCloseable {
+ private val queue = ArrayBlockingQueue<Any>(PARALLEL_QUEUE_CAPACITY)
+ private val service = ConcurrentUtils.newFixedThreadPool(
+ PARALLEL_MAX_THREADS, "package-parsing-test",
+ Process.THREAD_PRIORITY_FOREGROUND)
+
+ fun submit(file: File) {
+ service.submit {
+ try {
+ queue.put(parse(file))
+ } catch (e: Exception) {
+ queue.put(e)
+ }
+ }
+ }
+
+ fun take() = queue.poll(QUEUE_POLL_TIMEOUT_SECONDS, TimeUnit.SECONDS)
+
+ override fun close() {
+ service.shutdownNow()
+ }
+
+ fun parse(file: File) = cacher?.getCachedResult(file)
+ ?: parseImpl(file).also { cacher?.cacheResult(file, it) }
+
+ protected abstract fun parseImpl(file: File): PackageType
+ }
+
+ class ParallelParser1(private val cacher: PackageCacher1? = null)
+ : ParallelParser<PackageParser.Package>(cacher) {
+ val parser = PackageParser().apply {
+ setCallback { true }
+ }
+
+ override fun parseImpl(file: File) = parser.parsePackage(file, 0, cacher != null)
+ }
+
+ class ParallelParser2(cacher: PackageCacher2? = null)
+ : ParallelParser<ParsingPackageImpl>(cacher) {
+ val input = ThreadLocal.withInitial {
+ // For testing, just disable enforcement to avoid hooking up to compat framework
+ ParseTypeImpl(ParseInput.Callback { _, _, _ -> false })
+ }
+ val parser = ParsingPackageUtils(false,
+ null,
+ null,
+ emptyList(),
+ object :
+ ParsingPackageUtils.Callback {
+ override fun hasFeature(feature: String) = true
+
+ override fun startParsingPackage(
+ packageName: String,
+ baseApkPath: String,
+ path: String,
+ manifestArray: TypedArray,
+ isCoreApp: Boolean
+ ) = ParsingPackageImpl(
+ packageName,
+ baseApkPath,
+ path,
+ manifestArray
+ )
+ })
+
+ override fun parseImpl(file: File) =
+ parser.parsePackage(input.get()!!.reset(), file, 0).result
+ as ParsingPackageImpl
+ }
+
+ abstract class PackageCacher<PackageType : Parcelable>(private val cacheDir: File) {
+
+ fun getCachedResult(file: File): PackageType? {
+ val cacheFile = File(cacheDir, file.name)
+ if (!cacheFile.exists()) {
+ return null
+ }
+
+ val bytes = IoUtils.readFileAsByteArray(cacheFile.absolutePath)
+ val parcel = Parcel.obtain().apply {
+ unmarshall(bytes, 0, bytes.size)
+ setDataPosition(0)
+ }
+ ReadHelper(parcel).apply { startAndInstall() }
+ return fromParcel(parcel).also {
+ parcel.recycle()
+ }
+ }
+
+ fun cacheResult(file: File, parsed: Parcelable) {
+ val cacheFile = File(cacheDir, file.name)
+ if (cacheFile.exists()) {
+ if (!cacheFile.delete()) {
+ throw IllegalStateException("Unable to delete cache file: $cacheFile")
+ }
+ }
+ val cacheEntry = toCacheEntry(parsed)
+ return FileOutputStream(cacheFile).use { fos -> fos.write(cacheEntry) }
+ }
+
+ private fun toCacheEntry(pkg: Parcelable): ByteArray {
+ val parcel = Parcel.obtain()
+ val helper = WriteHelper(parcel)
+ pkg.writeToParcel(parcel, 0 /* flags */)
+ helper.finishAndUninstall()
+ return parcel.marshall().also {
+ parcel.recycle()
+ }
+ }
+
+ protected abstract fun fromParcel(parcel: Parcel): PackageType
+ }
+
+ /**
+ * Re-implementation of v1's cache, since that's gone in R+.
+ */
+ class PackageCacher1(cacheDir: File) : PackageCacher<PackageParser.Package>(cacheDir) {
+ override fun fromParcel(parcel: Parcel) = PackageParser.Package(parcel)
+ }
+
+ /**
+ * Re-implementation of the server side PackageCacher, as it's inaccessible here.
+ */
+ class PackageCacher2(cacheDir: File) : PackageCacher<ParsingPackageImpl>(cacheDir) {
+ override fun fromParcel(parcel: Parcel) =
+ ParsingPackageImpl(parcel)
+ }
+}