Disable auto-restore during install, if it was on
This commit is contained in:
parent
09074c5dff
commit
570850aa51
4 changed files with 88 additions and 3 deletions
|
@ -5,11 +5,13 @@
|
|||
|
||||
package com.stevesoltys.seedvault.restore.install
|
||||
|
||||
import android.app.backup.IBackupManager
|
||||
import android.content.Context
|
||||
import android.content.pm.PackageManager
|
||||
import android.content.pm.PackageManager.GET_SIGNATURES
|
||||
import android.content.pm.PackageManager.GET_SIGNING_CERTIFICATES
|
||||
import android.util.Log
|
||||
import com.stevesoltys.seedvault.BackupStateManager
|
||||
import com.stevesoltys.seedvault.MAGIC_PACKAGE_MANAGER
|
||||
import com.stevesoltys.seedvault.crypto.Crypto
|
||||
import com.stevesoltys.seedvault.metadata.ApkSplit
|
||||
|
@ -38,6 +40,8 @@ private val TAG = ApkRestore::class.java.simpleName
|
|||
|
||||
internal class ApkRestore(
|
||||
private val context: Context,
|
||||
private val backupManager: IBackupManager,
|
||||
private val backupStateManager: BackupStateManager,
|
||||
private val pluginManager: StoragePluginManager,
|
||||
@Suppress("Deprecation")
|
||||
private val legacyStoragePlugin: LegacyStoragePlugin,
|
||||
|
@ -81,9 +85,24 @@ internal class ApkRestore(
|
|||
return
|
||||
}
|
||||
mInstallResult.value = InstallResult(packages)
|
||||
val autoRestore = backupStateManager.isAutoRestoreEnabled
|
||||
try {
|
||||
// disable auto-restore before installing apps, if it was enabled before
|
||||
if (autoRestore) backupManager.setAutoRestore(false)
|
||||
reInstallApps(backup, packages.asIterable().reversed())
|
||||
} finally {
|
||||
// re-enable auto-restore, if it was enabled before
|
||||
if (autoRestore) backupManager.setAutoRestore(true)
|
||||
}
|
||||
mInstallResult.update { it.copy(isFinished = true) }
|
||||
}
|
||||
|
||||
private suspend fun reInstallApps(
|
||||
backup: RestorableBackup,
|
||||
packages: List<Map.Entry<String, ApkInstallResult>>,
|
||||
) {
|
||||
// re-install individual packages and emit updates (start from last and work your way up)
|
||||
for ((packageName, apkInstallResult) in packages.asIterable().reversed()) {
|
||||
for ((packageName, apkInstallResult) in packages) {
|
||||
try {
|
||||
if (isInstalled(packageName, apkInstallResult.metadata)) {
|
||||
mInstallResult.update { result ->
|
||||
|
@ -108,7 +127,6 @@ internal class ApkRestore(
|
|||
mInstallResult.update { it.fail(packageName) }
|
||||
}
|
||||
}
|
||||
mInstallResult.update { it.copy(isFinished = true) }
|
||||
}
|
||||
|
||||
@Throws(SecurityException::class)
|
||||
|
|
|
@ -14,7 +14,7 @@ val installModule = module {
|
|||
factory { DeviceInfo(androidContext()) }
|
||||
factory { ApkSplitCompatibilityChecker(get()) }
|
||||
factory {
|
||||
ApkRestore(androidContext(), get(), get(), get(), get(), get()) {
|
||||
ApkRestore(androidContext(), get(), get(), get(), get(), get(), get(), get()) {
|
||||
androidContext().getSystemService(UserManager::class.java)!!.isAllowedToInstallApks()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
|
||||
package com.stevesoltys.seedvault.restore.install
|
||||
|
||||
import android.app.backup.IBackupManager
|
||||
import android.content.Context
|
||||
import android.content.pm.PackageManager
|
||||
import android.content.pm.PackageManager.NameNotFoundException
|
||||
|
@ -12,6 +13,7 @@ import android.content.pm.Signature
|
|||
import android.graphics.drawable.Drawable
|
||||
import android.util.PackageUtils
|
||||
import app.cash.turbine.test
|
||||
import com.stevesoltys.seedvault.BackupStateManager
|
||||
import com.stevesoltys.seedvault.assertReadEquals
|
||||
import com.stevesoltys.seedvault.getRandomString
|
||||
import com.stevesoltys.seedvault.metadata.ApkSplit
|
||||
|
@ -57,6 +59,8 @@ internal class ApkBackupRestoreTest : TransportTest() {
|
|||
}
|
||||
|
||||
private val storagePluginManager: StoragePluginManager = mockk()
|
||||
private val backupManager: IBackupManager = mockk()
|
||||
private val backupStateManager: BackupStateManager = mockk()
|
||||
|
||||
@Suppress("Deprecation")
|
||||
private val legacyStoragePlugin: LegacyStoragePlugin = mockk()
|
||||
|
@ -68,6 +72,8 @@ internal class ApkBackupRestoreTest : TransportTest() {
|
|||
private val apkBackup = ApkBackup(pm, crypto, settingsManager, metadataManager)
|
||||
private val apkRestore: ApkRestore = ApkRestore(
|
||||
context = strictContext,
|
||||
backupManager = backupManager,
|
||||
backupStateManager = backupStateManager,
|
||||
pluginManager = storagePluginManager,
|
||||
legacyStoragePlugin = legacyStoragePlugin,
|
||||
crypto = crypto,
|
||||
|
@ -146,6 +152,7 @@ internal class ApkBackupRestoreTest : TransportTest() {
|
|||
val cacheFiles = slot<List<File>>()
|
||||
|
||||
every { installRestriction.isAllowedToInstallApks() } returns true
|
||||
every { backupStateManager.isAutoRestoreEnabled } returns false
|
||||
every { pm.getPackageInfo(packageName, any<Int>()) } throws NameNotFoundException()
|
||||
every { strictContext.cacheDir } returns tmpFile
|
||||
every { crypto.getNameForApk(salt, packageName, "") } returns name
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
|
||||
package com.stevesoltys.seedvault.restore.install
|
||||
|
||||
import android.app.backup.IBackupManager
|
||||
import android.content.Context
|
||||
import android.content.pm.ApplicationInfo.FLAG_INSTALLED
|
||||
import android.content.pm.ApplicationInfo.FLAG_SYSTEM
|
||||
|
@ -15,6 +16,7 @@ import android.content.pm.PackageManager.NameNotFoundException
|
|||
import android.graphics.drawable.Drawable
|
||||
import app.cash.turbine.TurbineTestContext
|
||||
import app.cash.turbine.test
|
||||
import com.stevesoltys.seedvault.BackupStateManager
|
||||
import com.stevesoltys.seedvault.getRandomBase64
|
||||
import com.stevesoltys.seedvault.getRandomByteArray
|
||||
import com.stevesoltys.seedvault.getRandomString
|
||||
|
@ -32,10 +34,13 @@ import com.stevesoltys.seedvault.restore.install.ApkInstallState.QUEUED
|
|||
import com.stevesoltys.seedvault.restore.install.ApkInstallState.SUCCEEDED
|
||||
import com.stevesoltys.seedvault.transport.TransportTest
|
||||
import com.stevesoltys.seedvault.worker.getSignatures
|
||||
import io.mockk.Runs
|
||||
import io.mockk.coEvery
|
||||
import io.mockk.every
|
||||
import io.mockk.just
|
||||
import io.mockk.mockk
|
||||
import io.mockk.mockkStatic
|
||||
import io.mockk.verifyOrder
|
||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import org.junit.jupiter.api.Assertions
|
||||
|
@ -57,6 +62,8 @@ internal class ApkRestoreTest : TransportTest() {
|
|||
private val strictContext: Context = mockk<Context>().apply {
|
||||
every { packageManager } returns pm
|
||||
}
|
||||
private val backupManager: IBackupManager = mockk()
|
||||
private val backupStateManager: BackupStateManager = mockk()
|
||||
private val storagePluginManager: StoragePluginManager = mockk()
|
||||
private val storagePlugin: StoragePlugin<*> = mockk()
|
||||
private val legacyStoragePlugin: LegacyStoragePlugin = mockk()
|
||||
|
@ -66,6 +73,8 @@ internal class ApkRestoreTest : TransportTest() {
|
|||
|
||||
private val apkRestore: ApkRestore = ApkRestore(
|
||||
context = strictContext,
|
||||
backupManager = backupManager,
|
||||
backupStateManager = backupStateManager,
|
||||
pluginManager = storagePluginManager,
|
||||
legacyStoragePlugin = legacyStoragePlugin,
|
||||
crypto = crypto,
|
||||
|
@ -107,6 +116,7 @@ internal class ApkRestoreTest : TransportTest() {
|
|||
val backup = swapPackages(hashMapOf(packageName to packageMetadata))
|
||||
|
||||
every { installRestriction.isAllowedToInstallApks() } returns true
|
||||
every { backupStateManager.isAutoRestoreEnabled } returns false
|
||||
every { strictContext.cacheDir } returns File(tmpDir.toString())
|
||||
every { crypto.getNameForApk(salt, packageName, "") } returns name
|
||||
coEvery { storagePlugin.getInputStream(token, name) } returns apkInputStream
|
||||
|
@ -131,6 +141,7 @@ internal class ApkRestoreTest : TransportTest() {
|
|||
val backup = swapPackages(hashMapOf(packageName to packageMetadata))
|
||||
|
||||
every { installRestriction.isAllowedToInstallApks() } returns true
|
||||
every { backupStateManager.isAutoRestoreEnabled } returns false
|
||||
every { storagePlugin.providerPackageName } returns storageProviderPackageName
|
||||
every { pm.getPackageInfo(packageName, any<Int>()) } throws NameNotFoundException()
|
||||
|
||||
|
@ -156,6 +167,7 @@ internal class ApkRestoreTest : TransportTest() {
|
|||
val backup = swapPackages(hashMapOf(packageName to packageMetadata))
|
||||
|
||||
every { installRestriction.isAllowedToInstallApks() } returns true
|
||||
every { backupStateManager.isAutoRestoreEnabled } returns false
|
||||
every { storagePlugin.providerPackageName } returns storageProviderPackageName
|
||||
|
||||
val packageInfo: PackageInfo = mockk()
|
||||
|
@ -178,6 +190,7 @@ internal class ApkRestoreTest : TransportTest() {
|
|||
packageInfo.packageName = getRandomString()
|
||||
|
||||
every { installRestriction.isAllowedToInstallApks() } returns true
|
||||
every { backupStateManager.isAutoRestoreEnabled } returns false
|
||||
every { strictContext.cacheDir } returns File(tmpDir.toString())
|
||||
every { crypto.getNameForApk(salt, packageName, "") } returns name
|
||||
coEvery { storagePlugin.getInputStream(token, name) } returns apkInputStream
|
||||
|
@ -194,6 +207,7 @@ internal class ApkRestoreTest : TransportTest() {
|
|||
@Test
|
||||
fun `test apkInstaller throws exceptions`(@TempDir tmpDir: Path) = runBlocking {
|
||||
every { installRestriction.isAllowedToInstallApks() } returns true
|
||||
every { backupStateManager.isAutoRestoreEnabled } returns false
|
||||
every { pm.getPackageInfo(packageName, any<Int>()) } throws NameNotFoundException()
|
||||
cacheBaseApkAndGetInfo(tmpDir)
|
||||
coEvery {
|
||||
|
@ -220,6 +234,7 @@ internal class ApkRestoreTest : TransportTest() {
|
|||
val installResult = InstallResult(packagesMap)
|
||||
|
||||
every { installRestriction.isAllowedToInstallApks() } returns true
|
||||
every { backupStateManager.isAutoRestoreEnabled } returns false
|
||||
every { pm.getPackageInfo(packageName, any<Int>()) } throws NameNotFoundException()
|
||||
cacheBaseApkAndGetInfo(tmpDir)
|
||||
coEvery {
|
||||
|
@ -249,6 +264,7 @@ internal class ApkRestoreTest : TransportTest() {
|
|||
val installResult = InstallResult(packagesMap)
|
||||
|
||||
every { installRestriction.isAllowedToInstallApks() } returns true
|
||||
every { backupStateManager.isAutoRestoreEnabled } returns false
|
||||
every { pm.getPackageInfo(packageName, any<Int>()) } throws NameNotFoundException()
|
||||
every { strictContext.cacheDir } returns File(tmpDir.toString())
|
||||
coEvery {
|
||||
|
@ -274,6 +290,7 @@ internal class ApkRestoreTest : TransportTest() {
|
|||
val packageInfo: PackageInfo = mockk()
|
||||
mockkStatic("com.stevesoltys.seedvault.worker.ApkBackupKt")
|
||||
every { installRestriction.isAllowedToInstallApks() } returns true
|
||||
every { backupStateManager.isAutoRestoreEnabled } returns false
|
||||
every { storagePlugin.providerPackageName } returns storageProviderPackageName
|
||||
every { pm.getPackageInfo(packageName, any<Int>()) } returns packageInfo
|
||||
every { packageInfo.signingInfo.getSignatures() } returns packageMetadata.signatures!!
|
||||
|
@ -302,6 +319,7 @@ internal class ApkRestoreTest : TransportTest() {
|
|||
val packageInfo: PackageInfo = mockk()
|
||||
mockkStatic("com.stevesoltys.seedvault.worker.ApkBackupKt")
|
||||
every { installRestriction.isAllowedToInstallApks() } returns true
|
||||
every { backupStateManager.isAutoRestoreEnabled } returns false
|
||||
every { storagePlugin.providerPackageName } returns storageProviderPackageName
|
||||
every { pm.getPackageInfo(packageName, any<Int>()) } returns packageInfo
|
||||
every { packageInfo.signingInfo.getSignatures() } returns packageMetadata.signatures!!
|
||||
|
@ -341,6 +359,7 @@ internal class ApkRestoreTest : TransportTest() {
|
|||
val packageInfo: PackageInfo = mockk()
|
||||
mockkStatic("com.stevesoltys.seedvault.worker.ApkBackupKt")
|
||||
every { installRestriction.isAllowedToInstallApks() } returns true
|
||||
every { backupStateManager.isAutoRestoreEnabled } returns false
|
||||
every { storagePlugin.providerPackageName } returns storageProviderPackageName
|
||||
every { pm.getPackageInfo(packageName, any<Int>()) } returns packageInfo
|
||||
every { packageInfo.signingInfo.getSignatures() } returns listOf("foobar")
|
||||
|
@ -370,6 +389,7 @@ internal class ApkRestoreTest : TransportTest() {
|
|||
val isSystemApp = Random.nextBoolean()
|
||||
|
||||
every { installRestriction.isAllowedToInstallApks() } returns true
|
||||
every { backupStateManager.isAutoRestoreEnabled } returns false
|
||||
every { pm.getPackageInfo(packageName, any<Int>()) } throws NameNotFoundException()
|
||||
cacheBaseApkAndGetInfo(tmpDir)
|
||||
every { storagePlugin.providerPackageName } returns storageProviderPackageName
|
||||
|
@ -438,6 +458,7 @@ internal class ApkRestoreTest : TransportTest() {
|
|||
)
|
||||
|
||||
every { installRestriction.isAllowedToInstallApks() } returns true
|
||||
every { backupStateManager.isAutoRestoreEnabled } returns false
|
||||
every { pm.getPackageInfo(packageName, any<Int>()) } throws NameNotFoundException()
|
||||
// cache APK and get icon as well as app name
|
||||
cacheBaseApkAndGetInfo(tmpDir)
|
||||
|
@ -464,6 +485,7 @@ internal class ApkRestoreTest : TransportTest() {
|
|||
)
|
||||
|
||||
every { installRestriction.isAllowedToInstallApks() } returns true
|
||||
every { backupStateManager.isAutoRestoreEnabled } returns false
|
||||
every { pm.getPackageInfo(packageName, any<Int>()) } throws NameNotFoundException()
|
||||
// cache APK and get icon as well as app name
|
||||
cacheBaseApkAndGetInfo(tmpDir)
|
||||
|
@ -493,6 +515,7 @@ internal class ApkRestoreTest : TransportTest() {
|
|||
)
|
||||
|
||||
every { installRestriction.isAllowedToInstallApks() } returns true
|
||||
every { backupStateManager.isAutoRestoreEnabled } returns false
|
||||
every { pm.getPackageInfo(packageName, any<Int>()) } throws NameNotFoundException()
|
||||
// cache APK and get icon as well as app name
|
||||
cacheBaseApkAndGetInfo(tmpDir)
|
||||
|
@ -524,6 +547,7 @@ internal class ApkRestoreTest : TransportTest() {
|
|||
)
|
||||
|
||||
every { installRestriction.isAllowedToInstallApks() } returns true
|
||||
every { backupStateManager.isAutoRestoreEnabled } returns false
|
||||
every { pm.getPackageInfo(packageName, any<Int>()) } throws NameNotFoundException()
|
||||
// cache APK and get icon as well as app name
|
||||
cacheBaseApkAndGetInfo(tmpDir)
|
||||
|
@ -566,6 +590,7 @@ internal class ApkRestoreTest : TransportTest() {
|
|||
@Test
|
||||
fun `storage provider app does not get reinstalled`() = runBlocking {
|
||||
every { installRestriction.isAllowedToInstallApks() } returns true
|
||||
every { backupStateManager.isAutoRestoreEnabled } returns false
|
||||
// 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
|
||||
|
@ -592,6 +617,7 @@ internal class ApkRestoreTest : TransportTest() {
|
|||
).also { assertFalse(it.hasApk()) }
|
||||
|
||||
every { installRestriction.isAllowedToInstallApks() } returns true
|
||||
every { backupStateManager.isAutoRestoreEnabled } returns false
|
||||
every { storagePlugin.providerPackageName } returns storageProviderPackageName
|
||||
|
||||
apkRestore.installResult.test {
|
||||
|
@ -608,6 +634,40 @@ internal class ApkRestoreTest : TransportTest() {
|
|||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `auto restore gets turned off, if it was on`(@TempDir tmpDir: Path) = runBlocking {
|
||||
val packagesMap = mapOf(
|
||||
packageName to ApkInstallResult(
|
||||
packageName,
|
||||
state = SUCCEEDED,
|
||||
metadata = PackageMetadata(),
|
||||
)
|
||||
)
|
||||
val installResult = InstallResult(packagesMap)
|
||||
|
||||
every { installRestriction.isAllowedToInstallApks() } returns true
|
||||
every { backupStateManager.isAutoRestoreEnabled } returns true
|
||||
every { storagePlugin.providerPackageName } returns storageProviderPackageName
|
||||
every { backupManager.setAutoRestore(false) } just Runs
|
||||
every { pm.getPackageInfo(packageName, any<Int>()) } throws NameNotFoundException()
|
||||
// cache APK and get icon as well as app name
|
||||
cacheBaseApkAndGetInfo(tmpDir)
|
||||
coEvery {
|
||||
apkInstaller.install(match { it.size == 1 }, packageName, installerName, any())
|
||||
} returns installResult
|
||||
every { backupManager.setAutoRestore(true) } just Runs
|
||||
|
||||
apkRestore.installResult.test {
|
||||
awaitItem() // initial empty state
|
||||
apkRestore.restore(backup)
|
||||
assertQueuedProgressSuccessFinished()
|
||||
}
|
||||
verifyOrder {
|
||||
backupManager.setAutoRestore(false)
|
||||
backupManager.setAutoRestore(true)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `no apks get installed when blocked by policy`() = runBlocking {
|
||||
every { installRestriction.isAllowedToInstallApks() } returns false
|
||||
|
|
Loading…
Reference in a new issue