Also snapshot unchanged APKs

This commit is contained in:
Torsten Grote 2024-09-11 17:38:20 -03:00
parent cacea886b0
commit 5b567c79a2
No known key found for this signature in database
GPG key ID: 3E5F77D92CF891FF
7 changed files with 111 additions and 42 deletions

View file

@ -14,9 +14,10 @@ import android.util.PackageUtils.computeSha256DigestBytes
import com.stevesoltys.seedvault.MAGIC_PACKAGE_MANAGER import com.stevesoltys.seedvault.MAGIC_PACKAGE_MANAGER
import com.stevesoltys.seedvault.metadata.PackageMetadata import com.stevesoltys.seedvault.metadata.PackageMetadata
import com.stevesoltys.seedvault.proto.Snapshot import com.stevesoltys.seedvault.proto.Snapshot
import com.stevesoltys.seedvault.proto.Snapshot.Apk
import com.stevesoltys.seedvault.proto.Snapshot.Blob
import com.stevesoltys.seedvault.proto.SnapshotKt.split import com.stevesoltys.seedvault.proto.SnapshotKt.split
import com.stevesoltys.seedvault.settings.SettingsManager import com.stevesoltys.seedvault.settings.SettingsManager
import com.stevesoltys.seedvault.transport.SnapshotManager
import com.stevesoltys.seedvault.transport.backup.AppBackupManager import com.stevesoltys.seedvault.transport.backup.AppBackupManager
import com.stevesoltys.seedvault.transport.backup.BackupReceiver import com.stevesoltys.seedvault.transport.backup.BackupReceiver
import com.stevesoltys.seedvault.transport.backup.forProto import com.stevesoltys.seedvault.transport.backup.forProto
@ -35,7 +36,6 @@ internal class ApkBackup(
private val pm: PackageManager, private val pm: PackageManager,
private val backupReceiver: BackupReceiver, private val backupReceiver: BackupReceiver,
private val appBackupManager: AppBackupManager, private val appBackupManager: AppBackupManager,
private val snapshotManager: SnapshotManager,
private val settingsManager: SettingsManager, private val settingsManager: SettingsManager,
) { ) {
@ -50,7 +50,7 @@ internal class ApkBackup(
* @return new [PackageMetadata] if an APK backup was made or null if no backup was made. * @return new [PackageMetadata] if an APK backup was made or null if no backup was made.
*/ */
@Throws(IOException::class) @Throws(IOException::class)
suspend fun backupApkIfNecessary(packageInfo: PackageInfo) { suspend fun backupApkIfNecessary(packageInfo: PackageInfo, latestSnapshot: Snapshot?) {
// do not back up @pm@ // do not back up @pm@
val packageName = packageInfo.packageName val packageName = packageInfo.packageName
if (packageName == MAGIC_PACKAGE_MANAGER) return if (packageName == MAGIC_PACKAGE_MANAGER) return
@ -93,23 +93,33 @@ internal class ApkBackup(
// get info from latest snapshot // get info from latest snapshot
val version = packageInfo.longVersionCode val version = packageInfo.longVersionCode
val oldApk = snapshotManager.latestSnapshot?.appsMap?.get(packageName)?.apk val oldApk = latestSnapshot?.appsMap?.get(packageName)?.apk
val backedUpVersion = oldApk?.versionCode ?: 0L // no version will cause backup val backedUpVersion = oldApk?.versionCode ?: 0L // no version will cause backup
// do not backup if we have the version already and signatures did not change // do not backup if we have the version already and signatures did not change
if (version <= backedUpVersion && !signaturesChanged(oldApk, signatures)) { val needsBackup = version > backedUpVersion || signaturesChanged(oldApk, signatures)
if (!needsBackup && oldApk != null) {
// We could also check if there are new feature module splits to back up,
// but we rely on the app themselves to re-download those, if needed after restore.
Log.d( Log.d(
TAG, "Package $packageName with version $version" + TAG, "Package $packageName with version $version" +
" already has a backup ($backedUpVersion)" + " already has a backup ($backedUpVersion)" +
" with the same signature. Not backing it up." " with the same signature. Not backing it up."
) )
// We could also check if there are new feature module splits to back up, // build up chunkMap from old snapshot
// but we rely on the app themselves to re-download those, if needed after restore. val chunkIds = oldApk.splitsList.flatMap {
it.chunkIdsList.map { chunkId -> chunkId.hexFromProto() }
}
val chunkMap = chunkIds.associateWith { chunkId ->
latestSnapshot.blobsMap[chunkId] ?: error("Missing blob for $chunkId")
}
// important: add old APK to snapshot or it wouldn't be part of backup
snapshotCreator.onApkBackedUp(packageInfo, oldApk, chunkMap)
return return
} }
// builder for Apk object // builder for Apk object
val apkBuilder = Snapshot.Apk.newBuilder().apply { val apkBuilder = Apk.newBuilder().apply {
versionCode = version versionCode = version
pm.getInstallSourceInfo(packageName).installingPackageName?.let { pm.getInstallSourceInfo(packageName).installingPackageName?.let {
// protobuf doesn't support null values // protobuf doesn't support null values
@ -142,12 +152,11 @@ internal class ApkBackup(
} }
val apk = apkBuilder.addAllSplits(splits).build() val apk = apkBuilder.addAllSplits(splits).build()
snapshotCreator.onApkBackedUp(packageInfo, apk, chunkMap) snapshotCreator.onApkBackedUp(packageInfo, apk, chunkMap)
Log.d(TAG, "Backed up new APK of $packageName with version ${packageInfo.versionName}.") Log.d(TAG, "Backed up new APK of $packageName with version ${packageInfo.versionName}.")
} }
private fun signaturesChanged( private fun signaturesChanged(
apk: Snapshot.Apk?, apk: Apk?,
signatures: List<String>, signatures: List<String>,
): Boolean { ): Boolean {
// no signatures counts as them not having changed // no signatures counts as them not having changed
@ -172,7 +181,7 @@ internal class ApkBackup(
@Throws(IOException::class) @Throws(IOException::class)
private suspend fun backupSplitApks( private suspend fun backupSplitApks(
packageInfo: PackageInfo, packageInfo: PackageInfo,
chunkMap: MutableMap<String, Snapshot.Blob>, chunkMap: MutableMap<String, Blob>,
): List<Snapshot.Split> { ): List<Snapshot.Split> {
check(packageInfo.splitNames != null) check(packageInfo.splitNames != null)
// attention: though not documented, splitSourceDirs can be null // attention: though not documented, splitSourceDirs can be null

View file

@ -13,6 +13,7 @@ import com.stevesoltys.seedvault.metadata.MetadataManager
import com.stevesoltys.seedvault.metadata.PackageState.NOT_ALLOWED import com.stevesoltys.seedvault.metadata.PackageState.NOT_ALLOWED
import com.stevesoltys.seedvault.metadata.PackageState.WAS_STOPPED import com.stevesoltys.seedvault.metadata.PackageState.WAS_STOPPED
import com.stevesoltys.seedvault.settings.SettingsManager import com.stevesoltys.seedvault.settings.SettingsManager
import com.stevesoltys.seedvault.transport.SnapshotManager
import com.stevesoltys.seedvault.transport.backup.PackageService import com.stevesoltys.seedvault.transport.backup.PackageService
import com.stevesoltys.seedvault.transport.backup.isStopped import com.stevesoltys.seedvault.transport.backup.isStopped
import com.stevesoltys.seedvault.ui.notification.BackupNotificationManager import com.stevesoltys.seedvault.ui.notification.BackupNotificationManager
@ -22,6 +23,7 @@ import java.io.IOException
internal class ApkBackupManager( internal class ApkBackupManager(
private val context: Context, private val context: Context,
private val settingsManager: SettingsManager, private val settingsManager: SettingsManager,
private val snapshotManager: SnapshotManager,
private val metadataManager: MetadataManager, private val metadataManager: MetadataManager,
private val packageService: PackageService, private val packageService: PackageService,
private val iconManager: IconManager, private val iconManager: IconManager,
@ -99,7 +101,7 @@ internal class ApkBackupManager(
private suspend fun backUpApk(packageInfo: PackageInfo) { private suspend fun backUpApk(packageInfo: PackageInfo) {
val packageName = packageInfo.packageName val packageName = packageInfo.packageName
try { try {
apkBackup.backupApkIfNecessary(packageInfo) apkBackup.backupApkIfNecessary(packageInfo, snapshotManager.latestSnapshot)
} catch (e: IOException) { } catch (e: IOException) {
Log.e(TAG, "Error while writing APK for $packageName", e) Log.e(TAG, "Error while writing APK for $packageName", e)
if (e.isOutOfSpace()) nm.onInsufficientSpaceError() if (e.isOutOfSpace()) nm.onInsufficientSpaceError()

View file

@ -33,7 +33,6 @@ val workerModule = module {
pm = androidContext().packageManager, pm = androidContext().packageManager,
backupReceiver = get(), backupReceiver = get(),
appBackupManager = get(), appBackupManager = get(),
snapshotManager = get(),
settingsManager = get(), settingsManager = get(),
) )
} }
@ -41,6 +40,7 @@ val workerModule = module {
ApkBackupManager( ApkBackupManager(
context = androidContext(), context = androidContext(),
settingsManager = get(), settingsManager = get(),
snapshotManager = get(),
metadataManager = get(), metadataManager = get(),
packageService = get(), packageService = get(),
apkBackup = get(), apkBackup = get(),

View file

@ -82,8 +82,7 @@ internal class ApkBackupRestoreTest : TransportTest() {
private val apkInstaller: ApkInstaller = mockk() private val apkInstaller: ApkInstaller = mockk()
private val installRestriction: InstallRestriction = mockk() private val installRestriction: InstallRestriction = mockk()
private val apkBackup = private val apkBackup = ApkBackup(pm, backupReceiver, appBackupManager, settingsManager)
ApkBackup(pm, backupReceiver, appBackupManager, snapshotManager, settingsManager)
private val apkRestore: ApkRestore = ApkRestore( private val apkRestore: ApkRestore = ApkRestore(
context = strictContext, context = strictContext,
backupManager = backupManager, backupManager = backupManager,
@ -153,7 +152,7 @@ internal class ApkBackupRestoreTest : TransportTest() {
snapshotCreator.onApkBackedUp(packageInfo, any<Snapshot.Apk>(), chunkMap) snapshotCreator.onApkBackedUp(packageInfo, any<Snapshot.Apk>(), chunkMap)
} just Runs } just Runs
apkBackup.backupApkIfNecessary(packageInfo) apkBackup.backupApkIfNecessary(packageInfo, snapshot)
assertArrayEquals(apkBytes, outputStream.toByteArray()) assertArrayEquals(apkBytes, outputStream.toByteArray())
assertArrayEquals(splitBytes, splitOutputStream.toByteArray()) assertArrayEquals(splitBytes, splitOutputStream.toByteArray())

View file

@ -189,7 +189,7 @@ internal class BackupCoordinatorTest : BackupTest() {
coEvery { coEvery {
full.performFullBackup(packageInfo, fileDescriptor, 0) full.performFullBackup(packageInfo, fileDescriptor, 0)
} returns TRANSPORT_OK } returns TRANSPORT_OK
coEvery { apkBackup.backupApkIfNecessary(packageInfo) } just Runs coEvery { apkBackup.backupApkIfNecessary(packageInfo, snapshot) } just Runs
assertEquals(TRANSPORT_OK, backup.performFullBackup(packageInfo, fileDescriptor, 0)) assertEquals(TRANSPORT_OK, backup.performFullBackup(packageInfo, fileDescriptor, 0))
} }
@ -286,7 +286,7 @@ internal class BackupCoordinatorTest : BackupTest() {
} }
private fun expectApkBackupAndMetadataWrite() { private fun expectApkBackupAndMetadataWrite() {
coEvery { apkBackup.backupApkIfNecessary(packageInfo) } just Runs coEvery { apkBackup.backupApkIfNecessary(packageInfo, snapshot) } just Runs
every { metadataManager.onApkBackedUp(any(), packageMetadata) } just Runs every { metadataManager.onApkBackedUp(any(), packageMetadata) } just Runs
} }

View file

@ -15,6 +15,7 @@ import com.stevesoltys.seedvault.metadata.PackageMetadata
import com.stevesoltys.seedvault.metadata.PackageState.NOT_ALLOWED import com.stevesoltys.seedvault.metadata.PackageState.NOT_ALLOWED
import com.stevesoltys.seedvault.metadata.PackageState.UNKNOWN_ERROR import com.stevesoltys.seedvault.metadata.PackageState.UNKNOWN_ERROR
import com.stevesoltys.seedvault.metadata.PackageState.WAS_STOPPED import com.stevesoltys.seedvault.metadata.PackageState.WAS_STOPPED
import com.stevesoltys.seedvault.transport.SnapshotManager
import com.stevesoltys.seedvault.transport.TransportTest import com.stevesoltys.seedvault.transport.TransportTest
import com.stevesoltys.seedvault.transport.backup.PackageService import com.stevesoltys.seedvault.transport.backup.PackageService
import com.stevesoltys.seedvault.ui.notification.BackupNotificationManager import com.stevesoltys.seedvault.ui.notification.BackupNotificationManager
@ -32,6 +33,7 @@ import org.junit.jupiter.api.Test
internal class ApkBackupManagerTest : TransportTest() { internal class ApkBackupManagerTest : TransportTest() {
private val snapshotManager: SnapshotManager = mockk()
private val packageService: PackageService = mockk() private val packageService: PackageService = mockk()
private val apkBackup: ApkBackup = mockk() private val apkBackup: ApkBackup = mockk()
private val iconManager: IconManager = mockk() private val iconManager: IconManager = mockk()
@ -42,6 +44,7 @@ internal class ApkBackupManagerTest : TransportTest() {
private val apkBackupManager = ApkBackupManager( private val apkBackupManager = ApkBackupManager(
context = context, context = context,
settingsManager = settingsManager, settingsManager = settingsManager,
snapshotManager = snapshotManager,
metadataManager = metadataManager, metadataManager = metadataManager,
packageService = packageService, packageService = packageService,
iconManager = iconManager, iconManager = iconManager,
@ -195,14 +198,15 @@ internal class ApkBackupManagerTest : TransportTest() {
every { every {
nm.onApkBackup(notAllowedPackages[0].packageName, any(), 0, notAllowedPackages.size) nm.onApkBackup(notAllowedPackages[0].packageName, any(), 0, notAllowedPackages.size)
} just Runs } just Runs
every { snapshotManager.latestSnapshot } returns snapshot
// no backup needed // no backup needed
coEvery { apkBackup.backupApkIfNecessary(notAllowedPackages[0]) } just Runs coEvery { apkBackup.backupApkIfNecessary(notAllowedPackages[0], snapshot) } just Runs
// update notification for second package // update notification for second package
every { every {
nm.onApkBackup(notAllowedPackages[1].packageName, any(), 1, notAllowedPackages.size) nm.onApkBackup(notAllowedPackages[1].packageName, any(), 1, notAllowedPackages.size)
} just Runs } just Runs
// was backed up, get new packageMetadata // was backed up, get new packageMetadata
coEvery { apkBackup.backupApkIfNecessary(notAllowedPackages[1]) } just Runs coEvery { apkBackup.backupApkIfNecessary(notAllowedPackages[1], snapshot) } just Runs
every { metadataManager.onApkBackedUp(notAllowedPackages[1], packageMetadata) } just Runs every { metadataManager.onApkBackedUp(notAllowedPackages[1], packageMetadata) } just Runs
every { nm.onApkBackupDone() } just Runs every { nm.onApkBackupDone() } just Runs
@ -210,8 +214,8 @@ internal class ApkBackupManagerTest : TransportTest() {
apkBackupManager.backup() apkBackupManager.backup()
coVerify { coVerify {
apkBackup.backupApkIfNecessary(notAllowedPackages[0]) apkBackup.backupApkIfNecessary(notAllowedPackages[0], snapshot)
apkBackup.backupApkIfNecessary(notAllowedPackages[1]) apkBackup.backupApkIfNecessary(notAllowedPackages[1], snapshot)
} }
} }

View file

@ -15,11 +15,11 @@ import android.content.pm.Signature
import android.util.PackageUtils import android.util.PackageUtils
import com.google.protobuf.ByteString.copyFromUtf8 import com.google.protobuf.ByteString.copyFromUtf8
import com.stevesoltys.seedvault.MAGIC_PACKAGE_MANAGER import com.stevesoltys.seedvault.MAGIC_PACKAGE_MANAGER
import com.stevesoltys.seedvault.decodeBase64
import com.stevesoltys.seedvault.getRandomString import com.stevesoltys.seedvault.getRandomString
import com.stevesoltys.seedvault.proto.Snapshot import com.stevesoltys.seedvault.proto.Snapshot
import com.stevesoltys.seedvault.proto.SnapshotKt.app import com.stevesoltys.seedvault.proto.SnapshotKt.app
import com.stevesoltys.seedvault.proto.copy import com.stevesoltys.seedvault.proto.copy
import com.stevesoltys.seedvault.transport.SnapshotManager
import com.stevesoltys.seedvault.transport.backup.AppBackupManager import com.stevesoltys.seedvault.transport.backup.AppBackupManager
import com.stevesoltys.seedvault.transport.backup.BackupData import com.stevesoltys.seedvault.transport.backup.BackupData
import com.stevesoltys.seedvault.transport.backup.BackupReceiver import com.stevesoltys.seedvault.transport.backup.BackupReceiver
@ -27,11 +27,13 @@ import com.stevesoltys.seedvault.transport.backup.BackupTest
import com.stevesoltys.seedvault.transport.backup.SnapshotCreator import com.stevesoltys.seedvault.transport.backup.SnapshotCreator
import io.mockk.Runs import io.mockk.Runs
import io.mockk.coEvery import io.mockk.coEvery
import io.mockk.coVerify
import io.mockk.every import io.mockk.every
import io.mockk.just import io.mockk.just
import io.mockk.mockk import io.mockk.mockk
import io.mockk.mockkStatic import io.mockk.mockkStatic
import io.mockk.slot import io.mockk.slot
import io.mockk.verify
import kotlinx.coroutines.runBlocking import kotlinx.coroutines.runBlocking
import org.junit.jupiter.api.Assertions.assertArrayEquals import org.junit.jupiter.api.Assertions.assertArrayEquals
import org.junit.jupiter.api.Assertions.assertThrows import org.junit.jupiter.api.Assertions.assertThrows
@ -49,11 +51,9 @@ internal class ApkBackupTest : BackupTest() {
private val pm: PackageManager = mockk() private val pm: PackageManager = mockk()
private val backupReceiver: BackupReceiver = mockk() private val backupReceiver: BackupReceiver = mockk()
private val appBackupManager: AppBackupManager = mockk() private val appBackupManager: AppBackupManager = mockk()
private val snapshotManager: SnapshotManager = mockk()
private val snapshotCreator: SnapshotCreator = mockk() private val snapshotCreator: SnapshotCreator = mockk()
private val apkBackup = private val apkBackup = ApkBackup(pm, backupReceiver, appBackupManager, settingsManager)
ApkBackup(pm, backupReceiver, appBackupManager, snapshotManager, settingsManager)
private val signatureBytes = byteArrayOf(0x01, 0x02, 0x03) private val signatureBytes = byteArrayOf(0x01, 0x02, 0x03)
private val signatureHash = byteArrayOf(0x03, 0x02, 0x01) private val signatureHash = byteArrayOf(0x03, 0x02, 0x01)
@ -67,7 +67,7 @@ internal class ApkBackupTest : BackupTest() {
@Test @Test
fun `does not back up @pm@`() = runBlocking { fun `does not back up @pm@`() = runBlocking {
val packageInfo = PackageInfo().apply { packageName = MAGIC_PACKAGE_MANAGER } val packageInfo = PackageInfo().apply { packageName = MAGIC_PACKAGE_MANAGER }
apkBackup.backupApkIfNecessary(packageInfo) apkBackup.backupApkIfNecessary(packageInfo, null)
} }
@Test @Test
@ -75,7 +75,7 @@ internal class ApkBackupTest : BackupTest() {
every { settingsManager.backupApks() } returns false every { settingsManager.backupApks() } returns false
every { settingsManager.isBackupEnabled(any()) } returns true every { settingsManager.isBackupEnabled(any()) } returns true
apkBackup.backupApkIfNecessary(packageInfo) apkBackup.backupApkIfNecessary(packageInfo, null)
} }
@Test @Test
@ -83,7 +83,7 @@ internal class ApkBackupTest : BackupTest() {
every { settingsManager.backupApks() } returns true every { settingsManager.backupApks() } returns true
every { settingsManager.isBackupEnabled(any()) } returns false every { settingsManager.isBackupEnabled(any()) } returns false
apkBackup.backupApkIfNecessary(packageInfo) apkBackup.backupApkIfNecessary(packageInfo, null)
} }
@Test @Test
@ -92,7 +92,7 @@ internal class ApkBackupTest : BackupTest() {
every { settingsManager.isBackupEnabled(any()) } returns true every { settingsManager.isBackupEnabled(any()) } returns true
every { settingsManager.backupApks() } returns true every { settingsManager.backupApks() } returns true
apkBackup.backupApkIfNecessary(packageInfo) apkBackup.backupApkIfNecessary(packageInfo, null)
} }
@Test @Test
@ -101,7 +101,7 @@ internal class ApkBackupTest : BackupTest() {
every { settingsManager.isBackupEnabled(any()) } returns true every { settingsManager.isBackupEnabled(any()) } returns true
every { settingsManager.backupApks() } returns true every { settingsManager.backupApks() } returns true
apkBackup.backupApkIfNecessary(packageInfo) apkBackup.backupApkIfNecessary(packageInfo, null)
} }
@Test @Test
@ -109,13 +109,61 @@ internal class ApkBackupTest : BackupTest() {
packageInfo.applicationInfo!!.flags = FLAG_UPDATED_SYSTEM_APP packageInfo.applicationInfo!!.flags = FLAG_UPDATED_SYSTEM_APP
val apk = apk.copy { versionCode = packageInfo.longVersionCode } val apk = apk.copy { versionCode = packageInfo.longVersionCode }
val app = app { this.apk = apk } val app = app { this.apk = apk }
expectChecks(snapshot.toBuilder().putApps(packageInfo.packageName, app).build()) val s = snapshot.copy { apps.put(packageName, app) }
expectChecks()
every {
snapshotCreator.onApkBackedUp(packageInfo, apk, chunkMap)
} just Runs
apkBackup.backupApkIfNecessary(packageInfo) apkBackup.backupApkIfNecessary(packageInfo, s)
// ensure we are still snapshotting this version
verify {
snapshotCreator.onApkBackedUp(packageInfo, apk, chunkMap)
}
} }
@Test @Test
fun `does back up the same version when signatures changes`() { fun `does back up the same version when signatures changes`(@TempDir tmpDir: Path) =
runBlocking {
val tmpFile = File(tmpDir.toAbsolutePath().toString())
packageInfo.applicationInfo!!.sourceDir = File(tmpFile, "test.apk").apply {
assertTrue(createNewFile())
}.absolutePath
val apk = apk.copy {
versionCode = packageInfo.longVersionCode
signatures[0] = copyFromUtf8("AwIX".decodeBase64())
splits.clear()
splits.add(baseSplit)
}
val app = app { this.apk = apk }
val s = snapshot.copy { apps.put(packageName, app) }
expectChecks()
every {
pm.getInstallSourceInfo(packageInfo.packageName)
} returns InstallSourceInfo(null, null, null, apk.installer)
coEvery { backupReceiver.readFromStream(any()) } just Runs
coEvery { backupReceiver.finalize() } returns apkBackupData
every {
snapshotCreator.onApkBackedUp(packageInfo, match<Snapshot.Apk> {
it.signaturesList != apk.signaturesList
}, apkBackupData.chunkMap)
} just Runs
apkBackup.backupApkIfNecessary(packageInfo, s)
coVerify {
backupReceiver.readFromStream(any())
backupReceiver.finalize()
snapshotCreator.onApkBackedUp(packageInfo, match<Snapshot.Apk> {
it.signaturesList != apk.signaturesList
}, apkBackupData.chunkMap)
}
}
@Test
fun `throws exception when APK doesn't exist`() {
packageInfo.applicationInfo!!.sourceDir = "/tmp/doesNotExist" packageInfo.applicationInfo!!.sourceDir = "/tmp/doesNotExist"
val apk = apk.copy { val apk = apk.copy {
signatures.clear() signatures.clear()
@ -123,14 +171,15 @@ internal class ApkBackupTest : BackupTest() {
versionCode = packageInfo.longVersionCode versionCode = packageInfo.longVersionCode
} }
val app = app { this.apk = apk } val app = app { this.apk = apk }
expectChecks(snapshot.toBuilder().putApps(packageInfo.packageName, app).build()) val s = snapshot.copy { apps.put(packageName, app) }
expectChecks()
every { every {
pm.getInstallSourceInfo(packageInfo.packageName) pm.getInstallSourceInfo(packageInfo.packageName)
} returns InstallSourceInfo(null, null, null, getRandomString()) } returns InstallSourceInfo(null, null, null, getRandomString())
assertThrows(IOException::class.java) { assertThrows(IOException::class.java) {
runBlocking { runBlocking {
apkBackup.backupApkIfNecessary(packageInfo) apkBackup.backupApkIfNecessary(packageInfo, s)
} }
} }
Unit Unit
@ -140,11 +189,10 @@ internal class ApkBackupTest : BackupTest() {
fun `do not accept empty signature`() = runBlocking { fun `do not accept empty signature`() = runBlocking {
every { settingsManager.backupApks() } returns true every { settingsManager.backupApks() } returns true
every { settingsManager.isBackupEnabled(any()) } returns true every { settingsManager.isBackupEnabled(any()) } returns true
every { snapshotManager.latestSnapshot } returns snapshot
every { sigInfo.hasMultipleSigners() } returns false every { sigInfo.hasMultipleSigners() } returns false
every { sigInfo.signingCertificateHistory } returns emptyArray() every { sigInfo.signingCertificateHistory } returns emptyArray()
apkBackup.backupApkIfNecessary(packageInfo) apkBackup.backupApkIfNecessary(packageInfo, snapshot)
} }
@Test @Test
@ -173,8 +221,12 @@ internal class ApkBackupTest : BackupTest() {
}, emptyMap()) }, emptyMap())
} just Runs } just Runs
apkBackup.backupApkIfNecessary(packageInfo) apkBackup.backupApkIfNecessary(packageInfo, snapshot)
assertArrayEquals(apkBytes, apkOutputStream.toByteArray()) assertArrayEquals(apkBytes, apkOutputStream.toByteArray())
coVerify {
backupReceiver.finalize()
}
} }
@Test @Test
@ -230,16 +282,19 @@ internal class ApkBackupTest : BackupTest() {
}, emptyMap()) }, emptyMap())
} just Runs } just Runs
apkBackup.backupApkIfNecessary(packageInfo) apkBackup.backupApkIfNecessary(packageInfo, snapshot)
assertArrayEquals(apkBytes, apkOutputStream.toByteArray()) assertArrayEquals(apkBytes, apkOutputStream.toByteArray())
assertArrayEquals(split1Bytes, split1OutputStream.toByteArray()) assertArrayEquals(split1Bytes, split1OutputStream.toByteArray())
assertArrayEquals(split2Bytes, split2OutputStream.toByteArray()) assertArrayEquals(split2Bytes, split2OutputStream.toByteArray())
coVerify {
backupReceiver.finalize()
}
} }
private fun expectChecks(snapshot: Snapshot = this.snapshot) { private fun expectChecks() {
every { settingsManager.isBackupEnabled(any()) } returns true every { settingsManager.isBackupEnabled(any()) } returns true
every { settingsManager.backupApks() } returns true every { settingsManager.backupApks() } returns true
every { snapshotManager.latestSnapshot } returns snapshot
every { PackageUtils.computeSha256DigestBytes(signatureBytes) } returns signatureHash every { PackageUtils.computeSha256DigestBytes(signatureBytes) } returns signatureHash
every { sigInfo.hasMultipleSigners() } returns false every { sigInfo.hasMultipleSigners() } returns false
every { sigInfo.signingCertificateHistory } returns sigs every { sigInfo.signingCertificateHistory } returns sigs