Skip to content

Commit

Permalink
Tell system to reinitialize when it refuses to give us data we need
Browse files Browse the repository at this point in the history
Another hacky workaround to fix a hacky workaround.

We had talked to Google about the backup API not getting notified when essential K/V apps like @pm@ don't have new data for backup. The only option they offered is BackupMonitor which gets the information at least, but out of process on another thread.

 So we implemented a hacky workaround where BackupMonitor tells SnapshotCreator to extract backup data from an old snapshot.

 Unfortunately, it is possible that we do a backup run which includes @pm@, but encounters an error later, so the system cancels the entire backup which causes us not to have @pm@ data in a snapshots for re-use. Still, the system thinks we backed up @pm@ and doesn't give us its data.
  • Loading branch information
grote committed Jan 21, 2025
1 parent ea10f1b commit f4b32e4
Show file tree
Hide file tree
Showing 7 changed files with 67 additions and 38 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ val repoModule = module {
val snapshotFolder = File(androidContext().filesDir, FOLDER_SNAPSHOTS)
SnapshotManager(snapshotFolder, get(), get(), get())
}
factory { SnapshotCreatorFactory(androidContext(), get(), get(), get()) }
factory { SnapshotCreatorFactory(androidContext(), get(), get(), get(), get()) }
factory { Pruner(get(), get(), get(), get()) }
single { Checker(get(), get(), get(), get(), get(), get()) }
}
65 changes: 39 additions & 26 deletions app/src/main/java/com/stevesoltys/seedvault/repo/SnapshotCreator.kt
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import com.stevesoltys.seedvault.proto.Snapshot
import com.stevesoltys.seedvault.proto.Snapshot.Apk
import com.stevesoltys.seedvault.proto.Snapshot.App
import com.stevesoltys.seedvault.proto.Snapshot.Blob
import com.stevesoltys.seedvault.transport.backup.BackupInitializer
import com.stevesoltys.seedvault.transport.backup.PackageService
import com.stevesoltys.seedvault.transport.backup.isSystemApp
import io.github.oshai.kotlinlogging.KotlinLogging
Expand All @@ -41,6 +42,7 @@ internal class SnapshotCreator(
private val clock: Clock,
private val packageService: PackageService,
private val metadataManager: MetadataManager,
private val backupInitializer: BackupInitializer,
) {

private val log = KotlinLogging.logger { }
Expand Down Expand Up @@ -115,44 +117,55 @@ internal class SnapshotCreator(
* we try to extract data from the given [snapshot] (ideally we latest we have) and
* add it to the current snapshot under construction.
*
* @param warnNoData log a warning, if [snapshot] had no data for the given [packageName].
* @param isStopped set to true if the app had no data, because it is currently force-stopped.
*/
fun onNoDataInCurrentRun(snapshot: Snapshot, packageName: String, isStopped: Boolean = false) {
log.info { "onKvPackageNotChanged(${snapshot.token}, $packageName)" }
fun onNoDataInCurrentRun(snapshot: Snapshot?, packageName: String, isStopped: Boolean = false) {
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) {
if (!isStopped) log.error {
" No changed data for $packageName, but we had no data for it"
if (snapshot == null) {
log.error { "No latest snapshot! Initializing transport..." }
// Tell the system that it needs to initialize our transport,
// so it stops telling us that it has no data.
// It seems crazy that we can do this mid-backup
val error = { log.error { "Error initializing transport." } }
backupInitializer.initialize(error) {
log.info { "Success initializing transport." }
}
} else {
val app = snapshot.appsMap[packageName]
if (app == null) {
if (!isStopped) log.error {
" No changed data for $packageName, but we had no data for it"
}
return
}
return
}

// get chunkIds from last snapshot
val chunkIds = app.chunkIdsList.hexFromProto() +
app.apk.splitsList.flatMap { it.chunkIdsList }.hexFromProto()
// 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
}
// 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)
// add info to current snapshot
appBuilderMap[packageName] = app.toBuilder()
blobsMap.putAll(blobMap)

// record local metadata if this is not a stopped app
if (!isStopped) {
val packageInfo = PackageInfo().apply { this.packageName = packageName }
metadataManager.onPackageBackedUp(packageInfo, app.type.toBackupType(), app.size)
// record local metadata if this is not a stopped app
if (!isStopped) {
val packageInfo = PackageInfo().apply { this.packageName = packageName }
metadataManager.onPackageBackedUp(packageInfo, app.type.toBackupType(), app.size)
}
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ package com.stevesoltys.seedvault.repo
import android.content.Context
import com.stevesoltys.seedvault.Clock
import com.stevesoltys.seedvault.metadata.MetadataManager
import com.stevesoltys.seedvault.transport.backup.BackupInitializer
import com.stevesoltys.seedvault.transport.backup.PackageService

/**
Expand All @@ -18,7 +19,8 @@ internal class SnapshotCreatorFactory(
private val clock: Clock,
private val packageService: PackageService,
private val metadataManager: MetadataManager,
private val backupInitializer: BackupInitializer,
) {
fun createSnapshotCreator() =
SnapshotCreator(context, clock, packageService, metadataManager)
SnapshotCreator(context, clock, packageService, metadataManager, backupInitializer)
}
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ internal class BackupCoordinator(
private val state = CoordinatorState(
calledInitialize = false,
calledClearBackupData = false,
cancelReason = UNKNOWN_ERROR
cancelReason = UNKNOWN_ERROR,
)
private val launchableSystemApps by lazy {
packageService.launchableSystemApps.map { it.activityInfo.packageName }.toSet()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,11 +33,7 @@ internal class BackupTransportMonitor(
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)
}
val snapshotCreator = appBackupManager.snapshotCreator ?: error("No SnapshotCreator")
snapshotCreator.onNoDataInCurrentRun(snapshot, packageName)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -23,12 +23,14 @@ 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.backup.BackupInitializer
import com.stevesoltys.seedvault.transport.backup.PackageService
import com.stevesoltys.seedvault.worker.BASE_SPLIT
import io.mockk.Runs
import io.mockk.every
import io.mockk.just
import io.mockk.mockk
import io.mockk.verify
import org.junit.Assert.assertEquals
import org.junit.Assert.assertTrue
import org.junit.Test
Expand All @@ -46,7 +48,9 @@ internal class SnapshotCreatorTest : TransportTest() {

private val ctx: Context = ApplicationProvider.getApplicationContext()
private val packageService: PackageService = mockk()
private val snapshotCreator = SnapshotCreator(ctx, clock, packageService, metadataManager)
private val backupInitializer: BackupInitializer = mockk()
private val snapshotCreator =
SnapshotCreator(ctx, clock, packageService, metadataManager, backupInitializer)

init {
every { packageService.launchableSystemApps } returns emptyList()
Expand Down Expand Up @@ -173,6 +177,15 @@ internal class SnapshotCreatorTest : TransportTest() {
}
}

@Test
fun `test onNoDataInCurrentRun re-initializes if no snapshot available`() {
every { backupInitializer.initialize(any(), any()) } just Runs

snapshotCreator.onNoDataInCurrentRun(null, MAGIC_PACKAGE_MANAGER)

verify { backupInitializer.initialize(any(), any()) }
}

@Test
fun `test onNoDataInCurrentRun`() {
val snapshot = snapshot.copy {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -66,8 +66,13 @@ internal class BackupCreationTest : BackupTest() {
private val backupReceiver = BackupReceiver(blobCache, blobCreator, cryptoImpl)
private val appBackupManager = mockk<AppBackupManager>()
private val packageService = mockk<PackageService>()
private val snapshotCreator =
SnapshotCreator(context, clock, packageService, mockk(relaxed = true))
private val snapshotCreator = SnapshotCreator(
context = context,
clock = clock,
packageService = packageService,
metadataManager = mockk(relaxed = true),
backupInitializer = mockk(relaxed = true),
)
private val notificationManager = mockk<BackupNotificationManager>()
private val db = TestKvDbManager()

Expand Down

0 comments on commit f4b32e4

Please sign in to comment.