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)
+    }
+}