Move shared library info out of PackageManagerService (2/n)

- Adding tests for SharedLibrariesImpl
- Rename SharedLibraryHelper to SharedLibraryUtils

Bug: 200588896
Test: atest StaticSharedLibsHostTests
Test: atest SharedLibrariesImplTest
Change-Id: I7f03720f54ae14b68ba7531f4f407a99d361a680
diff --git a/services/core/java/com/android/server/pm/ComputerEngine.java b/services/core/java/com/android/server/pm/ComputerEngine.java
index 2f4f271..df83d11 100644
--- a/services/core/java/com/android/server/pm/ComputerEngine.java
+++ b/services/core/java/com/android/server/pm/ComputerEngine.java
@@ -4788,7 +4788,7 @@
     @Override
     public List<PackageStateInternal> findSharedNonSystemLibraries(
             @NonNull PackageStateInternal pkgSetting) {
-        List<SharedLibraryInfo> deps = SharedLibraryHelper.findSharedLibraries(pkgSetting);
+        List<SharedLibraryInfo> deps = SharedLibraryUtils.findSharedLibraries(pkgSetting);
         if (!deps.isEmpty()) {
             List<PackageStateInternal> retValue = new ArrayList<>();
             for (SharedLibraryInfo info : deps) {
diff --git a/services/core/java/com/android/server/pm/DexOptHelper.java b/services/core/java/com/android/server/pm/DexOptHelper.java
index eac38af..dcad3ec 100644
--- a/services/core/java/com/android/server/pm/DexOptHelper.java
+++ b/services/core/java/com/android/server/pm/DexOptHelper.java
@@ -379,7 +379,7 @@
         // at boot, or background job), the passed 'targetCompilerFilter' stays the same,
         // and the first package that uses the library will dexopt it. The
         // others will see that the compiled code for the library is up to date.
-        Collection<SharedLibraryInfo> deps = SharedLibraryHelper.findSharedLibraries(pkgSetting);
+        Collection<SharedLibraryInfo> deps = SharedLibraryUtils.findSharedLibraries(pkgSetting);
         final String[] instructionSets = getAppDexInstructionSets(
                 AndroidPackageUtils.getPrimaryCpuAbi(p, pkgSetting),
                 AndroidPackageUtils.getSecondaryCpuAbi(p, pkgSetting));
diff --git a/services/core/java/com/android/server/pm/ReconcilePackageUtils.java b/services/core/java/com/android/server/pm/ReconcilePackageUtils.java
index 5a25004..7d2aa53 100644
--- a/services/core/java/com/android/server/pm/ReconcilePackageUtils.java
+++ b/services/core/java/com/android/server/pm/ReconcilePackageUtils.java
@@ -67,11 +67,11 @@
 
             // in the first pass, we'll build up the set of incoming shared libraries
             final List<SharedLibraryInfo> allowedSharedLibInfos =
-                    SharedLibraryHelper.getAllowedSharedLibInfos(scanResult,
+                    SharedLibraryUtils.getAllowedSharedLibInfos(scanResult,
                             request.mSharedLibrarySource);
             if (allowedSharedLibInfos != null) {
                 for (SharedLibraryInfo info : allowedSharedLibInfos) {
-                    if (!SharedLibraryHelper.addSharedLibraryToPackageVersionMap(
+                    if (!SharedLibraryUtils.addSharedLibraryToPackageVersionMap(
                             incomingSharedLibraries, info)) {
                         throw new ReconcileFailure("Shared Library " + info.getName()
                                 + " is being installed twice in this set!");
@@ -264,7 +264,7 @@
             }
             try {
                 result.get(installPackageName).mCollectedSharedLibraryInfos =
-                        SharedLibraryHelper.collectSharedLibraryInfos(
+                        SharedLibraryUtils.collectSharedLibraryInfos(
                                 scanResult.mRequest.mParsedPackage,
                                 combinedPackages, request.mSharedLibrarySource,
                                 incomingSharedLibraries, injector.getCompatibility());
diff --git a/services/core/java/com/android/server/pm/SharedLibrariesImpl.java b/services/core/java/com/android/server/pm/SharedLibrariesImpl.java
index 0055f4e..2335cc1 100644
--- a/services/core/java/com/android/server/pm/SharedLibrariesImpl.java
+++ b/services/core/java/com/android/server/pm/SharedLibrariesImpl.java
@@ -494,7 +494,7 @@
             @NonNull Map<String, AndroidPackage> availablePackages)
             throws PackageManagerException {
         final ArrayList<SharedLibraryInfo> sharedLibraryInfos =
-                SharedLibraryHelper.collectSharedLibraryInfos(
+                SharedLibraryUtils.collectSharedLibraryInfos(
                         pkgSetting.getPkg(), availablePackages, mSharedLibraries,
                         null /* newLibraries */, mInjector.getCompatibility());
         executeSharedLibrariesUpdateLPw(pkg, pkgSetting, changingLib, changingLibSetting,
diff --git a/services/core/java/com/android/server/pm/SharedLibraryHelper.java b/services/core/java/com/android/server/pm/SharedLibraryUtils.java
similarity index 99%
rename from services/core/java/com/android/server/pm/SharedLibraryHelper.java
rename to services/core/java/com/android/server/pm/SharedLibraryUtils.java
index dd8fad0..5259c58 100644
--- a/services/core/java/com/android/server/pm/SharedLibraryHelper.java
+++ b/services/core/java/com/android/server/pm/SharedLibraryUtils.java
@@ -47,7 +47,7 @@
 import java.util.Map;
 import java.util.Set;
 
-final class SharedLibraryHelper {
+final class SharedLibraryUtils {
     private static final boolean DEBUG_SHARED_LIBRARIES = false;
 
     /**
diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/MockSystem.kt b/services/tests/mockingservicestests/src/com/android/server/pm/MockSystem.kt
index 44a8b30..04a6eee 100644
--- a/services/tests/mockingservicestests/src/com/android/server/pm/MockSystem.kt
+++ b/services/tests/mockingservicestests/src/com/android/server/pm/MockSystem.kt
@@ -74,6 +74,7 @@
 import com.android.server.testutils.nullable
 import com.android.server.testutils.whenever
 import com.android.server.utils.WatchedArrayMap
+import libcore.util.HexEncoding
 import org.junit.Assert
 import org.junit.rules.TestRule
 import org.junit.runner.Description
@@ -140,6 +141,7 @@
                 .mockStatic(EventLog::class.java)
                 .mockStatic(LocalServices::class.java)
                 .mockStatic(DeviceConfig::class.java)
+                .mockStatic(HexEncoding::class.java)
                 .apply(withSession)
         session = apply.startMocking()
         whenever(mocks.settings.insertPackageSettingLPw(
diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/SharedLibrariesImplTest.kt b/services/tests/mockingservicestests/src/com/android/server/pm/SharedLibrariesImplTest.kt
new file mode 100644
index 0000000..f305321
--- /dev/null
+++ b/services/tests/mockingservicestests/src/com/android/server/pm/SharedLibrariesImplTest.kt
@@ -0,0 +1,413 @@
+/*
+ * Copyright (C) 2021 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 com.android.server.pm
+
+import android.content.pm.PackageManager
+import android.content.pm.SharedLibraryInfo
+import android.content.pm.VersionedPackage
+import android.os.Build
+import android.os.storage.StorageManager
+import android.util.ArrayMap
+import android.util.PackageUtils
+import com.android.server.SystemConfig.SharedLibraryEntry
+import com.android.server.compat.PlatformCompat
+import com.android.server.extendedtestutils.wheneverStatic
+import com.android.server.pm.PackageManagerService.PLATFORM_PACKAGE_NAME
+import com.android.server.pm.parsing.pkg.AndroidPackage
+import com.android.server.pm.parsing.pkg.PackageImpl
+import com.android.server.pm.parsing.pkg.ParsedPackage
+import com.android.server.testutils.any
+import com.android.server.testutils.eq
+import com.android.server.testutils.nullable
+import com.android.server.testutils.spy
+import com.android.server.testutils.whenever
+import com.android.server.utils.WatchedLongSparseArray
+import com.google.common.truth.Truth.assertThat
+import libcore.util.HexEncoding
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+import org.mockito.Mock
+import org.mockito.Mockito.doAnswer
+import org.mockito.Mockito.verify
+import org.mockito.MockitoAnnotations
+import java.io.File
+import kotlin.test.assertFailsWith
+import kotlin.test.assertFalse
+import kotlin.test.assertTrue
+
+@RunWith(JUnit4::class)
+class SharedLibrariesImplTest {
+
+    companion object {
+        const val TEST_LIB_NAME = "test.lib"
+        const val TEST_LIB_PACKAGE_NAME = "com.android.lib.test"
+        const val BUILTIN_LIB_NAME = "builtin.lib"
+        const val STATIC_LIB_NAME = "static.lib"
+        const val STATIC_LIB_VERSION = 7L
+        const val STATIC_LIB_PACKAGE_NAME = "com.android.lib.static.provider"
+        const val DYNAMIC_LIB_NAME = "dynamic.lib"
+        const val DYNAMIC_LIB_PACKAGE_NAME = "com.android.lib.dynamic.provider"
+        const val CONSUMER_PACKAGE_NAME = "com.android.lib.consumer"
+        const val VERSION_UNDEFINED = SharedLibraryInfo.VERSION_UNDEFINED.toLong()
+    }
+
+    @Rule
+    @JvmField
+    val mRule = MockSystemRule()
+
+    private val mExistingPackages: ArrayMap<String, AndroidPackage> = ArrayMap()
+    private val mExistingSettings: MutableMap<String, PackageSetting> = mutableMapOf()
+
+    private lateinit var mSharedLibrariesImpl: SharedLibrariesImpl
+    private lateinit var mPms: PackageManagerService
+    private lateinit var mSettings: Settings
+
+    @Mock
+    private lateinit var mDeletePackageHelper: DeletePackageHelper
+    @Mock
+    private lateinit var mStorageManager: StorageManager
+    @Mock
+    private lateinit var mFile: File
+    @Mock
+    private lateinit var mPlatformCompat: PlatformCompat
+
+    @Before
+    fun setup() {
+        MockitoAnnotations.initMocks(this)
+        mRule.system().stageNominalSystemState()
+        addExistingPackages()
+
+        val testParams = PackageManagerServiceTestParams().apply {
+            packages = mExistingPackages
+        }
+        mPms = spy(PackageManagerService(mRule.mocks().injector, testParams))
+        mSettings = mRule.mocks().injector.settings
+        mSharedLibrariesImpl = SharedLibrariesImpl(mPms, mRule.mocks().injector)
+        mSharedLibrariesImpl.setDeletePackageHelper(mDeletePackageHelper)
+        addExistingSharedLibraries()
+
+        whenever(mSettings.getPackageLPr(any())) { mExistingSettings[arguments[0]] }
+        whenever(mRule.mocks().injector.getSystemService(StorageManager::class.java))
+            .thenReturn(mStorageManager)
+        whenever(mStorageManager.findPathForUuid(nullable())).thenReturn(mFile)
+        doAnswer { it.arguments[0] }.`when`(mPms).resolveInternalPackageNameLPr(any(), any())
+        whenever(mDeletePackageHelper.deletePackageX(any(), any(), any(), any(), any()))
+            .thenReturn(PackageManager.DELETE_SUCCEEDED)
+        whenever(mRule.mocks().injector.compatibility).thenReturn(mPlatformCompat)
+        wheneverStatic { HexEncoding.decode(STATIC_LIB_NAME, false) }
+                .thenReturn(PackageUtils.computeSha256DigestBytes(
+                        mExistingSettings[STATIC_LIB_PACKAGE_NAME]!!
+                                .pkg.signingDetails.signatures!![0].toByteArray()))
+    }
+
+    @Test
+    fun snapshot_shouldSealed() {
+        val builtinLibs = mSharedLibrariesImpl.snapshot().all[BUILTIN_LIB_NAME]
+        assertThat(builtinLibs).isNotNull()
+
+        assertFailsWith(IllegalStateException::class) {
+            mSharedLibrariesImpl.snapshot().all[BUILTIN_LIB_NAME] = WatchedLongSparseArray()
+        }
+        assertFailsWith(IllegalStateException::class) {
+            builtinLibs!!.put(VERSION_UNDEFINED, libOfBuiltin(BUILTIN_LIB_NAME))
+        }
+    }
+
+    @Test
+    fun addBuiltInSharedLibrary() {
+        mSharedLibrariesImpl.addBuiltInSharedLibraryLPw(libEntry(TEST_LIB_NAME))
+
+        assertThat(mSharedLibrariesImpl.getSharedLibraryInfos(TEST_LIB_NAME)).isNotNull()
+        assertThat(mSharedLibrariesImpl.getSharedLibraryInfo(TEST_LIB_NAME, VERSION_UNDEFINED))
+            .isNotNull()
+    }
+
+    @Test
+    fun addBuiltInSharedLibrary_withDuplicateLibName() {
+        val duplicate = libEntry(BUILTIN_LIB_NAME, "duplicate.path")
+        mSharedLibrariesImpl.addBuiltInSharedLibraryLPw(duplicate)
+        val sharedLibInfo = mSharedLibrariesImpl
+            .getSharedLibraryInfo(BUILTIN_LIB_NAME, VERSION_UNDEFINED)
+
+        assertThat(sharedLibInfo).isNotNull()
+        assertThat(sharedLibInfo!!.path).isNotEqualTo(duplicate.filename)
+    }
+
+    @Test
+    fun commitSharedLibraryInfo_withStaticSharedLib() {
+        val testInfo = libOfStatic(TEST_LIB_PACKAGE_NAME, TEST_LIB_NAME, 1L)
+        mSharedLibrariesImpl.commitSharedLibraryInfoLPw(testInfo)
+        val sharedLibInfos = mSharedLibrariesImpl
+            .getStaticLibraryInfos(testInfo.declaringPackage.packageName)
+
+        assertThat(mSharedLibrariesImpl.getSharedLibraryInfos(TEST_LIB_NAME))
+            .isNotNull()
+        assertThat(mSharedLibrariesImpl.getSharedLibraryInfo(testInfo.name, testInfo.longVersion))
+            .isNotNull()
+        assertThat(sharedLibInfos).isNotNull()
+        assertThat(sharedLibInfos.get(testInfo.longVersion)).isNotNull()
+    }
+
+    @Test
+    fun removeSharedLibrary() {
+        doAnswer { mutableListOf(VersionedPackage(CONSUMER_PACKAGE_NAME, 1L)) }.`when`(mPms)
+            .getPackagesUsingSharedLibrary(any(), any(), any(), any())
+        val staticInfo = mSharedLibrariesImpl
+            .getSharedLibraryInfo(STATIC_LIB_NAME, STATIC_LIB_VERSION)!!
+
+        mSharedLibrariesImpl.removeSharedLibraryLPw(STATIC_LIB_NAME, STATIC_LIB_VERSION)
+
+        assertThat(mSharedLibrariesImpl.getSharedLibraryInfos(STATIC_LIB_NAME)).isNull()
+        assertThat(mSharedLibrariesImpl
+            .getStaticLibraryInfos(staticInfo.declaringPackage.packageName)).isNull()
+        verify(mExistingSettings[CONSUMER_PACKAGE_NAME]!!)
+            .setOverlayPathsForLibrary(any(), nullable(), any())
+    }
+
+    @Test
+    fun pruneUnusedStaticSharedLibraries() {
+        mSharedLibrariesImpl.pruneUnusedStaticSharedLibraries(Long.MAX_VALUE, 0)
+
+        verify(mDeletePackageHelper)
+            .deletePackageX(eq(STATIC_LIB_PACKAGE_NAME), any(), any(), any(), any())
+    }
+
+    @Test
+    fun getLatestSharedLibraVersion() {
+        val newLibSetting = addPackage(STATIC_LIB_PACKAGE_NAME + "_" + 10, 10L,
+            staticLibrary = STATIC_LIB_NAME, staticLibraryVersion = 10L)
+
+        val latestInfo = mSharedLibrariesImpl.getLatestSharedLibraVersionLPr(newLibSetting.pkg)!!
+
+        assertThat(latestInfo).isNotNull()
+        assertThat(latestInfo.name).isEqualTo(STATIC_LIB_NAME)
+        assertThat(latestInfo.longVersion).isEqualTo(STATIC_LIB_VERSION)
+    }
+
+    @Test
+    fun getStaticSharedLibLatestVersionSetting() {
+        val pair = createBasicAndroidPackage(STATIC_LIB_PACKAGE_NAME + "_" + 10, 10L,
+            staticLibrary = STATIC_LIB_NAME, staticLibraryVersion = 10L)
+        val parsedPackage = pair.second as ParsedPackage
+        val scanRequest = ScanRequest(parsedPackage, null, null, null,
+            null, null, null, 0, 0, false, null, null)
+        val scanResult = ScanResult(scanRequest, true, null, null, false, 0, null, null, null)
+
+        val latestInfoSetting =
+            mSharedLibrariesImpl.getStaticSharedLibLatestVersionSetting(scanResult)!!
+
+        assertThat(latestInfoSetting).isNotNull()
+        assertThat(latestInfoSetting.packageName).isEqualTo(STATIC_LIB_PACKAGE_NAME)
+    }
+
+    @Test
+    fun updateSharedLibraries_withDynamicLibPackage() {
+        val testPackageSetting = mExistingSettings[DYNAMIC_LIB_PACKAGE_NAME]!!
+        assertThat(testPackageSetting.usesLibraryFiles).isEmpty()
+
+        mSharedLibrariesImpl.updateSharedLibrariesLPw(testPackageSetting.pkg, testPackageSetting,
+                null /* changingLib */, null /* changingLibSetting */, mExistingPackages)
+
+        assertThat(testPackageSetting.usesLibraryFiles).hasSize(1)
+        assertThat(testPackageSetting.usesLibraryFiles).contains(builtinLibPath(BUILTIN_LIB_NAME))
+    }
+
+    @Test
+    fun updateSharedLibraries_withStaticLibPackage() {
+        val testPackageSetting = mExistingSettings[STATIC_LIB_PACKAGE_NAME]!!
+        assertThat(testPackageSetting.usesLibraryFiles).isEmpty()
+
+        mSharedLibrariesImpl.updateSharedLibrariesLPw(testPackageSetting.pkg, testPackageSetting,
+                null /* changingLib */, null /* changingLibSetting */, mExistingPackages)
+
+        assertThat(testPackageSetting.usesLibraryFiles).hasSize(1)
+        assertThat(testPackageSetting.usesLibraryFiles).contains(apkPath(DYNAMIC_LIB_PACKAGE_NAME))
+    }
+
+    @Test
+    fun updateSharedLibraries_withConsumerPackage() {
+        val testPackageSetting = mExistingSettings[CONSUMER_PACKAGE_NAME]!!
+        assertThat(testPackageSetting.usesLibraryFiles).isEmpty()
+
+        mSharedLibrariesImpl.updateSharedLibrariesLPw(testPackageSetting.pkg, testPackageSetting,
+                null /* changingLib */, null /* changingLibSetting */, mExistingPackages)
+
+        assertThat(testPackageSetting.usesLibraryFiles).hasSize(2)
+        assertThat(testPackageSetting.usesLibraryFiles).contains(apkPath(DYNAMIC_LIB_PACKAGE_NAME))
+        assertThat(testPackageSetting.usesLibraryFiles).contains(apkPath(STATIC_LIB_PACKAGE_NAME))
+    }
+
+    @Test
+    fun updateAllSharedLibraries() {
+        mExistingSettings.forEach {
+            assertThat(it.value.usesLibraryFiles).isEmpty()
+        }
+
+        mSharedLibrariesImpl.updateAllSharedLibrariesLPw(
+                null /* updatedPkg */, null /* updatedPkgSetting */, mExistingPackages)
+
+        var testPackageSetting = mExistingSettings[DYNAMIC_LIB_PACKAGE_NAME]!!
+        assertThat(testPackageSetting.usesLibraryFiles).hasSize(1)
+        assertThat(testPackageSetting.usesLibraryFiles).contains(builtinLibPath(BUILTIN_LIB_NAME))
+
+        testPackageSetting = mExistingSettings[STATIC_LIB_PACKAGE_NAME]!!
+        assertThat(testPackageSetting.usesLibraryFiles).hasSize(2)
+        assertThat(testPackageSetting.usesLibraryFiles).contains(builtinLibPath(BUILTIN_LIB_NAME))
+        assertThat(testPackageSetting.usesLibraryFiles).contains(apkPath(DYNAMIC_LIB_PACKAGE_NAME))
+
+        testPackageSetting = mExistingSettings[CONSUMER_PACKAGE_NAME]!!
+        assertThat(testPackageSetting.usesLibraryFiles).hasSize(3)
+        assertThat(testPackageSetting.usesLibraryFiles).contains(builtinLibPath(BUILTIN_LIB_NAME))
+        assertThat(testPackageSetting.usesLibraryFiles).contains(apkPath(DYNAMIC_LIB_PACKAGE_NAME))
+        assertThat(testPackageSetting.usesLibraryFiles).contains(apkPath(STATIC_LIB_PACKAGE_NAME))
+    }
+
+    private fun addExistingPackages() {
+        // add a dynamic shared library that is using the builtin library
+        addPackage(DYNAMIC_LIB_PACKAGE_NAME, 1L,
+            libraries = arrayOf(DYNAMIC_LIB_NAME),
+            usesLibraries = arrayOf(BUILTIN_LIB_NAME))
+
+        // add a static shared library v7 that is using the dynamic shared library
+        addPackage(STATIC_LIB_PACKAGE_NAME, STATIC_LIB_VERSION,
+            staticLibrary = STATIC_LIB_NAME, staticLibraryVersion = STATIC_LIB_VERSION,
+            usesLibraries = arrayOf(DYNAMIC_LIB_NAME))
+
+        // add a consumer package that is using the dynamic and static shared library
+        addPackage(CONSUMER_PACKAGE_NAME, 1L,
+            usesLibraries = arrayOf(DYNAMIC_LIB_NAME),
+            usesStaticLibraries = arrayOf(STATIC_LIB_NAME),
+            usesStaticLibraryVersions = arrayOf(STATIC_LIB_VERSION))
+    }
+
+    private fun addExistingSharedLibraries() {
+        mSharedLibrariesImpl.addBuiltInSharedLibraryLPw(libEntry(BUILTIN_LIB_NAME))
+        mSharedLibrariesImpl.commitSharedLibraryInfoLPw(
+            libOfDynamic(DYNAMIC_LIB_PACKAGE_NAME, DYNAMIC_LIB_NAME))
+        mSharedLibrariesImpl.commitSharedLibraryInfoLPw(
+            libOfStatic(STATIC_LIB_PACKAGE_NAME, STATIC_LIB_NAME, STATIC_LIB_VERSION))
+    }
+
+    private fun addPackage(
+        packageName: String,
+        version: Long,
+        libraries: Array<String>? = null,
+        staticLibrary: String? = null,
+        staticLibraryVersion: Long = 0L,
+        usesLibraries: Array<String>? = null,
+        usesStaticLibraries: Array<String>? = null,
+        usesStaticLibraryVersions: Array<Long>? = null
+    ): PackageSetting {
+        val pair = createBasicAndroidPackage(packageName, version, libraries, staticLibrary,
+            staticLibraryVersion, usesLibraries, usesStaticLibraries, usesStaticLibraryVersions)
+        val apkPath = pair.first
+        val parsingPackage = pair.second
+        val spyPkg = spy((parsingPackage as ParsedPackage).hideAsFinal())
+        mExistingPackages[packageName] = spyPkg
+
+        val spyPackageSetting = spy(mRule.system()
+            .createBasicSettingBuilder(apkPath.parentFile, spyPkg).build())
+        mExistingSettings[spyPackageSetting.packageName] = spyPackageSetting
+
+        return spyPackageSetting
+    }
+
+    private fun createBasicAndroidPackage(
+        packageName: String,
+        version: Long,
+        libraries: Array<String>? = null,
+        staticLibrary: String? = null,
+        staticLibraryVersion: Long = 0L,
+        usesLibraries: Array<String>? = null,
+        usesStaticLibraries: Array<String>? = null,
+        usesStaticLibraryVersions: Array<Long>? = null
+    ): Pair<File, PackageImpl> {
+        assertFalse { libraries != null && staticLibrary != null }
+        assertTrue { (usesStaticLibraries?.size ?: -1) == (usesStaticLibraryVersions?.size ?: -1) }
+
+        val pair = mRule.system()
+            .createBasicAndroidPackage(mRule.system().dataAppDirectory, packageName, version)
+        pair.second.apply {
+            setTargetSdkVersion(Build.VERSION_CODES.S)
+            libraries?.forEach { addLibraryName(it) }
+            staticLibrary?.let {
+                setStaticSharedLibName(it)
+                setStaticSharedLibVersion(staticLibraryVersion)
+                setStaticSharedLibrary(true)
+            }
+            usesLibraries?.forEach { addUsesLibrary(it) }
+            usesStaticLibraries?.forEachIndexed { index, s ->
+                addUsesStaticLibrary(s,
+                    usesStaticLibraryVersions?.get(index) ?: 0L,
+                        arrayOf(s))
+            }
+        }
+        return pair
+    }
+
+    private fun libEntry(libName: String, path: String? = null): SharedLibraryEntry =
+        SharedLibraryEntry(libName, path ?: builtinLibPath(libName),
+            arrayOfNulls(0), false /* isNative */)
+
+    private fun libOfBuiltin(libName: String): SharedLibraryInfo =
+        SharedLibraryInfo(builtinLibPath(libName),
+            null /* packageName */,
+            null /* codePaths */,
+            libName,
+            VERSION_UNDEFINED,
+            SharedLibraryInfo.TYPE_BUILTIN,
+            VersionedPackage(PLATFORM_PACKAGE_NAME, 0L /* versionCode */),
+            null /* dependentPackages */,
+            null /* dependencies */,
+            false /* isNative */)
+
+    private fun libOfStatic(
+        packageName: String,
+        libName: String,
+        version: Long
+    ): SharedLibraryInfo =
+        SharedLibraryInfo(null /* path */,
+            packageName,
+            listOf(apkPath(packageName)),
+            libName,
+            version,
+            SharedLibraryInfo.TYPE_STATIC,
+            VersionedPackage(packageName, version /* versionCode */),
+            null /* dependentPackages */,
+            null /* dependencies */,
+            false /* isNative */)
+
+    private fun libOfDynamic(packageName: String, libName: String): SharedLibraryInfo =
+        SharedLibraryInfo(null /* path */,
+            packageName,
+            listOf(apkPath(packageName)),
+            libName,
+            VERSION_UNDEFINED,
+            SharedLibraryInfo.TYPE_DYNAMIC,
+            VersionedPackage(packageName, 1L /* versionCode */),
+            null /* dependentPackages */,
+            null /* dependencies */,
+            false /* isNative */)
+
+    private fun builtinLibPath(libName: String): String = "/system/app/$libName/$libName.jar"
+
+    private fun apkPath(packageName: String): String =
+            File(mRule.system().dataAppDirectory, packageName).path
+}