| /* |
| * 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 |
| } |