blob: db0c3aece02361e1abca441c21e429f8e717b7b6 [file] [log] [blame]
/*
* Copyright (C) 2020 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.Context
import android.content.Intent
import android.content.pm.ActivityInfo
import android.content.pm.ApplicationInfo
import android.content.pm.FallbackCategoryProvider
import android.content.pm.FeatureInfo
import android.content.pm.PackageParser.SigningDetails
import android.content.pm.ResolveInfo
import android.content.pm.ServiceInfo
import android.content.pm.Signature
import android.content.pm.UserInfo
import android.content.pm.parsing.ParsingPackage
import android.content.pm.parsing.ParsingPackageUtils
import android.content.res.Resources
import android.hardware.display.DisplayManager
import android.os.Build
import android.os.Environment
import android.os.SystemProperties
import android.os.UserHandle
import android.os.UserManager
import android.os.incremental.IncrementalManager
import android.provider.DeviceConfig
import android.util.ArrayMap
import android.util.DisplayMetrics
import android.util.EventLog
import android.view.Display
import com.android.dx.mockito.inline.extended.ExtendedMockito
import com.android.dx.mockito.inline.extended.ExtendedMockito.any
import com.android.dx.mockito.inline.extended.ExtendedMockito.anyBoolean
import com.android.dx.mockito.inline.extended.ExtendedMockito.anyInt
import com.android.dx.mockito.inline.extended.ExtendedMockito.anyString
import com.android.dx.mockito.inline.extended.ExtendedMockito.argThat
import com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn
import com.android.dx.mockito.inline.extended.ExtendedMockito.eq
import com.android.dx.mockito.inline.extended.ExtendedMockito.spy
import com.android.dx.mockito.inline.extended.StaticMockitoSession
import com.android.dx.mockito.inline.extended.StaticMockitoSessionBuilder
import com.android.internal.R
import com.android.server.LocalServices
import com.android.server.LockGuard
import com.android.server.SystemConfig
import com.android.server.SystemServerInitThreadPool
import com.android.server.compat.PlatformCompat
import com.android.server.extendedtestutils.wheneverStatic
import com.android.server.pm.dex.DexManager
import com.android.server.pm.parsing.PackageParser2
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.pm.permission.PermissionManagerServiceInternal
import com.android.server.pm.verify.domain.DomainVerificationManagerInternal
import com.android.server.testutils.TestHandler
import com.android.server.testutils.mock
import com.android.server.testutils.nullable
import com.android.server.testutils.whenever
import com.android.server.utils.WatchedArrayMap
import org.junit.Assert
import org.junit.rules.TestRule
import org.junit.runner.Description
import org.junit.runners.model.Statement
import org.mockito.AdditionalMatchers.or
import org.mockito.quality.Strictness
import java.io.File
import java.io.IOException
import java.nio.file.Files
import java.security.PublicKey
import java.security.cert.CertificateException
import java.util.Arrays
import java.util.Random
import java.util.concurrent.FutureTask
/**
* A utility for mocking behavior of the system and dependencies when testing PackageManagerService
*
* Create one of these and call [stageNominalSystemState] as a basis for additional behavior in most
* tests.
*/
class MockSystem(withSession: (StaticMockitoSessionBuilder) -> Unit = {}) {
private val random = Random()
val mocks = Mocks()
val packageCacheDirectory: File =
Files.createTempDirectory("packageCache").toFile()
val rootDirectory: File =
Files.createTempDirectory("root").toFile()
val dataAppDirectory: File =
File(Files.createTempDirectory("data").toFile(), "app")
val frameworkSignature: SigningDetails = SigningDetails(arrayOf(generateSpySignature()), 3)
val systemPartitions: List<PackageManagerService.ScanPartition> =
redirectScanPartitions(PackageManagerService.SYSTEM_PARTITIONS)
val session: StaticMockitoSession
/** Tracks temporary files created by this class during the running of a test. */
private val createdFiles = ArrayList<File>()
/** Settings that are expected to be added as part of the test */
private val mPendingPackageAdds: MutableList<Pair<String, PackageSetting>> = ArrayList()
/** Settings simulated to be stored on disk */
private val mPreExistingSettings = ArrayMap<String, PackageSetting>()
/** The active map simulating the in memory storage of Settings */
private val mSettingsMap = WatchedArrayMap<String, PackageSetting>()
init {
val apply = ExtendedMockito.mockitoSession()
.strictness(Strictness.LENIENT)
.mockStatic(SystemProperties::class.java)
.mockStatic(SystemConfig::class.java)
.mockStatic(SELinuxMMAC::class.java)
.mockStatic(FallbackCategoryProvider::class.java)
.mockStatic(PackageManagerServiceUtils::class.java)
.mockStatic(Environment::class.java)
.mockStatic(SystemServerInitThreadPool::class.java)
.mockStatic(ParsingPackageUtils::class.java)
.mockStatic(LockGuard::class.java)
.mockStatic(EventLog::class.java)
.mockStatic(LocalServices::class.java)
.mockStatic(DeviceConfig::class.java)
.apply(withSession)
session = apply.startMocking()
whenever(mocks.settings.insertPackageSettingLPw(
any(PackageSetting::class.java), any(AndroidPackage::class.java))) {
val name: String = (getArgument<Any>(0) as PackageSetting).name
val pendingAdd =
mPendingPackageAdds.firstOrNull { it.first == name } ?: return@whenever null
mPendingPackageAdds.remove(pendingAdd)
mSettingsMap[name] = pendingAdd.second
null
}
whenever(mocks.settings.addPackageLPw(nullable(), nullable(), nullable(), nullable(),
nullable(), nullable(), nullable(), nullable(), nullable(), nullable(), nullable(),
nullable(), nullable(), nullable(), nullable())) {
val name: String = getArgument(0)
val pendingAdd = mPendingPackageAdds.firstOrNull { it.first == name }
?: return@whenever null
mPendingPackageAdds.remove(pendingAdd)
mSettingsMap[name] = pendingAdd.second
pendingAdd.second
}
whenever(mocks.settings.packagesLocked).thenReturn(mSettingsMap)
whenever(mocks.settings.getPackageLPr(anyString())) { mSettingsMap[getArgument<Any>(0)] }
whenever(mocks.settings.readLPw(nullable())) {
mSettingsMap.putAll(mPreExistingSettings)
!mPreExistingSettings.isEmpty()
}
}
/** Collection of mocks used for PackageManagerService tests. */
class Mocks {
val lock = PackageManagerTracedLock()
val installLock = Any()
val injector: PackageManagerService.Injector = mock()
val systemWrapper: PackageManagerService.SystemWrapper = mock()
val context: Context = mock()
val userManagerService: UserManagerService = mock()
val componentResolver: ComponentResolver = mock()
val permissionManagerInternal: PermissionManagerServiceInternal = mock()
val incrementalManager: IncrementalManager = mock()
val platformCompat: PlatformCompat = mock()
val settings: Settings = mock()
val resources: Resources = mock()
val systemConfig: SystemConfig = mock()
val apexManager: ApexManager = mock()
val userManagerInternal: UserManagerInternal = mock()
val packageParser: PackageParser2 = mock()
val keySetManagerService: KeySetManagerService = mock()
val packageAbiHelper: PackageAbiHelper = mock()
val appsFilter: AppsFilter = mock()
val dexManager: DexManager = mock()
val installer: Installer = mock()
val displayMetrics: DisplayMetrics = mock()
val domainVerificationManagerInternal: DomainVerificationManagerInternal = mock()
val handler = TestHandler(null)
}
companion object {
private const val DEVICE_PROVISIONING_PACKAGE_NAME =
"com.example.android.device.provisioning"
private val DEFAULT_AVAILABLE_FEATURES_MAP = ArrayMap<String, FeatureInfo>()
private val DEFAULT_ACTIVE_APEX_INFO_LIST = emptyList<ApexManager.ActiveApexInfo>()
private val DEFAULT_SHARED_LIBRARIES_LIST =
ArrayMap<String, SystemConfig.SharedLibraryEntry>()
private val DEFAULT_USERS = Arrays.asList(
UserInfo(UserHandle.USER_SYSTEM, "primary", "",
UserInfo.FLAG_PRIMARY or UserInfo.FLAG_SYSTEM or UserInfo.FLAG_FULL,
UserManager.USER_TYPE_FULL_SYSTEM))
public val DEFAULT_VERSION_INFO = Settings.VersionInfo()
init {
DEFAULT_VERSION_INFO.fingerprint = "abcdef"
DEFAULT_VERSION_INFO.sdkVersion = Build.VERSION_CODES.R
DEFAULT_VERSION_INFO.databaseVersion = Settings.CURRENT_DATABASE_VERSION
}
}
/**
* Clean up any potentially dangling state. This should be run at the end of every test to
* account for changes to static memory, such as [LocalServices]
*/
fun cleanup() {
createdFiles.forEach(File::delete)
createdFiles.clear()
mSettingsMap.clear()
mPendingPackageAdds.clear()
mPreExistingSettings.clear()
session.finishMocking()
}
/**
* Run this method to ensure that all expected actions were executed, such as pending
* [Settings] adds.
*/
fun validateFinalState() {
if (mPendingPackageAdds.isNotEmpty()) {
Assert.fail(
"Not all expected settings were added: ${mPendingPackageAdds.map { it.first }}")
}
}
/**
* This method stages enough of system startup to execute the PackageManagerService constructor
* successfullly.
*/
@Throws(Exception::class)
fun stageNominalSystemState() {
whenever(mocks.injector.context).thenReturn(mocks.context)
whenever(mocks.injector.lock).thenReturn(mocks.lock)
whenever(mocks.injector.installLock).thenReturn(mocks.installLock)
whenever(mocks.injector.systemWrapper).thenReturn(mocks.systemWrapper)
whenever(mocks.injector.userManagerService).thenReturn(mocks.userManagerService)
whenever(mocks.injector.componentResolver).thenReturn(mocks.componentResolver)
whenever(mocks.injector.permissionManagerServiceInternal) {
mocks.permissionManagerInternal
}
whenever(mocks.injector.incrementalManager).thenReturn(mocks.incrementalManager)
whenever(mocks.injector.compatibility).thenReturn(mocks.platformCompat)
whenever(mocks.injector.settings).thenReturn(mocks.settings)
whenever(mocks.injector.dexManager).thenReturn(mocks.dexManager)
whenever(mocks.injector.systemConfig).thenReturn(mocks.systemConfig)
whenever(mocks.injector.apexManager).thenReturn(mocks.apexManager)
whenever(mocks.injector.scanningCachingPackageParser).thenReturn(mocks.packageParser)
whenever(mocks.injector.scanningPackageParser).thenReturn(mocks.packageParser)
whenever(mocks.injector.systemPartitions).thenReturn(systemPartitions)
whenever(mocks.injector.appsFilter).thenReturn(mocks.appsFilter)
whenever(mocks.injector.abiHelper).thenReturn(mocks.packageAbiHelper)
whenever(mocks.injector.userManagerInternal).thenReturn(mocks.userManagerInternal)
whenever(mocks.injector.installer).thenReturn(mocks.installer)
whenever(mocks.injector.displayMetrics).thenReturn(mocks.displayMetrics)
whenever(mocks.injector.domainVerificationManagerInternal)
.thenReturn(mocks.domainVerificationManagerInternal)
whenever(mocks.injector.handler) { mocks.handler }
wheneverStatic { SystemConfig.getInstance() }.thenReturn(mocks.systemConfig)
whenever(mocks.systemConfig.availableFeatures).thenReturn(DEFAULT_AVAILABLE_FEATURES_MAP)
whenever(mocks.systemConfig.sharedLibraries).thenReturn(DEFAULT_SHARED_LIBRARIES_LIST)
wheneverStatic { SystemProperties.getBoolean("fw.free_cache_v2", true) }.thenReturn(true)
wheneverStatic { Environment.getPackageCacheDirectory() }.thenReturn(packageCacheDirectory)
wheneverStatic { SystemProperties.digestOf("ro.build.fingerprint") }.thenReturn("cacheName")
wheneverStatic { Environment.getRootDirectory() }.thenReturn(rootDirectory)
wheneverStatic { SystemServerInitThreadPool.submit(any(Runnable::class.java), anyString()) }
.thenAnswer { FutureTask<Any?>(it.getArgument(0), null) }
wheneverStatic { Environment.getDataDirectory() }.thenReturn(dataAppDirectory.parentFile)
wheneverStatic { Environment.getDataSystemDirectory() }
.thenReturn(File(dataAppDirectory.parentFile, "system"))
whenever(mocks.context.resources).thenReturn(mocks.resources)
whenever(mocks.resources.getString(R.string.config_deviceProvisioningPackage)) {
DEVICE_PROVISIONING_PACKAGE_NAME
}
whenever(mocks.apexManager.activeApexInfos).thenReturn(DEFAULT_ACTIVE_APEX_INFO_LIST)
whenever(mocks.settings.packagesLocked).thenReturn(mSettingsMap)
whenever(mocks.settings.internalVersion).thenReturn(DEFAULT_VERSION_INFO)
whenever(mocks.settings.keySetManagerService).thenReturn(mocks.keySetManagerService)
whenever(mocks.settings.keySetManagerService).thenReturn(mocks.keySetManagerService)
whenever(mocks.settings.snapshot()).thenReturn(mocks.settings)
whenever(mocks.packageAbiHelper.derivePackageAbi(
any(AndroidPackage::class.java), anyBoolean(), nullable(), any(File::class.java))) {
android.util.Pair(PackageAbiHelper.Abis("", ""),
PackageAbiHelper.NativeLibraryPaths("", false, "", ""))
}
whenever(mocks.userManagerInternal.getUsers(true, false, false)).thenReturn(DEFAULT_USERS)
whenever(mocks.userManagerService.userIds).thenReturn(intArrayOf(0))
whenever(mocks.userManagerService.exists(0)).thenReturn(true)
whenever(mocks.packageAbiHelper.deriveNativeLibraryPaths(
any(AndroidPackage::class.java), anyBoolean(), any(File::class.java))) {
PackageAbiHelper.NativeLibraryPaths("", false, "", "")
}
// everything visible by default
whenever(mocks.appsFilter.shouldFilterApplication(
anyInt(), nullable(), nullable(), anyInt())) { false }
val displayManager: DisplayManager = mock()
whenever(mocks.context.getSystemService(DisplayManager::class.java))
.thenReturn(displayManager)
val display: Display = mock()
whenever(displayManager.getDisplay(Display.DEFAULT_DISPLAY)).thenReturn(display)
stageFrameworkScan()
stageInstallerScan()
stageServicesExtensionScan()
stageSystemSharedLibraryScan()
stagePermissionsControllerScan()
stageInstantAppResolverScan()
}
/**
* This method will stage the parsing and scanning of a package as well as add it to the
* [PackageSetting]s read from disk.
*/
@Throws(Exception::class)
fun stageScanExistingPackage(
packageName: String,
versionCode: Long,
parent: File?,
withPackage: (PackageImpl) -> PackageImpl = { it },
withSetting:
(PackageSettingBuilder) -> PackageSettingBuilder = { it },
withExistingSetting:
(PackageSettingBuilder) -> PackageSettingBuilder = { it }
) {
val existingSettingBuilderRef = arrayOfNulls<PackageSettingBuilder>(1)
stageScanNewPackage(packageName, versionCode, parent, withPackage,
withSetting = { settingBuilder ->
withSetting(settingBuilder)
existingSettingBuilderRef[0] = settingBuilder
settingBuilder
})
existingSettingBuilderRef[0]?.setPackage(null)
val packageSetting = existingSettingBuilderRef[0]?.let { withExistingSetting(it) }!!.build()
addPreExistingSetting(packageName, packageSetting)
}
/**
* This method will stage a [PackageSetting] read from disk, but does not stage any scanning
* or parsing of the package.
*/
fun addPreExistingSetting(packageName: String, packageSetting: PackageSetting) {
mPreExistingSettings[packageName] = packageSetting
}
/**
* This method will stage the parsing and scanning of a package but will not add it to the set
* of [PackageSetting]s read from disk.
*/
@Throws(Exception::class)
fun stageScanNewPackage(
packageName: String,
versionCode: Long,
parent: File?,
withPackage: (PackageImpl) -> PackageImpl = { it },
withSetting: (PackageSettingBuilder) -> PackageSettingBuilder = { it }
) {
val pair = createBasicAndroidPackage(parent, packageName, versionCode)
val apkPath = pair.first
val pkg = withPackage(pair.second)
stageParse(apkPath, pkg)
val parentFile = apkPath.parentFile
val settingBuilder = withSetting(createBasicSettingBuilder(parentFile, pkg))
stageSettingInsert(packageName, settingBuilder.build())
}
/**
* Creates a simple package that should reasonably parse for scan operations. This can be used
* as a basis for more complicated packages.
*/
fun createBasicAndroidPackage(
parent: File?,
packageName: String,
versionCode: Long,
signingDetails: SigningDetails =
createRandomSigningDetails()
): Pair<File, PackageImpl> {
val apkPath = File(File(parent, packageName), "base.apk")
val pkg = PackageImpl.forTesting(packageName, apkPath.parentFile.path) as PackageImpl
pkg.signingDetails = signingDetails
wheneverStatic { ParsingPackageUtils.getSigningDetails(eq(pkg), anyBoolean()) }
.thenReturn(signingDetails)
pkg.versionCode = versionCode.toInt()
pkg.versionCodeMajor = (versionCode shr 32).toInt()
pkg.targetSdkVersion = Build.VERSION_CODES.CUR_DEVELOPMENT
return Pair(apkPath, pkg)
}
/**
* This method will create a spy of a [SigningDetails] object to be used when simulating the
* collection of signatures.
*/
fun createRandomSigningDetails(): SigningDetails {
val signingDetails = spy(SigningDetails(arrayOf(generateSpySignature()),
SigningDetails.SignatureSchemeVersion.SIGNING_BLOCK_V3))
doReturn(true).whenever(signingDetails).checkCapability(
anyString(), anyInt())
doReturn(true).whenever(signingDetails).checkCapability(
any(SigningDetails::class.java), anyInt())
return signingDetails
}
/**
* This method will create a basic [PackageSettingBuilder] from an [AndroidPackage] with all of
* the necessary parameters to be returned by a simple scan. This can be used as a basis for
* more complicated settings.
*/
fun createBasicSettingBuilder(parentFile: File, pkg: AndroidPackage): PackageSettingBuilder {
return createBasicSettingBuilder(parentFile, pkg.packageName, pkg.longVersionCode,
pkg.signingDetails)
.setPackage(pkg)
}
/**
* This method will create a basic [PackageSettingBuilder] with all of the necessary parameters
* to be returned by a simple scan. This can be used as a basis for more complicated settings.
*/
fun createBasicSettingBuilder(
parentFile: File,
packageName: String,
versionCode: Long,
signingDetails: SigningDetails
): PackageSettingBuilder {
return PackageSettingBuilder()
.setCodePath(parentFile.path)
.setName(packageName)
.setPVersionCode(versionCode)
.setSigningDetails(signingDetails)
}
fun createBasicApplicationInfo(pkg: ParsingPackage): ApplicationInfo {
val applicationInfo: ApplicationInfo = mock()
applicationInfo.packageName = pkg.packageName
return applicationInfo
}
fun createBasicActivityInfo(
pkg: ParsingPackage,
applicationInfo: ApplicationInfo?,
className: String?
):
ActivityInfo {
val activityInfo = ActivityInfo()
activityInfo.applicationInfo = applicationInfo
activityInfo.packageName = pkg.packageName
activityInfo.name = className
return activityInfo
}
fun createBasicServiceInfo(
pkg: ParsingPackage,
applicationInfo: ApplicationInfo?,
className: String?
):
ServiceInfo {
val serviceInfo = ServiceInfo()
serviceInfo.applicationInfo = applicationInfo
serviceInfo.packageName = pkg.packageName
serviceInfo.name = className
return serviceInfo
}
/** Finds the appropriate partition, if available, based on a scan flag unique to it. */
fun getPartitionFromFlag(scanFlagMask: Int): PackageManagerService.ScanPartition =
systemPartitions.first { (it.scanFlag and scanFlagMask) != 0 }
@Throws(Exception::class)
private fun stageParse(path: File, parseResult: ParsingPackage): ParsedPackage {
val basePath = path.parentFile
basePath.mkdirs()
path.createNewFile()
createdFiles.add(path)
val parsedPackage = parseResult.hideAsParsed() as ParsedPackage
whenever(mocks.packageParser.parsePackage(
or(eq(path), eq(basePath)), anyInt(), anyBoolean())) { parsedPackage }
return parsedPackage
}
private fun stageSettingInsert(name: String, setting: PackageSetting): PackageSetting {
mPendingPackageAdds.add(Pair(name, setting))
return setting
}
@Throws(Exception::class)
private fun stageFrameworkScan() {
val apk = File(File(rootDirectory, "framework"), "framework-res.apk")
val frameworkPkg = PackageImpl.forTesting("android",
apk.parentFile.path) as PackageImpl
wheneverStatic { ParsingPackageUtils.getSigningDetails(frameworkPkg, true) }
.thenReturn(frameworkSignature)
stageParse(apk, frameworkPkg)
stageSettingInsert("android",
PackageSettingBuilder().setCodePath(apk.path).setName(
"android").setPackage(frameworkPkg).build())
}
@Throws(Exception::class)
private fun stageInstantAppResolverScan() {
whenever(mocks.resources.getStringArray(R.array.config_ephemeralResolverPackage)) {
arrayOf("com.android.test.ephemeral.resolver")
}
stageScanNewPackage("com.android.test.ephemeral.resolver",
1L, getPartitionFromFlag(PackageManagerService.SCAN_AS_PRODUCT).privAppFolder,
withPackage = { pkg: PackageImpl ->
val applicationInfo: ApplicationInfo = createBasicApplicationInfo(pkg)
whenever(applicationInfo.isPrivilegedApp).thenReturn(true)
mockQueryServices(Intent.ACTION_RESOLVE_INSTANT_APP_PACKAGE,
createBasicServiceInfo(pkg, applicationInfo, "test.EphemeralService"))
mockQueryActivities(Intent.ACTION_INSTANT_APP_RESOLVER_SETTINGS,
createBasicActivityInfo(pkg, applicationInfo, "test.SettingsActivity"))
pkg
},
withSetting = { setting: PackageSettingBuilder ->
setting.setPkgFlags(ApplicationInfo.FLAG_SYSTEM)
})
}
@Throws(Exception::class)
private fun stagePermissionsControllerScan() {
stageScanNewPackage("com.android.permissions.controller",
1L, systemPartitions[0].privAppFolder,
withPackage = { pkg: PackageImpl ->
val applicationInfo: ApplicationInfo = createBasicApplicationInfo(pkg)
whenever(applicationInfo.isPrivilegedApp).thenReturn(true)
mockQueryActivities(Intent.ACTION_MANAGE_PERMISSIONS,
createBasicActivityInfo(
pkg, applicationInfo, "test.PermissionActivity"))
pkg
},
withSetting = { setting: PackageSettingBuilder ->
setting.setPkgFlags(ApplicationInfo.FLAG_SYSTEM)
})
}
@Throws(Exception::class)
private fun stageSystemSharedLibraryScan() {
stageScanNewPackage("android.ext.shared",
1L, systemPartitions[0].appFolder,
withPackage = { it.addLibraryName("android.ext.shared") as PackageImpl },
withSetting = { setting: PackageSettingBuilder ->
setting.setPkgFlags(ApplicationInfo.FLAG_SYSTEM)
}
)
}
@Throws(Exception::class)
private fun stageServicesExtensionScan() {
whenever(mocks.context.getString(R.string.config_servicesExtensionPackage)) {
"com.android.test.services.extension"
}
stageScanNewPackage("com.android.test.services.extension",
1L, getPartitionFromFlag(PackageManagerService.SCAN_AS_SYSTEM_EXT).privAppFolder,
withSetting = { setting: PackageSettingBuilder ->
setting.setPkgFlags(ApplicationInfo.FLAG_SYSTEM)
})
}
@Throws(Exception::class)
private fun stageInstallerScan() {
stageScanNewPackage(
"com.android.test.installer",
1L, getPartitionFromFlag(PackageManagerService.SCAN_AS_PRODUCT).privAppFolder,
withPackage = { pkg: PackageImpl ->
val applicationInfo: ApplicationInfo = createBasicApplicationInfo(pkg)
whenever(applicationInfo.isPrivilegedApp).thenReturn(true)
val installerActivity: ActivityInfo = createBasicActivityInfo(
pkg, applicationInfo, "test.InstallerActivity")
mockQueryActivities(Intent.ACTION_INSTALL_PACKAGE, installerActivity)
mockQueryActivities(Intent.ACTION_UNINSTALL_PACKAGE, installerActivity)
mockQueryActivities(Intent.ACTION_INSTALL_INSTANT_APP_PACKAGE,
installerActivity)
pkg
},
withSetting = { setting: PackageSettingBuilder ->
setting.setPkgFlags(ApplicationInfo.FLAG_SYSTEM)
}
)
}
private fun mockQueryActivities(action: String, vararg activities: ActivityInfo) {
whenever(mocks.componentResolver.queryActivities(
argThat { intent: Intent? -> intent != null && (action == intent.action) },
nullable(), anyInt(), anyInt())) {
ArrayList(activities.asList().map { info: ActivityInfo? ->
ResolveInfo().apply { activityInfo = info }
})
}
}
private fun mockQueryServices(action: String, vararg services: ServiceInfo) {
whenever(mocks.componentResolver.queryServices(
argThat { intent: Intent? -> intent != null && (action == intent.action) },
nullable(), anyInt(), anyInt())) {
ArrayList(services.asList().map { info ->
ResolveInfo().apply { serviceInfo = info }
})
}
}
fun generateSpySignature(): Signature {
val bytes = ByteArray(32)
random.nextBytes(bytes)
val signature = spy(Signature(bytes))
try {
val mockPublicKey: PublicKey = mock()
doReturn(mockPublicKey).whenever(signature).getPublicKey()
} catch (e: CertificateException) {
throw RuntimeException(e)
}
return signature
}
/** Override get*Folder methods to point to temporary local directories */
@Throws(IOException::class)
private fun redirectScanPartitions(partitions: List<PackageManagerService.ScanPartition>):
List<PackageManagerService.ScanPartition> {
val spiedPartitions: MutableList<PackageManagerService.ScanPartition> =
ArrayList(partitions.size)
for (partition: PackageManagerService.ScanPartition in partitions) {
val spy = spy(partition)
val newRoot = Files.createTempDirectory(partition.folder.name).toFile()
whenever(spy.overlayFolder).thenReturn(File(newRoot, "overlay"))
whenever(spy.appFolder).thenReturn(File(newRoot, "app"))
whenever(spy.privAppFolder).thenReturn(File(newRoot, "priv-app"))
whenever(spy.folder).thenReturn(newRoot)
spiedPartitions.add(spy)
}
return spiedPartitions
}
}
/**
* Sets up a basic [MockSystem] for use in a test method. This will create a MockSystem before the
* test method and any [org.junit.Before] annotated methods. It can then be used to access the
* MockSystem via the [system] method or the mocks directly via [mocks].
*/
class MockSystemRule : TestRule {
var mockSystem: MockSystem? = null
override fun apply(base: Statement?, description: Description?) = object : Statement() {
@Throws(Throwable::class)
override fun evaluate() {
mockSystem = MockSystem()
try {
base!!.evaluate()
} finally {
mockSystem?.cleanup()
mockSystem = null
}
}
}
/** Fetch the [MockSystem] instance prepared for this test */
fun system(): MockSystem = mockSystem!!
/** Fetch the [MockSystem.Mocks] prepared for this test */
fun mocks(): MockSystem.Mocks = mockSystem!!.mocks
}