Use BackupManagerMonitor to handle K/V with no data changed

The fake package manager package is essential for the backup, but when its data doesn't change and we request a normal incremental backup, it doesn't get included, because our transport doesn't even get called for it. Only the BackupMonitor gets a hint that it had no (new?) data via LOG_EVENT_ID_NO_DATA_TO_SEND.
This behavior started with Android 15 that fixed a bug that caused @pm@ to always backup. However, other K/V apps were probably affected before.
This commit is contained in:
Torsten Grote 2024-10-01 17:10:41 -03:00
parent f8451586df
commit c09ea7c075
No known key found for this signature in database
GPG key ID: 3E5F77D92CF891FF
12 changed files with 245 additions and 24 deletions

View file

@ -17,18 +17,26 @@ import android.util.Log.DEBUG
private val TAG = BackupMonitor::class.java.name private val TAG = BackupMonitor::class.java.name
class BackupMonitor : IBackupManagerMonitor.Stub() { open class BackupMonitor : IBackupManagerMonitor.Stub() {
override fun onEvent(bundle: Bundle) { override fun onEvent(bundle: Bundle) {
val id = bundle.getInt(EXTRA_LOG_EVENT_ID) onEvent(
val packageName = bundle.getString(EXTRA_LOG_EVENT_PACKAGE_NAME, "?") id = bundle.getInt(EXTRA_LOG_EVENT_ID),
category = bundle.getInt(EXTRA_LOG_EVENT_CATEGORY),
packageName = bundle.getString(EXTRA_LOG_EVENT_PACKAGE_NAME)
?: error("no package name for $bundle"),
bundle = bundle,
)
}
open fun onEvent(id: Int, category: Int, packageName: String, bundle: Bundle) {
if (id == LOG_EVENT_ID_ERROR_PREFLIGHT) { if (id == LOG_EVENT_ID_ERROR_PREFLIGHT) {
val preflightResult = bundle.getLong(EXTRA_LOG_PREFLIGHT_ERROR, -1) val preflightResult = bundle.getLong(EXTRA_LOG_PREFLIGHT_ERROR, -1)
Log.w(TAG, "Pre-flight error from $packageName: $preflightResult") Log.w(TAG, "Pre-flight error from $packageName: $preflightResult")
} }
if (!Log.isLoggable(TAG, DEBUG)) return if (!Log.isLoggable(TAG, DEBUG)) return
Log.d(TAG, "ID: $id") Log.d(TAG, "ID: $id")
Log.d(TAG, "CATEGORY: " + bundle.getInt(EXTRA_LOG_EVENT_CATEGORY, -1)) Log.d(TAG, "CATEGORY: $category")
Log.d(TAG, "PACKAGE: $packageName") Log.d(TAG, "PACKAGE: $packageName")
} }

View file

@ -121,11 +121,7 @@ data class PackageMetadata(
companion object { companion object {
fun fromSnapshot(app: Snapshot.App) = PackageMetadata( fun fromSnapshot(app: Snapshot.App) = PackageMetadata(
time = app.time, time = app.time,
backupType = when (app.type) { backupType = app.type.toBackupType(),
Snapshot.BackupType.FULL -> BackupType.FULL
Snapshot.BackupType.KV -> BackupType.KV
else -> null
},
name = app.name, name = app.name,
chunkIds = app.chunkIdsList.hexFromProto(), chunkIds = app.chunkIdsList.hexFromProto(),
system = app.system, system = app.system,
@ -153,6 +149,12 @@ data class PackageMetadata(
it.isNotEmpty() it.isNotEmpty()
}, },
) )
fun Snapshot.BackupType.toBackupType() = when (this) {
Snapshot.BackupType.FULL -> BackupType.FULL
Snapshot.BackupType.KV -> BackupType.KV
else -> null
}
} }
val isInternalSystem: Boolean = system && !isLaunchableSystemApp val isInternalSystem: Boolean = system && !isLaunchableSystemApp

View file

@ -53,16 +53,13 @@ internal class MetadataManager(
/** /**
* Call this after a package has been backed up successfully. * Call this after a package has been backed up successfully.
* *
* It updates the packages' metadata * It updates the packages' metadata.
* and writes it encrypted to the given [OutputStream] as well as the internal cache.
*
* Closing the [OutputStream] is the responsibility of the caller.
*/ */
@Synchronized @Synchronized
@Throws(IOException::class) @Throws(IOException::class)
fun onPackageBackedUp( fun onPackageBackedUp(
packageInfo: PackageInfo, packageInfo: PackageInfo,
type: BackupType, type: BackupType?,
size: Long?, size: Long?,
) { ) {
val packageName = packageInfo.packageName val packageName = packageInfo.packageName

View file

@ -16,9 +16,11 @@ import android.provider.Settings
import android.provider.Settings.Secure.ANDROID_ID import android.provider.Settings.Secure.ANDROID_ID
import com.google.protobuf.ByteString import com.google.protobuf.ByteString
import com.stevesoltys.seedvault.Clock import com.stevesoltys.seedvault.Clock
import com.stevesoltys.seedvault.MAGIC_PACKAGE_MANAGER
import com.stevesoltys.seedvault.header.VERSION import com.stevesoltys.seedvault.header.VERSION
import com.stevesoltys.seedvault.metadata.BackupType import com.stevesoltys.seedvault.metadata.BackupType
import com.stevesoltys.seedvault.metadata.MetadataManager import com.stevesoltys.seedvault.metadata.MetadataManager
import com.stevesoltys.seedvault.metadata.PackageMetadata.Companion.toBackupType
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.Apk
import com.stevesoltys.seedvault.proto.Snapshot.App import com.stevesoltys.seedvault.proto.Snapshot.App
@ -28,6 +30,7 @@ import com.stevesoltys.seedvault.transport.backup.isSystemApp
import io.github.oshai.kotlinlogging.KotlinLogging import io.github.oshai.kotlinlogging.KotlinLogging
import org.calyxos.seedvault.core.backends.AppBackupFileType import org.calyxos.seedvault.core.backends.AppBackupFileType
import org.calyxos.seedvault.core.toHexString import org.calyxos.seedvault.core.toHexString
import java.util.concurrent.ConcurrentHashMap
/** /**
* Assembles snapshot information over the course of a single backup run * Assembles snapshot information over the course of a single backup run
@ -43,8 +46,8 @@ internal class SnapshotCreator(
private val log = KotlinLogging.logger { } private val log = KotlinLogging.logger { }
private val snapshotBuilder = Snapshot.newBuilder() private val snapshotBuilder = Snapshot.newBuilder()
private val appBuilderMap = mutableMapOf<String, App.Builder>() private val appBuilderMap = ConcurrentHashMap<String, App.Builder>()
private val blobsMap = mutableMapOf<String, Blob>() private val blobsMap = ConcurrentHashMap<String, Blob>()
private val launchableSystemApps by lazy { private val launchableSystemApps by lazy {
// as we can't ask [PackageInfo] for this, we keep a set of packages around // as we can't ask [PackageInfo] for this, we keep a set of packages around
@ -103,6 +106,50 @@ internal class SnapshotCreator(
metadataManager.onPackageBackedUp(packageInfo, backupType, backupData.size) metadataManager.onPackageBackedUp(packageInfo, backupType, backupData.size)
} }
/**
* Call this when the given [packageName] may not call our transport at all in this run,
* but we need to include data for the package in the current snapshot.
* This may happen for K/V apps like @pm@ that don't call us when their data didn't change.
*
* If we do *not* have data for the given [packageName],
* we try to extract data from the given [snapshot] (ideally we latest we have) and
* add it to the current snapshot under construction.
*/
fun onNoDataInCurrentRun(snapshot: Snapshot, packageName: String) {
log.info { "onKvPackageNotChanged(${snapshot.token}, $packageName)" }
if (appBuilderMap.containsKey(packageName)) {
// the system backs up K/V apps repeatedly, e.g. @pm@
log.info { " Already have data for $packageName in current snapshot, not touching it" }
return
}
val app = snapshot.appsMap[packageName]
if (app == null) {
log.error { " No changed data for $packageName, but we had no data for it" }
return
}
// get chunkIds from last snapshot
val chunkIds = app.chunkIdsList.hexFromProto() +
app.apk.splitsList.flatMap { it.chunkIdsList }.hexFromProto()
// get blobs for chunkIds
val blobMap = mutableMapOf<String, Blob>()
chunkIds.forEach { chunkId ->
val blob = snapshot.blobsMap[chunkId]
if (blob == null) log.error { " No blob for $packageName chunk $chunkId" }
else blobMap[chunkId] = blob
}
// add info to current snapshot
appBuilderMap[packageName] = app.toBuilder()
blobsMap.putAll(blobMap)
// record local metadata
val packageInfo = PackageInfo().apply { this.packageName = packageName }
metadataManager.onPackageBackedUp(packageInfo, app.type.toBackupType(), app.size)
}
/** /**
* Call this after all blobs for the app icons have been saved to the backend. * Call this after all blobs for the app icons have been saved to the backend.
*/ */
@ -134,6 +181,8 @@ internal class SnapshotCreator(
putAllApps(appBuilderMap.mapValues { it.value.build() }) putAllApps(appBuilderMap.mapValues { it.value.build() })
putAllBlobs(this@SnapshotCreator.blobsMap) putAllBlobs(this@SnapshotCreator.blobsMap)
}.build() }.build()
// may as well fail the backup, if @pm@ isn't in it
check(MAGIC_PACKAGE_MANAGER in snapshot.appsMap) { "No metadata for @pm@" }
appBuilderMap.clear() appBuilderMap.clear()
snapshotBuilder.clear() snapshotBuilder.clear()
blobsMap.clear() blobsMap.clear()

View file

@ -39,6 +39,7 @@ internal class SnapshotManager(
* The latest [Snapshot]. May be stale if [onSnapshotsLoaded] has not returned * The latest [Snapshot]. May be stale if [onSnapshotsLoaded] has not returned
* or wasn't called since new snapshots have been created. * or wasn't called since new snapshots have been created.
*/ */
@Volatile
var latestSnapshot: Snapshot? = null var latestSnapshot: Snapshot? = null
private set private set

View file

@ -9,6 +9,7 @@ import org.koin.android.ext.koin.androidContext
import org.koin.dsl.module import org.koin.dsl.module
val backupModule = module { val backupModule = module {
factory { BackupTransportMonitor(get(), get()) }
single { BackupInitializer(get()) } single { BackupInitializer(get()) }
single { InputFactory() } single { InputFactory() }
single { single {

View file

@ -0,0 +1,43 @@
/*
* SPDX-FileCopyrightText: 2024 The Calyx Institute
* SPDX-License-Identifier: Apache-2.0
*/
package com.stevesoltys.seedvault.transport.backup
import android.app.backup.BackupManagerMonitor.LOG_EVENT_CATEGORY_BACKUP_MANAGER_POLICY
import android.app.backup.BackupManagerMonitor.LOG_EVENT_ID_NO_DATA_TO_SEND
import android.os.Bundle
import com.stevesoltys.seedvault.BackupMonitor
import com.stevesoltys.seedvault.repo.AppBackupManager
import com.stevesoltys.seedvault.repo.SnapshotManager
import io.github.oshai.kotlinlogging.KotlinLogging
internal class BackupTransportMonitor(
private val appBackupManager: AppBackupManager,
private val snapshotManager: SnapshotManager,
) : BackupMonitor() {
private val log = KotlinLogging.logger { }
override fun onEvent(id: Int, category: Int, packageName: String, bundle: Bundle) {
super.onEvent(id, category, packageName, bundle)
if (id == LOG_EVENT_ID_NO_DATA_TO_SEND &&
category == LOG_EVENT_CATEGORY_BACKUP_MANAGER_POLICY
) {
sendNoDataChanged(packageName)
}
}
private fun sendNoDataChanged(packageName: String) {
log.info { "sendNoDataChanged($packageName)" }
val snapshot = snapshotManager.latestSnapshot
if (snapshot == null) {
log.error { "No latest snapshot!" }
} else {
val snapshotCreator = appBackupManager.snapshotCreator ?: error("No SnapshotCreator")
snapshotCreator.onNoDataInCurrentRun(snapshot, packageName)
}
}
}

View file

@ -14,6 +14,7 @@ import android.app.backup.BackupTransport.TRANSPORT_OK
import android.content.pm.PackageInfo import android.content.pm.PackageInfo
import android.os.ParcelFileDescriptor import android.os.ParcelFileDescriptor
import android.util.Log import android.util.Log
import com.stevesoltys.seedvault.NO_DATA_END_SENTINEL
import com.stevesoltys.seedvault.repo.BackupData import com.stevesoltys.seedvault.repo.BackupData
import com.stevesoltys.seedvault.repo.BackupReceiver import com.stevesoltys.seedvault.repo.BackupReceiver
import java.io.IOException import java.io.IOException
@ -52,6 +53,8 @@ internal class KVBackup(
else -> Log.i(TAG, "Performing K/V backup for $packageName") else -> Log.i(TAG, "Performing K/V backup for $packageName")
} }
check(state == null) { "Have unexpected state for ${state?.packageInfo?.packageName}" } check(state == null) { "Have unexpected state for ${state?.packageInfo?.packageName}" }
// This fake package name just signals that we've seen all packages without new data
if (packageName == NO_DATA_END_SENTINEL) return TRANSPORT_OK
// initialize state // initialize state
state = KVBackupState(packageInfo = packageInfo, db = dbManager.getDb(packageName)) state = KVBackupState(packageInfo = packageInfo, db = dbManager.getDb(packageName))

View file

@ -59,18 +59,17 @@ internal class PackageService(
logPackages(packages) logPackages(packages)
} }
val eligibleApps = packages.filter(::shouldIncludeAppInBackup).toTypedArray() val eligibleApps = packages.filter(::shouldIncludeAppInBackup).toMutableList()
// log eligible packages // log eligible packages
if (Log.isLoggable(TAG, INFO)) { if (Log.isLoggable(TAG, INFO)) {
Log.i(TAG, "Filtering left ${eligibleApps.size} eligible packages:") Log.i(TAG, "Filtering left ${eligibleApps.size} eligible packages:")
logPackages(eligibleApps.toList()) logPackages(eligibleApps)
} }
// add magic @pm@ package (PACKAGE_MANAGER_SENTINEL) which holds package manager data // add magic @pm@ package (PACKAGE_MANAGER_SENTINEL) which holds package manager data
val packageArray = eligibleApps.toMutableList() eligibleApps.add(0, MAGIC_PACKAGE_MANAGER)
packageArray.add(MAGIC_PACKAGE_MANAGER)
return packageArray return eligibleApps
} }
/** /**

View file

@ -17,7 +17,7 @@ import androidx.core.content.ContextCompat.startForegroundService
import com.stevesoltys.seedvault.settings.SettingsManager import com.stevesoltys.seedvault.settings.SettingsManager
import com.stevesoltys.seedvault.storage.StorageBackupService import com.stevesoltys.seedvault.storage.StorageBackupService
import com.stevesoltys.seedvault.storage.StorageBackupService.Companion.EXTRA_START_APP_BACKUP import com.stevesoltys.seedvault.storage.StorageBackupService.Companion.EXTRA_START_APP_BACKUP
import com.stevesoltys.seedvault.BackupMonitor import com.stevesoltys.seedvault.transport.backup.BackupTransportMonitor
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
import com.stevesoltys.seedvault.ui.notification.NotificationBackupObserver import com.stevesoltys.seedvault.ui.notification.NotificationBackupObserver
@ -38,6 +38,7 @@ internal const val NUM_PACKAGES_PER_TRANSACTION = 100
internal class BackupRequester( internal class BackupRequester(
context: Context, context: Context,
private val backupManager: IBackupManager, private val backupManager: IBackupManager,
private val monitor: BackupTransportMonitor,
val packageService: PackageService, val packageService: PackageService,
) : KoinComponent { ) : KoinComponent {
@ -72,7 +73,6 @@ internal class BackupRequester(
backupRequester = this, backupRequester = this,
requestedPackages = packages.size, requestedPackages = packages.size,
) )
private val monitor = BackupMonitor()
/** /**
* The current package index. * The current package index.

View file

@ -13,6 +13,7 @@ val workerModule = module {
BackupRequester( BackupRequester(
context = androidContext(), context = androidContext(),
backupManager = get(), backupManager = get(),
monitor = get(),
packageService = get(), packageService = get(),
) )
} }

View file

@ -12,12 +12,19 @@ import android.content.pm.ApplicationInfo.FLAG_SYSTEM
import android.content.pm.ResolveInfo import android.content.pm.ResolveInfo
import androidx.test.core.app.ApplicationProvider import androidx.test.core.app.ApplicationProvider
import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.ext.junit.runners.AndroidJUnit4
import com.stevesoltys.seedvault.MAGIC_PACKAGE_MANAGER
import com.stevesoltys.seedvault.TestApp import com.stevesoltys.seedvault.TestApp
import com.stevesoltys.seedvault.header.VERSION import com.stevesoltys.seedvault.header.VERSION
import com.stevesoltys.seedvault.metadata.BackupType import com.stevesoltys.seedvault.metadata.BackupType
import com.stevesoltys.seedvault.metadata.BackupType.KV
import com.stevesoltys.seedvault.proto.Snapshot import com.stevesoltys.seedvault.proto.Snapshot
import com.stevesoltys.seedvault.proto.SnapshotKt.apk
import com.stevesoltys.seedvault.proto.SnapshotKt.app
import com.stevesoltys.seedvault.proto.SnapshotKt.split
import com.stevesoltys.seedvault.proto.copy
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.worker.BASE_SPLIT
import io.mockk.Runs import io.mockk.Runs
import io.mockk.every import io.mockk.every
import io.mockk.just import io.mockk.just
@ -25,6 +32,7 @@ import io.mockk.mockk
import org.junit.Assert.assertEquals import org.junit.Assert.assertEquals
import org.junit.Assert.assertTrue import org.junit.Assert.assertTrue
import org.junit.Test import org.junit.Test
import org.junit.jupiter.api.assertThrows
import org.junit.runner.RunWith import org.junit.runner.RunWith
import org.robolectric.annotation.Config import org.robolectric.annotation.Config
import kotlin.random.Random import kotlin.random.Random
@ -40,12 +48,18 @@ internal class SnapshotCreatorTest : TransportTest() {
private val packageService: PackageService = mockk() private val packageService: PackageService = mockk()
private val snapshotCreator = SnapshotCreator(ctx, clock, packageService, metadataManager) private val snapshotCreator = SnapshotCreator(ctx, clock, packageService, metadataManager)
init {
every { packageService.launchableSystemApps } returns emptyList()
every { metadataManager.onPackageBackedUp(pmPackageInfo, any(), any()) } just Runs
}
@Test @Test
fun `test onApkBackedUp`() { fun `test onApkBackedUp`() {
every { applicationInfo.loadLabel(any()) } returns name every { applicationInfo.loadLabel(any()) } returns name
every { clock.time() } returns token every { clock.time() } returns token
snapshotCreator.onApkBackedUp(packageInfo, apk, blobMap) snapshotCreator.onApkBackedUp(packageInfo, apk, blobMap)
snapshotCreator.onPackageBackedUp(pmPackageInfo, KV, BackupData(emptyList(), emptyMap()))
val s = snapshotCreator.finalizeSnapshot() val s = snapshotCreator.finalizeSnapshot()
assertEquals(apk, s.appsMap[packageName]?.apk) assertEquals(apk, s.appsMap[packageName]?.apk)
@ -72,6 +86,7 @@ internal class SnapshotCreatorTest : TransportTest() {
every { packageService.launchableSystemApps } returns listOf(resolveInfo) every { packageService.launchableSystemApps } returns listOf(resolveInfo)
snapshotCreator.onPackageBackedUp(packageInfo, BackupType.FULL, apkBackupData) snapshotCreator.onPackageBackedUp(packageInfo, BackupType.FULL, apkBackupData)
snapshotCreator.onPackageBackedUp(pmPackageInfo, KV, BackupData(emptyList(), emptyMap()))
val s = snapshotCreator.finalizeSnapshot() val s = snapshotCreator.finalizeSnapshot()
assertEquals(name, s.appsMap[packageName]?.name) assertEquals(name, s.appsMap[packageName]?.name)
@ -94,14 +109,115 @@ internal class SnapshotCreatorTest : TransportTest() {
every { packageService.launchableSystemApps } returns emptyList() every { packageService.launchableSystemApps } returns emptyList()
snapshotCreator.onPackageBackedUp(packageInfo, BackupType.FULL, apkBackupData) snapshotCreator.onPackageBackedUp(packageInfo, BackupType.FULL, apkBackupData)
snapshotCreator.onPackageBackedUp(pmPackageInfo, KV, BackupData(emptyList(), emptyMap()))
snapshotCreator.finalizeSnapshot() snapshotCreator.finalizeSnapshot()
} }
@Test
fun `test onNoDataInCurrentRun is no-op if no data in last snapshot`() {
snapshotCreator.onNoDataInCurrentRun(snapshot, MAGIC_PACKAGE_MANAGER)
every { clock.time() } returns token
// finalizing complains about not having @pm@
val e = assertThrows<IllegalStateException> {
snapshotCreator.finalizeSnapshot()
}
assertTrue(e.message?.contains(MAGIC_PACKAGE_MANAGER) == true)
}
@Test
fun `test onNoDataInCurrentRun doesn't overwrite existing data`() {
val snapshot1 = snapshot.copy {
apps[MAGIC_PACKAGE_MANAGER] = app {
system = true
type = Snapshot.BackupType.KV
size = 42L
chunkIds.addAll(listOf(chunkId1).forProto())
}
blobs.clear()
blobs[chunkId1] = blob1
}
val snapshot2 = snapshot.copy {
apps[MAGIC_PACKAGE_MANAGER] = app {
system = true
type = Snapshot.BackupType.KV
size = 1337L
chunkIds.addAll(listOf(chunkId2).forProto())
}
blobs.clear()
blobs[chunkId2] = blob2
}
every {
metadataManager.onPackageBackedUp(match {
it.packageName == MAGIC_PACKAGE_MANAGER
}, KV, 42L) // doesn't get run for size of snapshot2
} just Runs
// We just call the same method twice for ease of testing,
// but in reality, the existing data could come from other calls.
// Important is that existing data doesn't get replaced with data from old snapshots.
snapshotCreator.onNoDataInCurrentRun(snapshot1, MAGIC_PACKAGE_MANAGER)
snapshotCreator.onNoDataInCurrentRun(snapshot2, MAGIC_PACKAGE_MANAGER)
every { clock.time() } returns token
// finalizing includes @pm@ app and its blobs
snapshotCreator.finalizeSnapshot().also { s ->
// data from snapshot1 is used, not from snapshot2
assertEquals(snapshot1.appsMap[MAGIC_PACKAGE_MANAGER], s.appsMap[MAGIC_PACKAGE_MANAGER])
// only first blob is in map
assertEquals(1, s.blobsMap.size)
assertEquals(blob1, s.blobsMap[chunkId1])
}
}
@Test
fun `test onNoDataInCurrentRun`() {
val snapshot = snapshot.copy {
apps[MAGIC_PACKAGE_MANAGER] = app {
system = true
type = Snapshot.BackupType.KV
size = 42L
chunkIds.addAll(listOf(chunkId1).forProto())
apk = apk { // @pm@ doesn't have an APK, but we just add one for testing
val split = split {
this.name = BASE_SPLIT
this.chunkIds.addAll(listOf(chunkId2).forProto())
}
splits.add(split)
}
}
blobs.clear()
blobs[chunkId1] = blob1
blobs[chunkId2] = blob2
}
every {
metadataManager.onPackageBackedUp(match {
it.packageName == MAGIC_PACKAGE_MANAGER
}, KV, 42L)
} just Runs
snapshotCreator.onNoDataInCurrentRun(snapshot, MAGIC_PACKAGE_MANAGER)
every { clock.time() } returns token
// finalizing includes @pm@ app and its blobs
snapshotCreator.finalizeSnapshot().also { s ->
assertEquals(snapshot.appsMap[MAGIC_PACKAGE_MANAGER], s.appsMap[MAGIC_PACKAGE_MANAGER])
assertEquals(blob1, s.blobsMap[chunkId1])
assertEquals(blob2, s.blobsMap[chunkId2])
}
}
@Test @Test
fun `test onIconsBackedUp`() { fun `test onIconsBackedUp`() {
every { clock.time() } returns token andThen token + 1 every { clock.time() } returns token andThen token + 1
snapshotCreator.onIconsBackedUp(apkBackupData) snapshotCreator.onIconsBackedUp(apkBackupData)
snapshotCreator.onPackageBackedUp(pmPackageInfo, KV, BackupData(emptyList(), emptyMap()))
val s = snapshotCreator.finalizeSnapshot() val s = snapshotCreator.finalizeSnapshot()
assertEquals(apkBackupData.chunkIds.forProto(), s.iconChunkIdsList) assertEquals(apkBackupData.chunkIds.forProto(), s.iconChunkIdsList)
@ -112,6 +228,7 @@ internal class SnapshotCreatorTest : TransportTest() {
fun `test finalize`() { fun `test finalize`() {
every { clock.time() } returns token every { clock.time() } returns token
snapshotCreator.onPackageBackedUp(pmPackageInfo, KV, BackupData(emptyList(), emptyMap()))
val s = snapshotCreator.finalizeSnapshot() val s = snapshotCreator.finalizeSnapshot()
assertEquals(VERSION, s.version.toByte()) assertEquals(VERSION, s.version.toByte())
@ -122,7 +239,7 @@ internal class SnapshotCreatorTest : TransportTest() {
assertEquals(34, s.sdkInt) // as per config above, needs bump once possible assertEquals(34, s.sdkInt) // as per config above, needs bump once possible
assertEquals("unknown", s.androidIncremental) assertEquals("unknown", s.androidIncremental)
assertTrue(s.d2D) assertTrue(s.d2D)
assertEquals(0, s.appsCount) assertEquals(1, s.appsCount)
assertEquals(0, s.iconChunkIdsCount) assertEquals(0, s.iconChunkIdsCount)
assertEquals(emptyMap<String, Snapshot.Blob>(), s.blobsMap) assertEquals(emptyMap<String, Snapshot.Blob>(), s.blobsMap)
} }