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], * 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 * we try to extract data from the given [snapshot] (ideally we latest we have) and
* add it to the current snapshot under construction. * 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)" } log.info { "onKvPackageNotChanged(${snapshot.token}, $packageName)" }
if (appBuilderMap.containsKey(packageName)) { if (appBuilderMap.containsKey(packageName)) {
@ -125,7 +127,9 @@ internal class SnapshotCreator(
} }
val app = snapshot.appsMap[packageName] val app = snapshot.appsMap[packageName]
if (app == null) { 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 return
} }
@ -145,10 +149,12 @@ internal class SnapshotCreator(
appBuilderMap[packageName] = app.toBuilder() appBuilderMap[packageName] = app.toBuilder()
blobsMap.putAll(blobMap) blobsMap.putAll(blobMap)
// record local metadata // record local metadata if this is not a stopped app
if (!isStopped) {
val packageInfo = PackageInfo().apply { this.packageName = packageName } val packageInfo = PackageInfo().apply { this.packageName = packageName }
metadataManager.onPackageBackedUp(packageInfo, app.type.toBackupType(), app.size) 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.

View file

@ -5,6 +5,7 @@
package com.stevesoltys.seedvault.settings package com.stevesoltys.seedvault.settings
import android.annotation.SuppressLint
import android.content.Intent import android.content.Intent
import android.net.Uri import android.net.Uri
import android.provider.Settings.ACTION_APPLICATION_DETAILS_SETTINGS 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.Adapter
import androidx.recyclerview.widget.RecyclerView.NO_POSITION import androidx.recyclerview.widget.RecyclerView.NO_POSITION
import com.stevesoltys.seedvault.R 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.AppBackupState.SUCCEEDED
import com.stevesoltys.seedvault.ui.AppViewHolder import com.stevesoltys.seedvault.ui.AppViewHolder
import com.stevesoltys.seedvault.ui.toRelativeTime import com.stevesoltys.seedvault.ui.toRelativeTime
@ -122,7 +124,17 @@ internal class AppStatusAdapter(private val toggleListener: AppStatusToggleListe
" (${formatShortFileSize(v.context, item.size)})" " (${formatShortFileSize(v.context, item.size)})"
} }
appInfo.visibility = VISIBLE 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 checkBox.visibility = INVISIBLE
} }
// show disabled items differently // 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.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.repo.AppBackupManager
import com.stevesoltys.seedvault.repo.SnapshotManager import com.stevesoltys.seedvault.repo.SnapshotManager
import com.stevesoltys.seedvault.settings.SettingsManager import com.stevesoltys.seedvault.settings.SettingsManager
import com.stevesoltys.seedvault.transport.backup.PackageService import com.stevesoltys.seedvault.transport.backup.PackageService
@ -22,6 +23,7 @@ import java.io.IOException
internal class ApkBackupManager( internal class ApkBackupManager(
private val context: Context, private val context: Context,
private val appBackupManager: AppBackupManager,
private val settingsManager: SettingsManager, private val settingsManager: SettingsManager,
private val snapshotManager: SnapshotManager, private val snapshotManager: SnapshotManager,
private val metadataManager: MetadataManager, private val metadataManager: MetadataManager,
@ -84,6 +86,15 @@ internal class ApkBackupManager(
} catch (e: IOException) { } catch (e: IOException) {
Log.e(TAG, "Error storing new metadata for $packageName: ", e) 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 { single {
ApkBackupManager( ApkBackupManager(
context = androidContext(), context = androidContext(),
appBackupManager = get(),
settingsManager = get(), settingsManager = get(),
snapshotManager = get(), snapshotManager = get(),
metadataManager = 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.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.repo.AppBackupManager
import com.stevesoltys.seedvault.repo.SnapshotCreator
import com.stevesoltys.seedvault.repo.SnapshotManager import com.stevesoltys.seedvault.repo.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
@ -33,6 +35,7 @@ import org.junit.jupiter.api.Test
internal class ApkBackupManagerTest : TransportTest() { internal class ApkBackupManagerTest : TransportTest() {
private val appBackupManager: AppBackupManager = mockk()
private val snapshotManager: SnapshotManager = mockk() 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()
@ -43,6 +46,7 @@ internal class ApkBackupManagerTest : TransportTest() {
private val apkBackupManager = ApkBackupManager( private val apkBackupManager = ApkBackupManager(
context = context, context = context,
appBackupManager = appBackupManager,
settingsManager = settingsManager, settingsManager = settingsManager,
snapshotManager = snapshotManager, snapshotManager = snapshotManager,
metadataManager = metadataManager, metadataManager = metadataManager,
@ -53,9 +57,11 @@ internal class ApkBackupManagerTest : TransportTest() {
) )
private val packageMetadata: PackageMetadata = mockk() private val packageMetadata: PackageMetadata = mockk()
private val snapshotCreator: SnapshotCreator = mockk()
init { init {
every { backendManager.backend } returns backend every { backendManager.backend } returns backend
every { appBackupManager.snapshotCreator } returns snapshotCreator
} }
@Test @Test
@ -63,6 +69,8 @@ internal class ApkBackupManagerTest : TransportTest() {
every { nm.onAppsNotBackedUp() } just Runs every { nm.onAppsNotBackedUp() } just Runs
every { packageService.notBackedUpPackages } returns listOf(packageInfo) every { packageService.notBackedUpPackages } returns listOf(packageInfo)
every { settingsManager.isBackupEnabled(packageInfo.packageName) } returns true every { settingsManager.isBackupEnabled(packageInfo.packageName) } returns true
every { snapshotManager.latestSnapshot } returns snapshot
every { snapshotCreator.onNoDataInCurrentRun(snapshot, packageName, true) } just Runs
expectUploadIcons() expectUploadIcons()
@ -87,6 +95,8 @@ internal class ApkBackupManagerTest : TransportTest() {
every { nm.onAppsNotBackedUp() } just Runs every { nm.onAppsNotBackedUp() } just Runs
every { packageService.notBackedUpPackages } returns listOf(packageInfo) every { packageService.notBackedUpPackages } returns listOf(packageInfo)
every { settingsManager.isBackupEnabled(packageInfo.packageName) } returns true every { settingsManager.isBackupEnabled(packageInfo.packageName) } returns true
every { snapshotManager.latestSnapshot } returns snapshot
every { snapshotCreator.onNoDataInCurrentRun(snapshot, packageName, true) } just Runs
expectUploadIcons() expectUploadIcons()
@ -117,6 +127,8 @@ internal class ApkBackupManagerTest : TransportTest() {
every { nm.onAppsNotBackedUp() } just Runs every { nm.onAppsNotBackedUp() } just Runs
every { packageService.notBackedUpPackages } returns listOf(packageInfo) every { packageService.notBackedUpPackages } returns listOf(packageInfo)
every { settingsManager.isBackupEnabled(packageInfo.packageName) } returns true every { settingsManager.isBackupEnabled(packageInfo.packageName) } returns true
every { snapshotManager.latestSnapshot } returns snapshot
every { snapshotCreator.onNoDataInCurrentRun(snapshot, packageName, true) } just Runs
expectUploadIcons() expectUploadIcons()
@ -141,6 +153,8 @@ internal class ApkBackupManagerTest : TransportTest() {
every { nm.onAppsNotBackedUp() } just Runs every { nm.onAppsNotBackedUp() } just Runs
every { packageService.notBackedUpPackages } returns listOf(packageInfo) every { packageService.notBackedUpPackages } returns listOf(packageInfo)
every { settingsManager.isBackupEnabled(packageInfo.packageName) } returns true every { settingsManager.isBackupEnabled(packageInfo.packageName) } returns true
every { snapshotManager.latestSnapshot } returns snapshot
every { snapshotCreator.onNoDataInCurrentRun(snapshot, packageName, true) } just Runs
expectUploadIcons() expectUploadIcons()
@ -223,6 +237,8 @@ internal class ApkBackupManagerTest : TransportTest() {
every { nm.onAppsNotBackedUp() } just Runs every { nm.onAppsNotBackedUp() } just Runs
every { packageService.notBackedUpPackages } returns listOf(packageInfo) every { packageService.notBackedUpPackages } returns listOf(packageInfo)
every { settingsManager.isBackupEnabled(packageInfo.packageName) } returns true every { settingsManager.isBackupEnabled(packageInfo.packageName) } returns true
every { snapshotManager.latestSnapshot } returns snapshot
every { snapshotCreator.onNoDataInCurrentRun(snapshot, packageName, true) } just Runs
expectUploadIcons() expectUploadIcons()