Skip to content

Commit

Permalink
Merge pull request #502 from chirayudesai/78-skipapk
Browse files Browse the repository at this point in the history
Skip installing APKs if not allowed by policy
  • Loading branch information
grote committed May 14, 2024
2 parents bb562a4 + 422e3f5 commit 6dfc058
Show file tree
Hide file tree
Showing 5 changed files with 69 additions and 4 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import com.stevesoltys.seedvault.plugins.LegacyStoragePlugin
import com.stevesoltys.seedvault.plugins.StoragePlugin
import com.stevesoltys.seedvault.plugins.StoragePluginManager
import com.stevesoltys.seedvault.restore.RestorableBackup
import com.stevesoltys.seedvault.restore.install.ApkInstallState.FAILED
import com.stevesoltys.seedvault.restore.install.ApkInstallState.FAILED_SYSTEM_APP
import com.stevesoltys.seedvault.restore.install.ApkInstallState.IN_PROGRESS
import com.stevesoltys.seedvault.restore.install.ApkInstallState.QUEUED
Expand All @@ -35,6 +36,7 @@ internal class ApkRestore(
private val crypto: Crypto,
private val splitCompatChecker: ApkSplitCompatibilityChecker,
private val apkInstaller: ApkInstaller,
private val installRestriction: InstallRestriction,
) {

private val pm = context.packageManager
Expand All @@ -47,6 +49,7 @@ internal class ApkRestore(
// Otherwise, it gets killed when we install it, terminating our restoration.
it.key != storagePlugin.providerPackageName
}
val isAllowedToInstallApks = installRestriction.isAllowedToInstallApks()
val total = packages.size
var progress = 0

Expand All @@ -57,11 +60,17 @@ internal class ApkRestore(
installResult[packageName] = ApkInstallResult(
packageName = packageName,
progress = progress,
state = QUEUED,
state = if (isAllowedToInstallApks) QUEUED else FAILED,
installerPackageName = metadata.installer
)
}
emit(installResult)
if (isAllowedToInstallApks) {
emit(installResult)
} else {
installResult.isFinished = true
emit(installResult)
return@flow
}

// re-install individual packages and emit updates
for ((packageName, metadata) in packages) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,16 @@
package com.stevesoltys.seedvault.restore.install

import android.os.UserManager
import org.koin.android.ext.koin.androidContext
import org.koin.dsl.module

val installModule = module {
factory { ApkInstaller(androidContext()) }
factory { DeviceInfo(androidContext()) }
factory { ApkSplitCompatibilityChecker(get()) }
factory { ApkRestore(androidContext(), get(), get(), get(), get(), get()) }
factory {
ApkRestore(androidContext(), get(), get(), get(), get(), get()) {
androidContext().getSystemService(UserManager::class.java).isAllowedToInstallApks()
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package com.stevesoltys.seedvault.restore.install

import android.os.UserManager

internal fun interface InstallRestriction {
fun isAllowedToInstallApks(): Boolean
}

private fun UserManager.isRestricted(restriction: String): Boolean {
return userRestrictions.getBoolean(restriction, false)
}

internal fun UserManager.isAllowedToInstallApks(): Boolean {
return isRestricted(UserManager.DISALLOW_INSTALL_APPS) ||
isRestricted(UserManager.DISALLOW_INSTALL_UNKNOWN_SOURCES) ||
isRestricted(UserManager.DISALLOW_INSTALL_UNKNOWN_SOURCES_GLOBALLY)
}
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ internal class ApkBackupRestoreTest : TransportTest() {
private val storagePlugin: StoragePlugin<*> = mockk()
private val splitCompatChecker: ApkSplitCompatibilityChecker = mockk()
private val apkInstaller: ApkInstaller = mockk()
private val installRestriction: InstallRestriction = mockk()

private val apkBackup = ApkBackup(pm, crypto, settingsManager, metadataManager)
private val apkRestore: ApkRestore = ApkRestore(
Expand All @@ -60,7 +61,8 @@ internal class ApkBackupRestoreTest : TransportTest() {
legacyStoragePlugin = legacyStoragePlugin,
crypto = crypto,
splitCompatChecker = splitCompatChecker,
apkInstaller = apkInstaller
apkInstaller = apkInstaller,
installRestriction = installRestriction,
)

private val signatureBytes = byteArrayOf(0x01, 0x02, 0x03)
Expand Down Expand Up @@ -132,6 +134,7 @@ internal class ApkBackupRestoreTest : TransportTest() {
val apkPath = slot<String>()
val cacheFiles = slot<List<File>>()

every { installRestriction.isAllowedToInstallApks() } returns true
every { strictContext.cacheDir } returns tmpFile
every { crypto.getNameForApk(salt, packageName, "") } returns name
coEvery { storagePlugin.getInputStream(token, name) } returns inputStream
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ internal class ApkRestoreTest : TransportTest() {
private val legacyStoragePlugin: LegacyStoragePlugin = mockk()
private val splitCompatChecker: ApkSplitCompatibilityChecker = mockk()
private val apkInstaller: ApkInstaller = mockk()
private val installRestriction: InstallRestriction = mockk()

private val apkRestore: ApkRestore = ApkRestore(
context = strictContext,
Expand All @@ -62,6 +63,7 @@ internal class ApkRestoreTest : TransportTest() {
crypto = crypto,
splitCompatChecker = splitCompatChecker,
apkInstaller = apkInstaller,
installRestriction = installRestriction,
)

private val icon: Drawable = mockk()
Expand Down Expand Up @@ -96,6 +98,7 @@ internal class ApkRestoreTest : TransportTest() {
val packageMetadata = packageMetadata.copy(sha256 = getRandomString())
val backup = swapPackages(hashMapOf(packageName to packageMetadata))

every { installRestriction.isAllowedToInstallApks() } returns true
every { strictContext.cacheDir } returns File(tmpDir.toString())
every { crypto.getNameForApk(salt, packageName, "") } returns name
coEvery { storagePlugin.getInputStream(token, name) } returns apkInputStream
Expand All @@ -111,6 +114,7 @@ internal class ApkRestoreTest : TransportTest() {
// change package name to random string
packageInfo.packageName = getRandomString()

every { installRestriction.isAllowedToInstallApks() } returns true
every { strictContext.cacheDir } returns File(tmpDir.toString())
every { crypto.getNameForApk(salt, packageName, "") } returns name
coEvery { storagePlugin.getInputStream(token, name) } returns apkInputStream
Expand All @@ -124,6 +128,7 @@ internal class ApkRestoreTest : TransportTest() {

@Test
fun `test apkInstaller throws exceptions`(@TempDir tmpDir: Path) = runBlocking {
every { installRestriction.isAllowedToInstallApks() } returns true
cacheBaseApkAndGetInfo(tmpDir)
coEvery {
apkInstaller.install(match { it.size == 1 }, packageName, installerName, any())
Expand All @@ -147,6 +152,7 @@ internal class ApkRestoreTest : TransportTest() {
)
}

every { installRestriction.isAllowedToInstallApks() } returns true
cacheBaseApkAndGetInfo(tmpDir)
coEvery {
apkInstaller.install(match { it.size == 1 }, packageName, installerName, any())
Expand All @@ -173,6 +179,7 @@ internal class ApkRestoreTest : TransportTest() {
)
}

every { installRestriction.isAllowedToInstallApks() } returns true
every { strictContext.cacheDir } returns File(tmpDir.toString())
@Suppress("Deprecation")
coEvery {
Expand Down Expand Up @@ -200,6 +207,7 @@ internal class ApkRestoreTest : TransportTest() {
val willFail = Random.nextBoolean()
val isSystemApp = Random.nextBoolean()

every { installRestriction.isAllowedToInstallApks() } returns true
cacheBaseApkAndGetInfo(tmpDir)
every { storagePlugin.providerPackageName } returns storageProviderPackageName

Expand Down Expand Up @@ -274,6 +282,7 @@ internal class ApkRestoreTest : TransportTest() {
)
)

every { installRestriction.isAllowedToInstallApks() } returns true
// cache APK and get icon as well as app name
cacheBaseApkAndGetInfo(tmpDir)

Expand All @@ -296,6 +305,7 @@ internal class ApkRestoreTest : TransportTest() {
splits = listOf(ApkSplit(splitName, Random.nextLong(), getRandomBase64(23)))
)

every { installRestriction.isAllowedToInstallApks() } returns true
// cache APK and get icon as well as app name
cacheBaseApkAndGetInfo(tmpDir)

Expand All @@ -321,6 +331,7 @@ internal class ApkRestoreTest : TransportTest() {
splits = listOf(ApkSplit(splitName, Random.nextLong(), sha256))
)

every { installRestriction.isAllowedToInstallApks() } returns true
// cache APK and get icon as well as app name
cacheBaseApkAndGetInfo(tmpDir)

Expand Down Expand Up @@ -348,6 +359,7 @@ internal class ApkRestoreTest : TransportTest() {
)
)

every { installRestriction.isAllowedToInstallApks() } returns true
// cache APK and get icon as well as app name
cacheBaseApkAndGetInfo(tmpDir)

Expand Down Expand Up @@ -387,6 +399,7 @@ internal class ApkRestoreTest : TransportTest() {

@Test
fun `storage provider app does not get reinstalled`(@TempDir tmpDir: Path) = runBlocking {
every { installRestriction.isAllowedToInstallApks() } returns true
// set the storage provider package name to match our current package name,
// and ensure that the current package is therefore skipped.
every { storagePlugin.providerPackageName } returns packageName
Expand All @@ -406,6 +419,24 @@ internal class ApkRestoreTest : TransportTest() {
}
}

@Test
fun `no apks get installed when blocked by policy`(@TempDir tmpDir: Path) = runBlocking {
every { installRestriction.isAllowedToInstallApks() } returns false
every { storagePlugin.providerPackageName } returns storageProviderPackageName

apkRestore.restore(backup).collectIndexed { i, value ->
when (i) {
0 -> {
// single package fails without attempting to install it
assertEquals(1, value.total)
assertEquals(FAILED, value[packageName].state)
assertTrue(value.isFinished)
}
else -> fail("more values emitted")
}
}
}

private fun swapPackages(packageMetadataMap: PackageMetadataMap): RestorableBackup {
val metadata = metadata.copy(packageMetadataMap = packageMetadataMap)
return backup.copy(backupMetadata = metadata)
Expand Down

0 comments on commit 6dfc058

Please sign in to comment.