Merge pull request #502 from chirayudesai/78-skipapk
Skip installing APKs if not allowed by policy
This commit is contained in:
commit
6dfc0583e5
5 changed files with 69 additions and 4 deletions
|
@ -12,6 +12,7 @@ import com.stevesoltys.seedvault.plugins.LegacyStoragePlugin
|
||||||
import com.stevesoltys.seedvault.plugins.StoragePlugin
|
import com.stevesoltys.seedvault.plugins.StoragePlugin
|
||||||
import com.stevesoltys.seedvault.plugins.StoragePluginManager
|
import com.stevesoltys.seedvault.plugins.StoragePluginManager
|
||||||
import com.stevesoltys.seedvault.restore.RestorableBackup
|
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.FAILED_SYSTEM_APP
|
||||||
import com.stevesoltys.seedvault.restore.install.ApkInstallState.IN_PROGRESS
|
import com.stevesoltys.seedvault.restore.install.ApkInstallState.IN_PROGRESS
|
||||||
import com.stevesoltys.seedvault.restore.install.ApkInstallState.QUEUED
|
import com.stevesoltys.seedvault.restore.install.ApkInstallState.QUEUED
|
||||||
|
@ -35,6 +36,7 @@ internal class ApkRestore(
|
||||||
private val crypto: Crypto,
|
private val crypto: Crypto,
|
||||||
private val splitCompatChecker: ApkSplitCompatibilityChecker,
|
private val splitCompatChecker: ApkSplitCompatibilityChecker,
|
||||||
private val apkInstaller: ApkInstaller,
|
private val apkInstaller: ApkInstaller,
|
||||||
|
private val installRestriction: InstallRestriction,
|
||||||
) {
|
) {
|
||||||
|
|
||||||
private val pm = context.packageManager
|
private val pm = context.packageManager
|
||||||
|
@ -47,6 +49,7 @@ internal class ApkRestore(
|
||||||
// Otherwise, it gets killed when we install it, terminating our restoration.
|
// Otherwise, it gets killed when we install it, terminating our restoration.
|
||||||
it.key != storagePlugin.providerPackageName
|
it.key != storagePlugin.providerPackageName
|
||||||
}
|
}
|
||||||
|
val isAllowedToInstallApks = installRestriction.isAllowedToInstallApks()
|
||||||
val total = packages.size
|
val total = packages.size
|
||||||
var progress = 0
|
var progress = 0
|
||||||
|
|
||||||
|
@ -57,11 +60,17 @@ internal class ApkRestore(
|
||||||
installResult[packageName] = ApkInstallResult(
|
installResult[packageName] = ApkInstallResult(
|
||||||
packageName = packageName,
|
packageName = packageName,
|
||||||
progress = progress,
|
progress = progress,
|
||||||
state = QUEUED,
|
state = if (isAllowedToInstallApks) QUEUED else FAILED,
|
||||||
installerPackageName = metadata.installer
|
installerPackageName = metadata.installer
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
if (isAllowedToInstallApks) {
|
||||||
emit(installResult)
|
emit(installResult)
|
||||||
|
} else {
|
||||||
|
installResult.isFinished = true
|
||||||
|
emit(installResult)
|
||||||
|
return@flow
|
||||||
|
}
|
||||||
|
|
||||||
// re-install individual packages and emit updates
|
// re-install individual packages and emit updates
|
||||||
for ((packageName, metadata) in packages) {
|
for ((packageName, metadata) in packages) {
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
package com.stevesoltys.seedvault.restore.install
|
package com.stevesoltys.seedvault.restore.install
|
||||||
|
|
||||||
|
import android.os.UserManager
|
||||||
import org.koin.android.ext.koin.androidContext
|
import org.koin.android.ext.koin.androidContext
|
||||||
import org.koin.dsl.module
|
import org.koin.dsl.module
|
||||||
|
|
||||||
|
@ -7,5 +8,9 @@ val installModule = module {
|
||||||
factory { ApkInstaller(androidContext()) }
|
factory { ApkInstaller(androidContext()) }
|
||||||
factory { DeviceInfo(androidContext()) }
|
factory { DeviceInfo(androidContext()) }
|
||||||
factory { ApkSplitCompatibilityChecker(get()) }
|
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()
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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)
|
||||||
|
}
|
|
@ -52,6 +52,7 @@ internal class ApkBackupRestoreTest : TransportTest() {
|
||||||
private val storagePlugin: StoragePlugin<*> = mockk()
|
private val storagePlugin: StoragePlugin<*> = mockk()
|
||||||
private val splitCompatChecker: ApkSplitCompatibilityChecker = mockk()
|
private val splitCompatChecker: ApkSplitCompatibilityChecker = mockk()
|
||||||
private val apkInstaller: ApkInstaller = mockk()
|
private val apkInstaller: ApkInstaller = mockk()
|
||||||
|
private val installRestriction: InstallRestriction = mockk()
|
||||||
|
|
||||||
private val apkBackup = ApkBackup(pm, crypto, settingsManager, metadataManager)
|
private val apkBackup = ApkBackup(pm, crypto, settingsManager, metadataManager)
|
||||||
private val apkRestore: ApkRestore = ApkRestore(
|
private val apkRestore: ApkRestore = ApkRestore(
|
||||||
|
@ -60,7 +61,8 @@ internal class ApkBackupRestoreTest : TransportTest() {
|
||||||
legacyStoragePlugin = legacyStoragePlugin,
|
legacyStoragePlugin = legacyStoragePlugin,
|
||||||
crypto = crypto,
|
crypto = crypto,
|
||||||
splitCompatChecker = splitCompatChecker,
|
splitCompatChecker = splitCompatChecker,
|
||||||
apkInstaller = apkInstaller
|
apkInstaller = apkInstaller,
|
||||||
|
installRestriction = installRestriction,
|
||||||
)
|
)
|
||||||
|
|
||||||
private val signatureBytes = byteArrayOf(0x01, 0x02, 0x03)
|
private val signatureBytes = byteArrayOf(0x01, 0x02, 0x03)
|
||||||
|
@ -132,6 +134,7 @@ internal class ApkBackupRestoreTest : TransportTest() {
|
||||||
val apkPath = slot<String>()
|
val apkPath = slot<String>()
|
||||||
val cacheFiles = slot<List<File>>()
|
val cacheFiles = slot<List<File>>()
|
||||||
|
|
||||||
|
every { installRestriction.isAllowedToInstallApks() } returns true
|
||||||
every { strictContext.cacheDir } returns tmpFile
|
every { strictContext.cacheDir } returns tmpFile
|
||||||
every { crypto.getNameForApk(salt, packageName, "") } returns name
|
every { crypto.getNameForApk(salt, packageName, "") } returns name
|
||||||
coEvery { storagePlugin.getInputStream(token, name) } returns inputStream
|
coEvery { storagePlugin.getInputStream(token, name) } returns inputStream
|
||||||
|
|
|
@ -54,6 +54,7 @@ internal class ApkRestoreTest : TransportTest() {
|
||||||
private val legacyStoragePlugin: LegacyStoragePlugin = mockk()
|
private val legacyStoragePlugin: LegacyStoragePlugin = mockk()
|
||||||
private val splitCompatChecker: ApkSplitCompatibilityChecker = mockk()
|
private val splitCompatChecker: ApkSplitCompatibilityChecker = mockk()
|
||||||
private val apkInstaller: ApkInstaller = mockk()
|
private val apkInstaller: ApkInstaller = mockk()
|
||||||
|
private val installRestriction: InstallRestriction = mockk()
|
||||||
|
|
||||||
private val apkRestore: ApkRestore = ApkRestore(
|
private val apkRestore: ApkRestore = ApkRestore(
|
||||||
context = strictContext,
|
context = strictContext,
|
||||||
|
@ -62,6 +63,7 @@ internal class ApkRestoreTest : TransportTest() {
|
||||||
crypto = crypto,
|
crypto = crypto,
|
||||||
splitCompatChecker = splitCompatChecker,
|
splitCompatChecker = splitCompatChecker,
|
||||||
apkInstaller = apkInstaller,
|
apkInstaller = apkInstaller,
|
||||||
|
installRestriction = installRestriction,
|
||||||
)
|
)
|
||||||
|
|
||||||
private val icon: Drawable = mockk()
|
private val icon: Drawable = mockk()
|
||||||
|
@ -96,6 +98,7 @@ internal class ApkRestoreTest : TransportTest() {
|
||||||
val packageMetadata = packageMetadata.copy(sha256 = getRandomString())
|
val packageMetadata = packageMetadata.copy(sha256 = getRandomString())
|
||||||
val backup = swapPackages(hashMapOf(packageName to packageMetadata))
|
val backup = swapPackages(hashMapOf(packageName to packageMetadata))
|
||||||
|
|
||||||
|
every { installRestriction.isAllowedToInstallApks() } returns true
|
||||||
every { strictContext.cacheDir } returns File(tmpDir.toString())
|
every { strictContext.cacheDir } returns File(tmpDir.toString())
|
||||||
every { crypto.getNameForApk(salt, packageName, "") } returns name
|
every { crypto.getNameForApk(salt, packageName, "") } returns name
|
||||||
coEvery { storagePlugin.getInputStream(token, name) } returns apkInputStream
|
coEvery { storagePlugin.getInputStream(token, name) } returns apkInputStream
|
||||||
|
@ -111,6 +114,7 @@ internal class ApkRestoreTest : TransportTest() {
|
||||||
// change package name to random string
|
// change package name to random string
|
||||||
packageInfo.packageName = getRandomString()
|
packageInfo.packageName = getRandomString()
|
||||||
|
|
||||||
|
every { installRestriction.isAllowedToInstallApks() } returns true
|
||||||
every { strictContext.cacheDir } returns File(tmpDir.toString())
|
every { strictContext.cacheDir } returns File(tmpDir.toString())
|
||||||
every { crypto.getNameForApk(salt, packageName, "") } returns name
|
every { crypto.getNameForApk(salt, packageName, "") } returns name
|
||||||
coEvery { storagePlugin.getInputStream(token, name) } returns apkInputStream
|
coEvery { storagePlugin.getInputStream(token, name) } returns apkInputStream
|
||||||
|
@ -124,6 +128,7 @@ internal class ApkRestoreTest : TransportTest() {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `test apkInstaller throws exceptions`(@TempDir tmpDir: Path) = runBlocking {
|
fun `test apkInstaller throws exceptions`(@TempDir tmpDir: Path) = runBlocking {
|
||||||
|
every { installRestriction.isAllowedToInstallApks() } returns true
|
||||||
cacheBaseApkAndGetInfo(tmpDir)
|
cacheBaseApkAndGetInfo(tmpDir)
|
||||||
coEvery {
|
coEvery {
|
||||||
apkInstaller.install(match { it.size == 1 }, packageName, installerName, any())
|
apkInstaller.install(match { it.size == 1 }, packageName, installerName, any())
|
||||||
|
@ -147,6 +152,7 @@ internal class ApkRestoreTest : TransportTest() {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
every { installRestriction.isAllowedToInstallApks() } returns true
|
||||||
cacheBaseApkAndGetInfo(tmpDir)
|
cacheBaseApkAndGetInfo(tmpDir)
|
||||||
coEvery {
|
coEvery {
|
||||||
apkInstaller.install(match { it.size == 1 }, packageName, installerName, any())
|
apkInstaller.install(match { it.size == 1 }, packageName, installerName, any())
|
||||||
|
@ -173,6 +179,7 @@ internal class ApkRestoreTest : TransportTest() {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
every { installRestriction.isAllowedToInstallApks() } returns true
|
||||||
every { strictContext.cacheDir } returns File(tmpDir.toString())
|
every { strictContext.cacheDir } returns File(tmpDir.toString())
|
||||||
@Suppress("Deprecation")
|
@Suppress("Deprecation")
|
||||||
coEvery {
|
coEvery {
|
||||||
|
@ -200,6 +207,7 @@ internal class ApkRestoreTest : TransportTest() {
|
||||||
val willFail = Random.nextBoolean()
|
val willFail = Random.nextBoolean()
|
||||||
val isSystemApp = Random.nextBoolean()
|
val isSystemApp = Random.nextBoolean()
|
||||||
|
|
||||||
|
every { installRestriction.isAllowedToInstallApks() } returns true
|
||||||
cacheBaseApkAndGetInfo(tmpDir)
|
cacheBaseApkAndGetInfo(tmpDir)
|
||||||
every { storagePlugin.providerPackageName } returns storageProviderPackageName
|
every { storagePlugin.providerPackageName } returns storageProviderPackageName
|
||||||
|
|
||||||
|
@ -274,6 +282,7 @@ internal class ApkRestoreTest : TransportTest() {
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
every { installRestriction.isAllowedToInstallApks() } returns true
|
||||||
// cache APK and get icon as well as app name
|
// cache APK and get icon as well as app name
|
||||||
cacheBaseApkAndGetInfo(tmpDir)
|
cacheBaseApkAndGetInfo(tmpDir)
|
||||||
|
|
||||||
|
@ -296,6 +305,7 @@ internal class ApkRestoreTest : TransportTest() {
|
||||||
splits = listOf(ApkSplit(splitName, Random.nextLong(), getRandomBase64(23)))
|
splits = listOf(ApkSplit(splitName, Random.nextLong(), getRandomBase64(23)))
|
||||||
)
|
)
|
||||||
|
|
||||||
|
every { installRestriction.isAllowedToInstallApks() } returns true
|
||||||
// cache APK and get icon as well as app name
|
// cache APK and get icon as well as app name
|
||||||
cacheBaseApkAndGetInfo(tmpDir)
|
cacheBaseApkAndGetInfo(tmpDir)
|
||||||
|
|
||||||
|
@ -321,6 +331,7 @@ internal class ApkRestoreTest : TransportTest() {
|
||||||
splits = listOf(ApkSplit(splitName, Random.nextLong(), sha256))
|
splits = listOf(ApkSplit(splitName, Random.nextLong(), sha256))
|
||||||
)
|
)
|
||||||
|
|
||||||
|
every { installRestriction.isAllowedToInstallApks() } returns true
|
||||||
// cache APK and get icon as well as app name
|
// cache APK and get icon as well as app name
|
||||||
cacheBaseApkAndGetInfo(tmpDir)
|
cacheBaseApkAndGetInfo(tmpDir)
|
||||||
|
|
||||||
|
@ -348,6 +359,7 @@ internal class ApkRestoreTest : TransportTest() {
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
every { installRestriction.isAllowedToInstallApks() } returns true
|
||||||
// cache APK and get icon as well as app name
|
// cache APK and get icon as well as app name
|
||||||
cacheBaseApkAndGetInfo(tmpDir)
|
cacheBaseApkAndGetInfo(tmpDir)
|
||||||
|
|
||||||
|
@ -387,6 +399,7 @@ internal class ApkRestoreTest : TransportTest() {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `storage provider app does not get reinstalled`(@TempDir tmpDir: Path) = runBlocking {
|
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,
|
// set the storage provider package name to match our current package name,
|
||||||
// and ensure that the current package is therefore skipped.
|
// and ensure that the current package is therefore skipped.
|
||||||
every { storagePlugin.providerPackageName } returns packageName
|
every { storagePlugin.providerPackageName } returns packageName
|
||||||
|
@ -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 {
|
private fun swapPackages(packageMetadataMap: PackageMetadataMap): RestorableBackup {
|
||||||
val metadata = metadata.copy(packageMetadataMap = packageMetadataMap)
|
val metadata = metadata.copy(packageMetadataMap = packageMetadataMap)
|
||||||
return backup.copy(backupMetadata = metadata)
|
return backup.copy(backupMetadata = metadata)
|
||||||
|
|
Loading…
Reference in a new issue