diff --git a/app/src/main/java/com/stevesoltys/seedvault/repo/RepoModule.kt b/app/src/main/java/com/stevesoltys/seedvault/repo/RepoModule.kt index 948f11050..6a0397236 100644 --- a/app/src/main/java/com/stevesoltys/seedvault/repo/RepoModule.kt +++ b/app/src/main/java/com/stevesoltys/seedvault/repo/RepoModule.kt @@ -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()) } } diff --git a/app/src/main/java/com/stevesoltys/seedvault/repo/SnapshotCreator.kt b/app/src/main/java/com/stevesoltys/seedvault/repo/SnapshotCreator.kt index d24ee5ab0..9429a8965 100644 --- a/app/src/main/java/com/stevesoltys/seedvault/repo/SnapshotCreator.kt +++ b/app/src/main/java/com/stevesoltys/seedvault/repo/SnapshotCreator.kt @@ -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 @@ -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 { } @@ -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() - 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() + 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) + } } } diff --git a/app/src/main/java/com/stevesoltys/seedvault/repo/SnapshotCreatorFactory.kt b/app/src/main/java/com/stevesoltys/seedvault/repo/SnapshotCreatorFactory.kt index ac2abd4f0..e55f3727f 100644 --- a/app/src/main/java/com/stevesoltys/seedvault/repo/SnapshotCreatorFactory.kt +++ b/app/src/main/java/com/stevesoltys/seedvault/repo/SnapshotCreatorFactory.kt @@ -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 /** @@ -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) } diff --git a/app/src/main/java/com/stevesoltys/seedvault/transport/backup/BackupCoordinator.kt b/app/src/main/java/com/stevesoltys/seedvault/transport/backup/BackupCoordinator.kt index 4172d39df..f58bc275b 100644 --- a/app/src/main/java/com/stevesoltys/seedvault/transport/backup/BackupCoordinator.kt +++ b/app/src/main/java/com/stevesoltys/seedvault/transport/backup/BackupCoordinator.kt @@ -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() diff --git a/app/src/main/java/com/stevesoltys/seedvault/transport/backup/BackupTransportMonitor.kt b/app/src/main/java/com/stevesoltys/seedvault/transport/backup/BackupTransportMonitor.kt index 53fc0f9f8..666753fef 100644 --- a/app/src/main/java/com/stevesoltys/seedvault/transport/backup/BackupTransportMonitor.kt +++ b/app/src/main/java/com/stevesoltys/seedvault/transport/backup/BackupTransportMonitor.kt @@ -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) } } diff --git a/app/src/test/java/com/stevesoltys/seedvault/repo/SnapshotCreatorTest.kt b/app/src/test/java/com/stevesoltys/seedvault/repo/SnapshotCreatorTest.kt index 4592778c7..0414a9d33 100644 --- a/app/src/test/java/com/stevesoltys/seedvault/repo/SnapshotCreatorTest.kt +++ b/app/src/test/java/com/stevesoltys/seedvault/repo/SnapshotCreatorTest.kt @@ -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 @@ -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() @@ -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 { diff --git a/app/src/test/java/com/stevesoltys/seedvault/transport/backup/BackupCreationTest.kt b/app/src/test/java/com/stevesoltys/seedvault/transport/backup/BackupCreationTest.kt index d04c64a47..93d18c168 100644 --- a/app/src/test/java/com/stevesoltys/seedvault/transport/backup/BackupCreationTest.kt +++ b/app/src/test/java/com/stevesoltys/seedvault/transport/backup/BackupCreationTest.kt @@ -66,8 +66,13 @@ internal class BackupCreationTest : BackupTest() { private val backupReceiver = BackupReceiver(blobCache, blobCreator, cryptoImpl) private val appBackupManager = mockk() private val packageService = mockk() - 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() private val db = TestKvDbManager()