Try to recover data for force stopped apps from latest snapshot

The system doesn't allow us to backup forced stopped apps, but if we had data for them once, we can at least carry it along.
This commit is contained in:
Torsten Grote 2024-10-03 12:09:06 -03:00
parent c09ea7c075
commit 682afa1c69
No known key found for this signature in database
GPG key ID: 3E5F77D92CF891FF
5 changed files with 51 additions and 5 deletions

View file

@ -114,8 +114,10 @@ internal class SnapshotCreator(
* 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.
*
* @param warnNoData log a warning, if [snapshot] had no data for the given [packageName].
*/
fun onNoDataInCurrentRun(snapshot: Snapshot, packageName: String) {
fun onNoDataInCurrentRun(snapshot: Snapshot, packageName: String, isStopped: Boolean = false) {
log.info { "onKvPackageNotChanged(${snapshot.token}, $packageName)" }
if (appBuilderMap.containsKey(packageName)) {
@ -125,7 +127,9 @@ internal class SnapshotCreator(
}
val app = snapshot.appsMap[packageName]
if (app == null) {
log.error { " No changed data for $packageName, but we had no data for it" }
if (!isStopped) log.error {
" No changed data for $packageName, but we had no data for it"
}
return
}
@ -145,9 +149,11 @@ internal class SnapshotCreator(
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)
// 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)
}
}
/**

View file

@ -5,6 +5,7 @@
package com.stevesoltys.seedvault.settings
import android.annotation.SuppressLint
import android.content.Intent
import android.net.Uri
import android.provider.Settings.ACTION_APPLICATION_DETAILS_SETTINGS
@ -24,6 +25,7 @@ import androidx.recyclerview.widget.RecyclerView
import androidx.recyclerview.widget.RecyclerView.Adapter
import androidx.recyclerview.widget.RecyclerView.NO_POSITION
import com.stevesoltys.seedvault.R
import com.stevesoltys.seedvault.ui.AppBackupState.FAILED_WAS_STOPPED
import com.stevesoltys.seedvault.ui.AppBackupState.SUCCEEDED
import com.stevesoltys.seedvault.ui.AppViewHolder
import com.stevesoltys.seedvault.ui.toRelativeTime
@ -122,7 +124,17 @@ internal class AppStatusAdapter(private val toggleListener: AppStatusToggleListe
" (${formatShortFileSize(v.context, item.size)})"
}
appInfo.visibility = VISIBLE
} else if (item.status == FAILED_WAS_STOPPED && item.time > 0) {
@SuppressLint("SetTextI18n")
appInfo.text = if (item.size == null) {
item.time.toRelativeTime(context).toString()
} else {
item.time.toRelativeTime(context).toString() +
" (${formatShortFileSize(v.context, item.size)})"
} + "\n${item.status.getBackupText(context)}"
appInfo.visibility = VISIBLE
}
// setState() above sets appInfo state for other cases already
checkBox.visibility = INVISIBLE
}
// show disabled items differently

View file

@ -11,6 +11,7 @@ import android.util.Log
import com.stevesoltys.seedvault.metadata.MetadataManager
import com.stevesoltys.seedvault.metadata.PackageState.NOT_ALLOWED
import com.stevesoltys.seedvault.metadata.PackageState.WAS_STOPPED
import com.stevesoltys.seedvault.repo.AppBackupManager
import com.stevesoltys.seedvault.repo.SnapshotManager
import com.stevesoltys.seedvault.settings.SettingsManager
import com.stevesoltys.seedvault.transport.backup.PackageService
@ -22,6 +23,7 @@ import java.io.IOException
internal class ApkBackupManager(
private val context: Context,
private val appBackupManager: AppBackupManager,
private val settingsManager: SettingsManager,
private val snapshotManager: SnapshotManager,
private val metadataManager: MetadataManager,
@ -84,6 +86,15 @@ internal class ApkBackupManager(
} catch (e: IOException) {
Log.e(TAG, "Error storing new metadata for $packageName: ", e)
}
// see if there's data in latest snapshot for this app and re-use it
// this can be helpful for backing up recently STOPPED apps
snapshotManager.latestSnapshot?.let { snapshot ->
appBackupManager.snapshotCreator?.onNoDataInCurrentRun(
snapshot = snapshot,
packageName = packageName,
isStopped = true,
)
}
}
}

View file

@ -38,6 +38,7 @@ val workerModule = module {
single {
ApkBackupManager(
context = androidContext(),
appBackupManager = get(),
settingsManager = get(),
snapshotManager = get(),
metadataManager = get(),

View file

@ -15,6 +15,8 @@ import com.stevesoltys.seedvault.metadata.PackageMetadata
import com.stevesoltys.seedvault.metadata.PackageState.NOT_ALLOWED
import com.stevesoltys.seedvault.metadata.PackageState.UNKNOWN_ERROR
import com.stevesoltys.seedvault.metadata.PackageState.WAS_STOPPED
import com.stevesoltys.seedvault.repo.AppBackupManager
import com.stevesoltys.seedvault.repo.SnapshotCreator
import com.stevesoltys.seedvault.repo.SnapshotManager
import com.stevesoltys.seedvault.transport.TransportTest
import com.stevesoltys.seedvault.transport.backup.PackageService
@ -33,6 +35,7 @@ import org.junit.jupiter.api.Test
internal class ApkBackupManagerTest : TransportTest() {
private val appBackupManager: AppBackupManager = mockk()
private val snapshotManager: SnapshotManager = mockk()
private val packageService: PackageService = mockk()
private val apkBackup: ApkBackup = mockk()
@ -43,6 +46,7 @@ internal class ApkBackupManagerTest : TransportTest() {
private val apkBackupManager = ApkBackupManager(
context = context,
appBackupManager = appBackupManager,
settingsManager = settingsManager,
snapshotManager = snapshotManager,
metadataManager = metadataManager,
@ -53,9 +57,11 @@ internal class ApkBackupManagerTest : TransportTest() {
)
private val packageMetadata: PackageMetadata = mockk()
private val snapshotCreator: SnapshotCreator = mockk()
init {
every { backendManager.backend } returns backend
every { appBackupManager.snapshotCreator } returns snapshotCreator
}
@Test
@ -63,6 +69,8 @@ internal class ApkBackupManagerTest : TransportTest() {
every { nm.onAppsNotBackedUp() } just Runs
every { packageService.notBackedUpPackages } returns listOf(packageInfo)
every { settingsManager.isBackupEnabled(packageInfo.packageName) } returns true
every { snapshotManager.latestSnapshot } returns snapshot
every { snapshotCreator.onNoDataInCurrentRun(snapshot, packageName, true) } just Runs
expectUploadIcons()
@ -87,6 +95,8 @@ internal class ApkBackupManagerTest : TransportTest() {
every { nm.onAppsNotBackedUp() } just Runs
every { packageService.notBackedUpPackages } returns listOf(packageInfo)
every { settingsManager.isBackupEnabled(packageInfo.packageName) } returns true
every { snapshotManager.latestSnapshot } returns snapshot
every { snapshotCreator.onNoDataInCurrentRun(snapshot, packageName, true) } just Runs
expectUploadIcons()
@ -117,6 +127,8 @@ internal class ApkBackupManagerTest : TransportTest() {
every { nm.onAppsNotBackedUp() } just Runs
every { packageService.notBackedUpPackages } returns listOf(packageInfo)
every { settingsManager.isBackupEnabled(packageInfo.packageName) } returns true
every { snapshotManager.latestSnapshot } returns snapshot
every { snapshotCreator.onNoDataInCurrentRun(snapshot, packageName, true) } just Runs
expectUploadIcons()
@ -141,6 +153,8 @@ internal class ApkBackupManagerTest : TransportTest() {
every { nm.onAppsNotBackedUp() } just Runs
every { packageService.notBackedUpPackages } returns listOf(packageInfo)
every { settingsManager.isBackupEnabled(packageInfo.packageName) } returns true
every { snapshotManager.latestSnapshot } returns snapshot
every { snapshotCreator.onNoDataInCurrentRun(snapshot, packageName, true) } just Runs
expectUploadIcons()
@ -223,6 +237,8 @@ internal class ApkBackupManagerTest : TransportTest() {
every { nm.onAppsNotBackedUp() } just Runs
every { packageService.notBackedUpPackages } returns listOf(packageInfo)
every { settingsManager.isBackupEnabled(packageInfo.packageName) } returns true
every { snapshotManager.latestSnapshot } returns snapshot
every { snapshotCreator.onNoDataInCurrentRun(snapshot, packageName, true) } just Runs
expectUploadIcons()