Skip to content

Commit

Permalink
Try to recover data for force stopped apps from latest snapshot
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
grote committed Oct 7, 2024
1 parent 1c3af13 commit d2daf25
Show file tree
Hide file tree
Showing 5 changed files with 51 additions and 5 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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)) {
Expand All @@ -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
}

Expand All @@ -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)
}
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand Down Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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,
Expand Down Expand Up @@ -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,
)
}
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ val workerModule = module {
single {
ApkBackupManager(
context = androidContext(),
appBackupManager = get(),
settingsManager = get(),
snapshotManager = get(),
metadataManager = get(),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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()
Expand All @@ -43,6 +46,7 @@ internal class ApkBackupManagerTest : TransportTest() {

private val apkBackupManager = ApkBackupManager(
context = context,
appBackupManager = appBackupManager,
settingsManager = settingsManager,
snapshotManager = snapshotManager,
metadataManager = metadataManager,
Expand All @@ -53,16 +57,20 @@ 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
fun `Package state of app that is not stopped gets recorded as not-allowed`() = runBlocking {
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()

Expand All @@ -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()

Expand Down Expand Up @@ -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()

Expand All @@ -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()

Expand Down Expand Up @@ -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()

Expand Down

0 comments on commit d2daf25

Please sign in to comment.