Polish SnapshotCreator and write tests

This commit is contained in:
Torsten Grote 2024-09-16 16:28:11 -03:00
parent 463fc33230
commit 7702fb7bd8
No known key found for this signature in database
GPG key ID: 3E5F77D92CF891FF
8 changed files with 252 additions and 120 deletions

View file

@ -74,7 +74,24 @@ public object SnapshotKt {
}
/**
* <code>string androidId = 4;</code>
* <code>string user = 4;</code>
*/
public var user: kotlin.String
@JvmName("getUser")
get() = _builder.getUser()
@JvmName("setUser")
set(value) {
_builder.setUser(value)
}
/**
* <code>string user = 4;</code>
*/
public fun clearUser() {
_builder.clearUser()
}
/**
* <code>string androidId = 5;</code>
*/
public var androidId: kotlin.String
@JvmName("getAndroidId")
@ -84,14 +101,14 @@ public object SnapshotKt {
_builder.setAndroidId(value)
}
/**
* <code>string androidId = 4;</code>
* <code>string androidId = 5;</code>
*/
public fun clearAndroidId() {
_builder.clearAndroidId()
}
/**
* <code>uint32 sdkInt = 5;</code>
* <code>uint32 sdkInt = 6;</code>
*/
public var sdkInt: kotlin.Int
@JvmName("getSdkInt")
@ -101,14 +118,14 @@ public object SnapshotKt {
_builder.setSdkInt(value)
}
/**
* <code>uint32 sdkInt = 5;</code>
* <code>uint32 sdkInt = 6;</code>
*/
public fun clearSdkInt() {
_builder.clearSdkInt()
}
/**
* <code>string androidIncremental = 6;</code>
* <code>string androidIncremental = 7;</code>
*/
public var androidIncremental: kotlin.String
@JvmName("getAndroidIncremental")
@ -118,14 +135,14 @@ public object SnapshotKt {
_builder.setAndroidIncremental(value)
}
/**
* <code>string androidIncremental = 6;</code>
* <code>string androidIncremental = 7;</code>
*/
public fun clearAndroidIncremental() {
_builder.clearAndroidIncremental()
}
/**
* <code>bool d2d = 7;</code>
* <code>bool d2d = 8;</code>
*/
public var d2D: kotlin.Boolean
@JvmName("getD2D")
@ -135,7 +152,7 @@ public object SnapshotKt {
_builder.setD2D(value)
}
/**
* <code>bool d2d = 7;</code>
* <code>bool d2d = 8;</code>
*/
public fun clearD2D() {
_builder.clearD2D()
@ -148,7 +165,7 @@ public object SnapshotKt {
@kotlin.OptIn(com.google.protobuf.kotlin.OnlyForUseByGeneratedProtoCode::class)
public class AppsProxy private constructor() : com.google.protobuf.kotlin.DslProxy()
/**
* <code>map&lt;string, .com.stevesoltys.seedvault.proto.Snapshot.App&gt; apps = 8;</code>
* <code>map&lt;string, .com.stevesoltys.seedvault.proto.Snapshot.App&gt; apps = 9;</code>
*/
public val apps: com.google.protobuf.kotlin.DslMap<kotlin.String, com.stevesoltys.seedvault.proto.Snapshot.App, AppsProxy>
@kotlin.jvm.JvmSynthetic
@ -157,7 +174,7 @@ public object SnapshotKt {
_builder.getAppsMap()
)
/**
* <code>map&lt;string, .com.stevesoltys.seedvault.proto.Snapshot.App&gt; apps = 8;</code>
* <code>map&lt;string, .com.stevesoltys.seedvault.proto.Snapshot.App&gt; apps = 9;</code>
*/
@JvmName("putApps")
public fun com.google.protobuf.kotlin.DslMap<kotlin.String, com.stevesoltys.seedvault.proto.Snapshot.App, AppsProxy>
@ -165,7 +182,7 @@ public object SnapshotKt {
_builder.putApps(key, value)
}
/**
* <code>map&lt;string, .com.stevesoltys.seedvault.proto.Snapshot.App&gt; apps = 8;</code>
* <code>map&lt;string, .com.stevesoltys.seedvault.proto.Snapshot.App&gt; apps = 9;</code>
*/
@kotlin.jvm.JvmSynthetic
@JvmName("setApps")
@ -175,7 +192,7 @@ public object SnapshotKt {
put(key, value)
}
/**
* <code>map&lt;string, .com.stevesoltys.seedvault.proto.Snapshot.App&gt; apps = 8;</code>
* <code>map&lt;string, .com.stevesoltys.seedvault.proto.Snapshot.App&gt; apps = 9;</code>
*/
@kotlin.jvm.JvmSynthetic
@JvmName("removeApps")
@ -184,7 +201,7 @@ public object SnapshotKt {
_builder.removeApps(key)
}
/**
* <code>map&lt;string, .com.stevesoltys.seedvault.proto.Snapshot.App&gt; apps = 8;</code>
* <code>map&lt;string, .com.stevesoltys.seedvault.proto.Snapshot.App&gt; apps = 9;</code>
*/
@kotlin.jvm.JvmSynthetic
@JvmName("putAllApps")
@ -193,7 +210,7 @@ public object SnapshotKt {
_builder.putAllApps(map)
}
/**
* <code>map&lt;string, .com.stevesoltys.seedvault.proto.Snapshot.App&gt; apps = 8;</code>
* <code>map&lt;string, .com.stevesoltys.seedvault.proto.Snapshot.App&gt; apps = 9;</code>
*/
@kotlin.jvm.JvmSynthetic
@JvmName("clearApps")
@ -209,7 +226,7 @@ public object SnapshotKt {
@kotlin.OptIn(com.google.protobuf.kotlin.OnlyForUseByGeneratedProtoCode::class)
public class IconChunkIdsProxy private constructor() : com.google.protobuf.kotlin.DslProxy()
/**
* <code>repeated bytes iconChunkIds = 9;</code>
* <code>repeated bytes iconChunkIds = 10;</code>
*/
public val iconChunkIds: com.google.protobuf.kotlin.DslList<com.google.protobuf.ByteString, IconChunkIdsProxy>
@kotlin.jvm.JvmSynthetic
@ -217,7 +234,7 @@ public object SnapshotKt {
_builder.getIconChunkIdsList()
)
/**
* <code>repeated bytes iconChunkIds = 9;</code>
* <code>repeated bytes iconChunkIds = 10;</code>
* @param value The iconChunkIds to add.
*/
@kotlin.jvm.JvmSynthetic
@ -225,7 +242,7 @@ public object SnapshotKt {
public fun com.google.protobuf.kotlin.DslList<com.google.protobuf.ByteString, IconChunkIdsProxy>.add(value: com.google.protobuf.ByteString) {
_builder.addIconChunkIds(value)
}/**
* <code>repeated bytes iconChunkIds = 9;</code>
* <code>repeated bytes iconChunkIds = 10;</code>
* @param value The iconChunkIds to add.
*/
@kotlin.jvm.JvmSynthetic
@ -234,7 +251,7 @@ public object SnapshotKt {
public inline operator fun com.google.protobuf.kotlin.DslList<com.google.protobuf.ByteString, IconChunkIdsProxy>.plusAssign(value: com.google.protobuf.ByteString) {
add(value)
}/**
* <code>repeated bytes iconChunkIds = 9;</code>
* <code>repeated bytes iconChunkIds = 10;</code>
* @param values The iconChunkIds to add.
*/
@kotlin.jvm.JvmSynthetic
@ -242,7 +259,7 @@ public object SnapshotKt {
public fun com.google.protobuf.kotlin.DslList<com.google.protobuf.ByteString, IconChunkIdsProxy>.addAll(values: kotlin.collections.Iterable<com.google.protobuf.ByteString>) {
_builder.addAllIconChunkIds(values)
}/**
* <code>repeated bytes iconChunkIds = 9;</code>
* <code>repeated bytes iconChunkIds = 10;</code>
* @param values The iconChunkIds to add.
*/
@kotlin.jvm.JvmSynthetic
@ -251,7 +268,7 @@ public object SnapshotKt {
public inline operator fun com.google.protobuf.kotlin.DslList<com.google.protobuf.ByteString, IconChunkIdsProxy>.plusAssign(values: kotlin.collections.Iterable<com.google.protobuf.ByteString>) {
addAll(values)
}/**
* <code>repeated bytes iconChunkIds = 9;</code>
* <code>repeated bytes iconChunkIds = 10;</code>
* @param index The index to set the value at.
* @param value The iconChunkIds to set.
*/
@ -260,7 +277,7 @@ public object SnapshotKt {
public operator fun com.google.protobuf.kotlin.DslList<com.google.protobuf.ByteString, IconChunkIdsProxy>.set(index: kotlin.Int, value: com.google.protobuf.ByteString) {
_builder.setIconChunkIds(index, value)
}/**
* <code>repeated bytes iconChunkIds = 9;</code>
* <code>repeated bytes iconChunkIds = 10;</code>
*/
@kotlin.jvm.JvmSynthetic
@kotlin.jvm.JvmName("clearIconChunkIds")
@ -274,7 +291,7 @@ public object SnapshotKt {
@kotlin.OptIn(com.google.protobuf.kotlin.OnlyForUseByGeneratedProtoCode::class)
public class BlobsProxy private constructor() : com.google.protobuf.kotlin.DslProxy()
/**
* <code>map&lt;string, .com.stevesoltys.seedvault.proto.Snapshot.Blob&gt; blobs = 10;</code>
* <code>map&lt;string, .com.stevesoltys.seedvault.proto.Snapshot.Blob&gt; blobs = 11;</code>
*/
public val blobs: com.google.protobuf.kotlin.DslMap<kotlin.String, com.stevesoltys.seedvault.proto.Snapshot.Blob, BlobsProxy>
@kotlin.jvm.JvmSynthetic
@ -283,7 +300,7 @@ public object SnapshotKt {
_builder.getBlobsMap()
)
/**
* <code>map&lt;string, .com.stevesoltys.seedvault.proto.Snapshot.Blob&gt; blobs = 10;</code>
* <code>map&lt;string, .com.stevesoltys.seedvault.proto.Snapshot.Blob&gt; blobs = 11;</code>
*/
@JvmName("putBlobs")
public fun com.google.protobuf.kotlin.DslMap<kotlin.String, com.stevesoltys.seedvault.proto.Snapshot.Blob, BlobsProxy>
@ -291,7 +308,7 @@ public object SnapshotKt {
_builder.putBlobs(key, value)
}
/**
* <code>map&lt;string, .com.stevesoltys.seedvault.proto.Snapshot.Blob&gt; blobs = 10;</code>
* <code>map&lt;string, .com.stevesoltys.seedvault.proto.Snapshot.Blob&gt; blobs = 11;</code>
*/
@kotlin.jvm.JvmSynthetic
@JvmName("setBlobs")
@ -301,7 +318,7 @@ public object SnapshotKt {
put(key, value)
}
/**
* <code>map&lt;string, .com.stevesoltys.seedvault.proto.Snapshot.Blob&gt; blobs = 10;</code>
* <code>map&lt;string, .com.stevesoltys.seedvault.proto.Snapshot.Blob&gt; blobs = 11;</code>
*/
@kotlin.jvm.JvmSynthetic
@JvmName("removeBlobs")
@ -310,7 +327,7 @@ public object SnapshotKt {
_builder.removeBlobs(key)
}
/**
* <code>map&lt;string, .com.stevesoltys.seedvault.proto.Snapshot.Blob&gt; blobs = 10;</code>
* <code>map&lt;string, .com.stevesoltys.seedvault.proto.Snapshot.Blob&gt; blobs = 11;</code>
*/
@kotlin.jvm.JvmSynthetic
@JvmName("putAllBlobs")
@ -319,7 +336,7 @@ public object SnapshotKt {
_builder.putAllBlobs(map)
}
/**
* <code>map&lt;string, .com.stevesoltys.seedvault.proto.Snapshot.Blob&gt; blobs = 10;</code>
* <code>map&lt;string, .com.stevesoltys.seedvault.proto.Snapshot.Blob&gt; blobs = 11;</code>
*/
@kotlin.jvm.JvmSynthetic
@JvmName("clearBlobs")
@ -365,24 +382,7 @@ public object SnapshotKt {
}
/**
* <code>string state = 2;</code>
*/
public var state: kotlin.String
@JvmName("getState")
get() = _builder.getState()
@JvmName("setState")
set(value) {
_builder.setState(value)
}
/**
* <code>string state = 2;</code>
*/
public fun clearState() {
_builder.clearState()
}
/**
* <code>.com.stevesoltys.seedvault.proto.Snapshot.BackupType type = 3;</code>
* <code>.com.stevesoltys.seedvault.proto.Snapshot.BackupType type = 2;</code>
*/
public var type: com.stevesoltys.seedvault.proto.Snapshot.BackupType
@JvmName("getType")
@ -392,14 +392,14 @@ public object SnapshotKt {
_builder.setType(value)
}
/**
* <code>.com.stevesoltys.seedvault.proto.Snapshot.BackupType type = 3;</code>
* <code>.com.stevesoltys.seedvault.proto.Snapshot.BackupType type = 2;</code>
*/
public fun clearType() {
_builder.clearType()
}
/**
* <code>string name = 4;</code>
* <code>string name = 3;</code>
*/
public var name: kotlin.String
@JvmName("getName")
@ -409,14 +409,14 @@ public object SnapshotKt {
_builder.setName(value)
}
/**
* <code>string name = 4;</code>
* <code>string name = 3;</code>
*/
public fun clearName() {
_builder.clearName()
}
/**
* <code>bool system = 5;</code>
* <code>bool system = 4;</code>
*/
public var system: kotlin.Boolean
@JvmName("getSystem")
@ -426,14 +426,14 @@ public object SnapshotKt {
_builder.setSystem(value)
}
/**
* <code>bool system = 5;</code>
* <code>bool system = 4;</code>
*/
public fun clearSystem() {
_builder.clearSystem()
}
/**
* <code>bool launchableSystemApp = 6;</code>
* <code>bool launchableSystemApp = 5;</code>
*/
public var launchableSystemApp: kotlin.Boolean
@JvmName("getLaunchableSystemApp")
@ -443,7 +443,7 @@ public object SnapshotKt {
_builder.setLaunchableSystemApp(value)
}
/**
* <code>bool launchableSystemApp = 6;</code>
* <code>bool launchableSystemApp = 5;</code>
*/
public fun clearLaunchableSystemApp() {
_builder.clearLaunchableSystemApp()
@ -456,7 +456,7 @@ public object SnapshotKt {
@kotlin.OptIn(com.google.protobuf.kotlin.OnlyForUseByGeneratedProtoCode::class)
public class ChunkIdsProxy private constructor() : com.google.protobuf.kotlin.DslProxy()
/**
* <code>repeated bytes chunkIds = 7;</code>
* <code>repeated bytes chunkIds = 6;</code>
*/
public val chunkIds: com.google.protobuf.kotlin.DslList<com.google.protobuf.ByteString, ChunkIdsProxy>
@kotlin.jvm.JvmSynthetic
@ -464,7 +464,7 @@ public object SnapshotKt {
_builder.getChunkIdsList()
)
/**
* <code>repeated bytes chunkIds = 7;</code>
* <code>repeated bytes chunkIds = 6;</code>
* @param value The chunkIds to add.
*/
@kotlin.jvm.JvmSynthetic
@ -472,7 +472,7 @@ public object SnapshotKt {
public fun com.google.protobuf.kotlin.DslList<com.google.protobuf.ByteString, ChunkIdsProxy>.add(value: com.google.protobuf.ByteString) {
_builder.addChunkIds(value)
}/**
* <code>repeated bytes chunkIds = 7;</code>
* <code>repeated bytes chunkIds = 6;</code>
* @param value The chunkIds to add.
*/
@kotlin.jvm.JvmSynthetic
@ -481,7 +481,7 @@ public object SnapshotKt {
public inline operator fun com.google.protobuf.kotlin.DslList<com.google.protobuf.ByteString, ChunkIdsProxy>.plusAssign(value: com.google.protobuf.ByteString) {
add(value)
}/**
* <code>repeated bytes chunkIds = 7;</code>
* <code>repeated bytes chunkIds = 6;</code>
* @param values The chunkIds to add.
*/
@kotlin.jvm.JvmSynthetic
@ -489,7 +489,7 @@ public object SnapshotKt {
public fun com.google.protobuf.kotlin.DslList<com.google.protobuf.ByteString, ChunkIdsProxy>.addAll(values: kotlin.collections.Iterable<com.google.protobuf.ByteString>) {
_builder.addAllChunkIds(values)
}/**
* <code>repeated bytes chunkIds = 7;</code>
* <code>repeated bytes chunkIds = 6;</code>
* @param values The chunkIds to add.
*/
@kotlin.jvm.JvmSynthetic
@ -498,7 +498,7 @@ public object SnapshotKt {
public inline operator fun com.google.protobuf.kotlin.DslList<com.google.protobuf.ByteString, ChunkIdsProxy>.plusAssign(values: kotlin.collections.Iterable<com.google.protobuf.ByteString>) {
addAll(values)
}/**
* <code>repeated bytes chunkIds = 7;</code>
* <code>repeated bytes chunkIds = 6;</code>
* @param index The index to set the value at.
* @param value The chunkIds to set.
*/
@ -507,7 +507,7 @@ public object SnapshotKt {
public operator fun com.google.protobuf.kotlin.DslList<com.google.protobuf.ByteString, ChunkIdsProxy>.set(index: kotlin.Int, value: com.google.protobuf.ByteString) {
_builder.setChunkIds(index, value)
}/**
* <code>repeated bytes chunkIds = 7;</code>
* <code>repeated bytes chunkIds = 6;</code>
*/
@kotlin.jvm.JvmSynthetic
@kotlin.jvm.JvmName("clearChunkIds")
@ -515,7 +515,7 @@ public object SnapshotKt {
_builder.clearChunkIds()
}
/**
* <code>.com.stevesoltys.seedvault.proto.Snapshot.Apk apk = 8;</code>
* <code>.com.stevesoltys.seedvault.proto.Snapshot.Apk apk = 7;</code>
*/
public var apk: com.stevesoltys.seedvault.proto.Snapshot.Apk
@JvmName("getApk")
@ -525,13 +525,13 @@ public object SnapshotKt {
_builder.setApk(value)
}
/**
* <code>.com.stevesoltys.seedvault.proto.Snapshot.Apk apk = 8;</code>
* <code>.com.stevesoltys.seedvault.proto.Snapshot.Apk apk = 7;</code>
*/
public fun clearApk() {
_builder.clearApk()
}
/**
* <code>.com.stevesoltys.seedvault.proto.Snapshot.Apk apk = 8;</code>
* <code>.com.stevesoltys.seedvault.proto.Snapshot.Apk apk = 7;</code>
* @return Whether the apk field is set.
*/
public fun hasApk(): kotlin.Boolean {

View file

@ -39,7 +39,7 @@ data class BackupMetadata(
time = s.token,
androidVersion = s.sdkInt,
androidIncremental = s.androidIncremental,
deviceName = s.name,
deviceName = "${s.name} - ${s.user}",
d2dBackup = s.d2D,
packageMetadataMap = s.appsMap.mapValues { (_, app) ->
PackageMetadata.fromSnapshot(app)
@ -121,7 +121,6 @@ data class PackageMetadata(
companion object {
fun fromSnapshot(app: Snapshot.App) = PackageMetadata(
time = app.time,
state = if (app.state.isBlank()) UNKNOWN_ERROR else PackageState.valueOf(app.state),
backupType = when (app.type) {
Snapshot.BackupType.FULL -> BackupType.FULL
Snapshot.BackupType.KV -> BackupType.KV

View file

@ -13,8 +13,6 @@ import androidx.annotation.VisibleForTesting
import androidx.annotation.WorkerThread
import com.stevesoltys.seedvault.Clock
import com.stevesoltys.seedvault.metadata.PackageState.APK_AND_DATA
import com.stevesoltys.seedvault.transport.backup.PackageService
import com.stevesoltys.seedvault.transport.backup.isSystemApp
import java.io.FileNotFoundException
import java.io.IOException
import java.io.OutputStream
@ -31,7 +29,6 @@ internal class MetadataManager(
private val clock: Clock,
private val metadataWriter: MetadataWriter,
private val metadataReader: MetadataReader,
private val packageService: PackageService,
) {
private val uninitializedMetadata = BackupMetadata(token = -42L, salt = "foo bar")
@ -52,10 +49,6 @@ internal class MetadataManager(
return field
}
private val launchableSystemApps by lazy {
packageService.launchableSystemApps.map { it.activityInfo.packageName }.toSet()
}
/**
* Call this after a package has been backed up successfully.
*
@ -75,15 +68,11 @@ internal class MetadataManager(
modifyCachedMetadata {
val now = clock.time()
metadata.packageMetadataMap.getOrPut(packageName) {
val isSystemApp = packageInfo.isSystemApp()
PackageMetadata(
time = now,
state = APK_AND_DATA,
backupType = type,
size = size,
system = isSystemApp,
isLaunchableSystemApp = isSystemApp &&
launchableSystemApps.contains(packageName),
)
}.apply {
time = now
@ -111,15 +100,11 @@ internal class MetadataManager(
check(packageState != APK_AND_DATA) { "Backup Error with non-error package state." }
modifyCachedMetadata {
metadata.packageMetadataMap.getOrPut(packageInfo.packageName) {
val isSystemApp = packageInfo.isSystemApp()
PackageMetadata(
time = 0L,
state = packageState,
backupType = backupType,
name = packageInfo.applicationInfo?.loadLabel(context.packageManager),
system = isSystemApp,
isLaunchableSystemApp = isSystemApp &&
launchableSystemApps.contains(packageInfo.packageName),
)
}.state = packageState
}
@ -137,14 +122,10 @@ internal class MetadataManager(
packageState: PackageState,
) = modifyCachedMetadata {
metadata.packageMetadataMap.getOrPut(packageInfo.packageName) {
val isSystemApp = packageInfo.isSystemApp()
PackageMetadata(
time = 0L,
state = packageState,
name = packageInfo.applicationInfo?.loadLabel(context.packageManager),
system = isSystemApp,
isLaunchableSystemApp = isSystemApp &&
launchableSystemApps.contains(packageInfo.packageName),
)
}.apply {
state = packageState

View file

@ -9,7 +9,7 @@ import org.koin.android.ext.koin.androidContext
import org.koin.dsl.module
val metadataModule = module {
single { MetadataManager(androidContext(), get(), get(), get(), get()) }
single { MetadataManager(androidContext(), get(), get(), get()) }
single<MetadataWriter> { MetadataWriterImpl() }
single<MetadataReader> { MetadataReaderImpl(get()) }
}

View file

@ -5,6 +5,7 @@
package com.stevesoltys.seedvault.transport.backup
import android.Manifest
import android.annotation.SuppressLint
import android.content.Context
import android.content.pm.PackageInfo
@ -18,7 +19,6 @@ import com.stevesoltys.seedvault.Clock
import com.stevesoltys.seedvault.header.VERSION
import com.stevesoltys.seedvault.metadata.BackupType
import com.stevesoltys.seedvault.metadata.MetadataManager
import com.stevesoltys.seedvault.metadata.PackageState.APK_AND_DATA
import com.stevesoltys.seedvault.proto.Snapshot
import com.stevesoltys.seedvault.proto.Snapshot.Apk
import com.stevesoltys.seedvault.proto.Snapshot.App
@ -28,6 +28,9 @@ import io.github.oshai.kotlinlogging.KotlinLogging
import org.calyxos.seedvault.core.backends.AppBackupFileType
import org.calyxos.seedvault.core.toHexString
/**
* Creates a new [SnapshotCreator], because one is only valid for a single backup run.
*/
internal class SnapshotCreatorFactory(
private val context: Context,
private val clock: Clock,
@ -39,6 +42,10 @@ internal class SnapshotCreatorFactory(
SnapshotCreator(context, clock, packageService, settingsManager, metadataManager)
}
/**
* Assembles snapshot information over the course of a single backup run
* and creates a [Snapshot] object in the end by calling [finalizeSnapshot].
*/
internal class SnapshotCreator(
private val context: Context,
private val clock: Clock,
@ -48,14 +55,21 @@ internal class SnapshotCreator(
) {
private val log = KotlinLogging.logger { }
private val snapshotBuilder = Snapshot.newBuilder()
private val appBuilderMap = mutableMapOf<String, App.Builder>()
private val blobsMap = mutableMapOf<String, Blob>()
private val launchableSystemApps by lazy {
// as we can't ask [PackageInfo] for this, we keep a set of packages around
packageService.launchableSystemApps.map { it.activityInfo.packageName }.toSet()
}
/**
* Call this after all blobs for the given [apk] have been saved to the backend.
* The [apk] must contain the ordered list of chunk IDs
* and the given [blobMap] must have one [Blob] per chunk ID.
*/
fun onApkBackedUp(
packageInfo: PackageInfo,
apk: Apk,
@ -71,6 +85,14 @@ internal class SnapshotCreator(
blobsMap.putAll(blobMap)
}
/**
* Call this after all blobs for the package identified by the given [packageInfo]
* have been saved to the backend.
* The given [backupData] must contain the full ordered list of [BackupData.chunkIds]
* and the [BackupData.blobMap] must have one [Blob] per chunk ID.
*
* Failure to call this method results in the package effectively not getting backed up.
*/
fun onPackageBackedUp(
packageInfo: PackageInfo,
backupType: BackupType,
@ -83,7 +105,6 @@ internal class SnapshotCreator(
App.newBuilder()
}.apply {
time = clock.time()
state = APK_AND_DATA.name // TODO review those states and their usefulness for snapshot
type = backupType.forSnapshot()
val label = packageInfo.applicationInfo?.loadLabel(context.packageManager)
if (label != null) name = label.toString()
@ -95,26 +116,31 @@ internal class SnapshotCreator(
metadataManager.onPackageBackedUp(packageInfo, backupType, backupData.size)
}
/**
* Call this after all blobs for the app icons have been saved to the backend.
*/
fun onIconsBackedUp(backupData: BackupData) {
snapshotBuilder.addAllIconChunkIds(backupData.chunkIds.forProto())
blobsMap.putAll(backupData.blobMap)
}
/**
* Must get called after all backup data was saved to the backend.
* Returns the assembled [Snapshot] which must be saved to the backend as well
* to complete the current backup run.
*
* Internal state will be cleared to free up memory.
* Still, it isn't safe to re-use an instance of this class, after it has been finalized.
*/
fun finalizeSnapshot(): Snapshot {
log.info { "finalizeSnapshot()" }
val userName = getUserName()
val deviceName = if (userName == null) {
"${Build.MANUFACTURER} ${Build.MODEL}"
} else {
"${Build.MANUFACTURER} ${Build.MODEL} - $userName"
}
@SuppressLint("HardwareIds")
val snapshot = snapshotBuilder.apply {
version = VERSION.toInt()
token = clock.time()
name = deviceName
androidId = Settings.Secure.getString(context.contentResolver, ANDROID_ID)
name = "${Build.MANUFACTURER} ${Build.MODEL}"
user = getUserName() ?: ""
androidId = Settings.Secure.getString(context.contentResolver, ANDROID_ID) ?: ""
sdkInt = Build.VERSION.SDK_INT
androidIncremental = Build.VERSION.INCREMENTAL
d2D = settingsManager.d2dBackupsEnabled()
@ -123,11 +149,13 @@ internal class SnapshotCreator(
}.build()
appBuilderMap.clear()
snapshotBuilder.clear()
blobsMap.clear()
return snapshot
}
private fun getUserName(): String? {
val perm = "android.permission.QUERY_USERS"
@Suppress("UNRESOLVED_REFERENCE") // hidden AOSP API
val perm = Manifest.permission.QUERY_USERS
return if (context.checkSelfPermission(perm) == PERMISSION_GRANTED) {
val userManager = context.getSystemService(UserManager::class.java) ?: return null
userManager.userName

View file

@ -8,23 +8,23 @@ message Snapshot {
uint32 version = 1;
uint64 token = 2;
string name = 3;
string androidId = 4;
uint32 sdkInt = 5;
string androidIncremental = 6;
bool d2d = 7;
map<string, App> apps = 8;
repeated bytes iconChunkIds = 9;
map<string, Blob> blobs = 10;
string user = 4;
string androidId = 5;
uint32 sdkInt = 6;
string androidIncremental = 7;
bool d2d = 8;
map<string, App> apps = 9;
repeated bytes iconChunkIds = 10;
map<string, Blob> blobs = 11;
message App {
uint64 time = 1;
string state = 2;
BackupType type = 3;
string name = 4;
bool system = 5;
bool launchableSystemApp = 6;
repeated bytes chunkIds = 7;
Apk apk = 8;
BackupType type = 2;
string name = 3;
bool system = 4;
bool launchableSystemApp = 5;
repeated bytes chunkIds = 6;
Apk apk = 7;
}
enum BackupType {

View file

@ -9,7 +9,6 @@ import android.content.Context
import android.content.Context.MODE_PRIVATE
import android.content.pm.ApplicationInfo
import android.content.pm.ApplicationInfo.FLAG_ALLOW_BACKUP
import android.content.pm.ApplicationInfo.FLAG_SYSTEM
import android.content.pm.PackageInfo
import android.content.pm.PackageManager
import androidx.test.ext.junit.runners.AndroidJUnit4
@ -23,7 +22,6 @@ import com.stevesoltys.seedvault.metadata.PackageState.NOT_ALLOWED
import com.stevesoltys.seedvault.metadata.PackageState.NO_DATA
import com.stevesoltys.seedvault.metadata.PackageState.WAS_STOPPED
import com.stevesoltys.seedvault.settings.SettingsManager
import com.stevesoltys.seedvault.transport.backup.PackageService
import io.mockk.Runs
import io.mockk.every
import io.mockk.just
@ -37,7 +35,6 @@ import org.junit.Test
import org.junit.runner.RunWith
import org.koin.core.context.stopKoin
import org.robolectric.annotation.Config
import java.io.ByteArrayOutputStream
import java.io.FileInputStream
import java.io.FileOutputStream
import kotlin.random.Random
@ -53,7 +50,6 @@ class MetadataManagerTest {
private val clock: Clock = mockk()
private val metadataWriter: MetadataWriter = mockk()
private val metadataReader: MetadataReader = mockk()
private val packageService: PackageService = mockk()
private val settingsManager: SettingsManager = mockk()
private val manager = MetadataManager(
@ -61,7 +57,6 @@ class MetadataManagerTest {
clock = clock,
metadataWriter = metadataWriter,
metadataReader = metadataReader,
packageService = packageService,
)
private val packageManager: PackageManager = mockk()
@ -76,7 +71,6 @@ class MetadataManagerTest {
private val saltBytes = Random.nextBytes(METADATA_SALT_SIZE)
private val salt = saltBytes.encodeBase64()
private val initialMetadata = BackupMetadata(token = token, salt = salt)
private val storageOutputStream = ByteArrayOutputStream()
private val cacheOutputStream: FileOutputStream = mockk()
private val cacheInputStream: FileInputStream = mockk()
private val encodedMetadata = getRandomByteArray()
@ -93,7 +87,6 @@ class MetadataManagerTest {
@Test
fun `test onPackageBackedUp()`() {
packageInfo.applicationInfo!!.flags = FLAG_SYSTEM
val updatedMetadata = initialMetadata.copy(
time = time,
packageMetadataMap = PackageMetadataMap() // otherwise this isn't copied, but referenced
@ -103,7 +96,6 @@ class MetadataManagerTest {
updatedMetadata.packageMetadataMap[packageName] = packageMetadata
every { context.packageManager } returns packageManager
every { packageService.launchableSystemApps } returns emptyList()
expectReadFromCache()
every { clock.time() } returns time
expectWriteToCache(initialMetadata)
@ -115,8 +107,6 @@ class MetadataManagerTest {
state = APK_AND_DATA,
backupType = BackupType.FULL,
size = size,
system = true,
isLaunchableSystemApp = false,
),
manager.getPackageMetadata(packageName)
)

View file

@ -0,0 +1,134 @@
/*
* SPDX-FileCopyrightText: 2024 The Calyx Institute
* SPDX-License-Identifier: Apache-2.0
*/
package com.stevesoltys.seedvault.transport.backup
import android.content.Context
import android.content.pm.ActivityInfo
import android.content.pm.ApplicationInfo
import android.content.pm.ApplicationInfo.FLAG_SYSTEM
import android.content.pm.ResolveInfo
import androidx.test.core.app.ApplicationProvider
import androidx.test.ext.junit.runners.AndroidJUnit4
import com.stevesoltys.seedvault.TestApp
import com.stevesoltys.seedvault.header.VERSION
import com.stevesoltys.seedvault.metadata.BackupType
import com.stevesoltys.seedvault.proto.Snapshot
import com.stevesoltys.seedvault.transport.TransportTest
import io.mockk.Runs
import io.mockk.every
import io.mockk.just
import io.mockk.mockk
import org.junit.Assert.assertEquals
import org.junit.Test
import org.junit.runner.RunWith
import org.robolectric.annotation.Config
import kotlin.random.Random
@RunWith(AndroidJUnit4::class)
@Config(
sdk = [34], // TODO: Drop once robolectric supports 35
application = TestApp::class
)
internal class SnapshotCreatorTest : TransportTest() {
private val ctx: Context = ApplicationProvider.getApplicationContext()
private val packageService: PackageService = mockk()
private val snapshotCreator =
SnapshotCreator(ctx, clock, packageService, settingsManager, metadataManager)
@Test
fun `test onApkBackedUp`() {
every { applicationInfo.loadLabel(any()) } returns name
every { clock.time() } returns token
every { settingsManager.d2dBackupsEnabled() } returns Random.nextBoolean()
snapshotCreator.onApkBackedUp(packageInfo, apk, blobMap)
val s = snapshotCreator.finalizeSnapshot()
assertEquals(apk, s.appsMap[packageName]?.apk)
assertEquals(name, s.appsMap[packageName]?.name)
assertEquals(blobMap, s.blobsMap)
}
@Test
fun `test onPackageBackedUp`() {
val size = apkBackupData.size
val isSystem = Random.nextBoolean()
val appInfo = mockk<ApplicationInfo> {
flags = if (isSystem) FLAG_SYSTEM else 0
}
packageInfo.applicationInfo = appInfo
val resolveInfo = ResolveInfo().apply { // if isSystem, then it will be launchable
activityInfo = ActivityInfo().apply {
packageName = this@SnapshotCreatorTest.packageName
}
}
every { appInfo.loadLabel(any()) } returns name
every { metadataManager.onPackageBackedUp(packageInfo, BackupType.FULL, size) } just Runs
every { clock.time() } returns token andThen token + 1
every { settingsManager.d2dBackupsEnabled() } returns Random.nextBoolean()
every { packageService.launchableSystemApps } returns listOf(resolveInfo)
snapshotCreator.onPackageBackedUp(packageInfo, BackupType.FULL, apkBackupData)
val s = snapshotCreator.finalizeSnapshot()
assertEquals(name, s.appsMap[packageName]?.name)
assertEquals(token, s.appsMap[packageName]?.time)
assertEquals(Snapshot.BackupType.FULL, s.appsMap[packageName]?.type)
assertEquals(isSystem, s.appsMap[packageName]?.system)
assertEquals(isSystem, s.appsMap[packageName]?.launchableSystemApp)
assertEquals(apkBackupData.chunkIds.forProto(), s.appsMap[packageName]?.chunkIdsList)
assertEquals(apkBackupData.blobMap, s.blobsMap)
}
@Test
fun `test onPackageBackedUp handles no application info`() {
packageInfo.applicationInfo = null
val size = apkBackupData.size
every { metadataManager.onPackageBackedUp(packageInfo, BackupType.FULL, size) } just Runs
every { clock.time() } returns token andThen token + 1
every { settingsManager.d2dBackupsEnabled() } returns Random.nextBoolean()
every { packageService.launchableSystemApps } returns emptyList()
snapshotCreator.onPackageBackedUp(packageInfo, BackupType.FULL, apkBackupData)
snapshotCreator.finalizeSnapshot()
}
@Test
fun `test onIconsBackedUp`() {
every { clock.time() } returns token andThen token + 1
every { settingsManager.d2dBackupsEnabled() } returns Random.nextBoolean()
snapshotCreator.onIconsBackedUp(apkBackupData)
val s = snapshotCreator.finalizeSnapshot()
assertEquals(apkBackupData.chunkIds.forProto(), s.iconChunkIdsList)
assertEquals(apkBackupData.blobMap, s.blobsMap)
}
@Test
fun `test finalize`() {
val d2d = Random.nextBoolean()
every { clock.time() } returns token
every { settingsManager.d2dBackupsEnabled() } returns d2d
val s = snapshotCreator.finalizeSnapshot()
assertEquals(VERSION, s.version.toByte())
assertEquals(token, s.token)
assertEquals("robolectric robolectric", s.name)
assertEquals("", s.user) // no perm
assertEquals("", s.androidId) // not mocked
assertEquals(34, s.sdkInt) // as per config above, needs bump once possible
assertEquals("unknown", s.androidIncremental)
assertEquals(d2d, s.d2D)
assertEquals(0, s.appsCount)
assertEquals(0, s.iconChunkIdsCount)
assertEquals(emptyMap<String, Snapshot.Blob>(), s.blobsMap)
}
}