diff --git a/app/build/generated/source/proto/debug/kotlin/com/stevesoltys/seedvault/proto/SnapshotKt.kt b/app/build/generated/source/proto/debug/kotlin/com/stevesoltys/seedvault/proto/SnapshotKt.kt
index e9ec0f04..f5141adc 100644
--- a/app/build/generated/source/proto/debug/kotlin/com/stevesoltys/seedvault/proto/SnapshotKt.kt
+++ b/app/build/generated/source/proto/debug/kotlin/com/stevesoltys/seedvault/proto/SnapshotKt.kt
@@ -74,7 +74,24 @@ public object SnapshotKt {
}
/**
- * string androidId = 4;
+ * string user = 4;
+ */
+ public var user: kotlin.String
+ @JvmName("getUser")
+ get() = _builder.getUser()
+ @JvmName("setUser")
+ set(value) {
+ _builder.setUser(value)
+ }
+ /**
+ * string user = 4;
+ */
+ public fun clearUser() {
+ _builder.clearUser()
+ }
+
+ /**
+ * string androidId = 5;
*/
public var androidId: kotlin.String
@JvmName("getAndroidId")
@@ -84,14 +101,14 @@ public object SnapshotKt {
_builder.setAndroidId(value)
}
/**
- * string androidId = 4;
+ * string androidId = 5;
*/
public fun clearAndroidId() {
_builder.clearAndroidId()
}
/**
- * uint32 sdkInt = 5;
+ * uint32 sdkInt = 6;
*/
public var sdkInt: kotlin.Int
@JvmName("getSdkInt")
@@ -101,14 +118,14 @@ public object SnapshotKt {
_builder.setSdkInt(value)
}
/**
- * uint32 sdkInt = 5;
+ * uint32 sdkInt = 6;
*/
public fun clearSdkInt() {
_builder.clearSdkInt()
}
/**
- * string androidIncremental = 6;
+ * string androidIncremental = 7;
*/
public var androidIncremental: kotlin.String
@JvmName("getAndroidIncremental")
@@ -118,14 +135,14 @@ public object SnapshotKt {
_builder.setAndroidIncremental(value)
}
/**
- * string androidIncremental = 6;
+ * string androidIncremental = 7;
*/
public fun clearAndroidIncremental() {
_builder.clearAndroidIncremental()
}
/**
- * bool d2d = 7;
+ * bool d2d = 8;
*/
public var d2D: kotlin.Boolean
@JvmName("getD2D")
@@ -135,7 +152,7 @@ public object SnapshotKt {
_builder.setD2D(value)
}
/**
- * bool d2d = 7;
+ * bool d2d = 8;
*/
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()
/**
- * map<string, .com.stevesoltys.seedvault.proto.Snapshot.App> apps = 8;
+ * map<string, .com.stevesoltys.seedvault.proto.Snapshot.App> apps = 9;
*/
public val apps: com.google.protobuf.kotlin.DslMap
@kotlin.jvm.JvmSynthetic
@@ -157,7 +174,7 @@ public object SnapshotKt {
_builder.getAppsMap()
)
/**
- * map<string, .com.stevesoltys.seedvault.proto.Snapshot.App> apps = 8;
+ * map<string, .com.stevesoltys.seedvault.proto.Snapshot.App> apps = 9;
*/
@JvmName("putApps")
public fun com.google.protobuf.kotlin.DslMap
@@ -165,7 +182,7 @@ public object SnapshotKt {
_builder.putApps(key, value)
}
/**
- * map<string, .com.stevesoltys.seedvault.proto.Snapshot.App> apps = 8;
+ * map<string, .com.stevesoltys.seedvault.proto.Snapshot.App> apps = 9;
*/
@kotlin.jvm.JvmSynthetic
@JvmName("setApps")
@@ -175,7 +192,7 @@ public object SnapshotKt {
put(key, value)
}
/**
- * map<string, .com.stevesoltys.seedvault.proto.Snapshot.App> apps = 8;
+ * map<string, .com.stevesoltys.seedvault.proto.Snapshot.App> apps = 9;
*/
@kotlin.jvm.JvmSynthetic
@JvmName("removeApps")
@@ -184,7 +201,7 @@ public object SnapshotKt {
_builder.removeApps(key)
}
/**
- * map<string, .com.stevesoltys.seedvault.proto.Snapshot.App> apps = 8;
+ * map<string, .com.stevesoltys.seedvault.proto.Snapshot.App> apps = 9;
*/
@kotlin.jvm.JvmSynthetic
@JvmName("putAllApps")
@@ -193,7 +210,7 @@ public object SnapshotKt {
_builder.putAllApps(map)
}
/**
- * map<string, .com.stevesoltys.seedvault.proto.Snapshot.App> apps = 8;
+ * map<string, .com.stevesoltys.seedvault.proto.Snapshot.App> apps = 9;
*/
@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()
/**
- * repeated bytes iconChunkIds = 9;
+ * repeated bytes iconChunkIds = 10;
*/
public val iconChunkIds: com.google.protobuf.kotlin.DslList
@kotlin.jvm.JvmSynthetic
@@ -217,7 +234,7 @@ public object SnapshotKt {
_builder.getIconChunkIdsList()
)
/**
- * repeated bytes iconChunkIds = 9;
+ * repeated bytes iconChunkIds = 10;
* @param value The iconChunkIds to add.
*/
@kotlin.jvm.JvmSynthetic
@@ -225,7 +242,7 @@ public object SnapshotKt {
public fun com.google.protobuf.kotlin.DslList.add(value: com.google.protobuf.ByteString) {
_builder.addIconChunkIds(value)
}/**
- * repeated bytes iconChunkIds = 9;
+ * repeated bytes iconChunkIds = 10;
* @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.plusAssign(value: com.google.protobuf.ByteString) {
add(value)
}/**
- * repeated bytes iconChunkIds = 9;
+ * repeated bytes iconChunkIds = 10;
* @param values The iconChunkIds to add.
*/
@kotlin.jvm.JvmSynthetic
@@ -242,7 +259,7 @@ public object SnapshotKt {
public fun com.google.protobuf.kotlin.DslList.addAll(values: kotlin.collections.Iterable) {
_builder.addAllIconChunkIds(values)
}/**
- * repeated bytes iconChunkIds = 9;
+ * repeated bytes iconChunkIds = 10;
* @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.plusAssign(values: kotlin.collections.Iterable) {
addAll(values)
}/**
- * repeated bytes iconChunkIds = 9;
+ * repeated bytes iconChunkIds = 10;
* @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.set(index: kotlin.Int, value: com.google.protobuf.ByteString) {
_builder.setIconChunkIds(index, value)
}/**
- * repeated bytes iconChunkIds = 9;
+ * repeated bytes iconChunkIds = 10;
*/
@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()
/**
- * map<string, .com.stevesoltys.seedvault.proto.Snapshot.Blob> blobs = 10;
+ * map<string, .com.stevesoltys.seedvault.proto.Snapshot.Blob> blobs = 11;
*/
public val blobs: com.google.protobuf.kotlin.DslMap
@kotlin.jvm.JvmSynthetic
@@ -283,7 +300,7 @@ public object SnapshotKt {
_builder.getBlobsMap()
)
/**
- * map<string, .com.stevesoltys.seedvault.proto.Snapshot.Blob> blobs = 10;
+ * map<string, .com.stevesoltys.seedvault.proto.Snapshot.Blob> blobs = 11;
*/
@JvmName("putBlobs")
public fun com.google.protobuf.kotlin.DslMap
@@ -291,7 +308,7 @@ public object SnapshotKt {
_builder.putBlobs(key, value)
}
/**
- * map<string, .com.stevesoltys.seedvault.proto.Snapshot.Blob> blobs = 10;
+ * map<string, .com.stevesoltys.seedvault.proto.Snapshot.Blob> blobs = 11;
*/
@kotlin.jvm.JvmSynthetic
@JvmName("setBlobs")
@@ -301,7 +318,7 @@ public object SnapshotKt {
put(key, value)
}
/**
- * map<string, .com.stevesoltys.seedvault.proto.Snapshot.Blob> blobs = 10;
+ * map<string, .com.stevesoltys.seedvault.proto.Snapshot.Blob> blobs = 11;
*/
@kotlin.jvm.JvmSynthetic
@JvmName("removeBlobs")
@@ -310,7 +327,7 @@ public object SnapshotKt {
_builder.removeBlobs(key)
}
/**
- * map<string, .com.stevesoltys.seedvault.proto.Snapshot.Blob> blobs = 10;
+ * map<string, .com.stevesoltys.seedvault.proto.Snapshot.Blob> blobs = 11;
*/
@kotlin.jvm.JvmSynthetic
@JvmName("putAllBlobs")
@@ -319,7 +336,7 @@ public object SnapshotKt {
_builder.putAllBlobs(map)
}
/**
- * map<string, .com.stevesoltys.seedvault.proto.Snapshot.Blob> blobs = 10;
+ * map<string, .com.stevesoltys.seedvault.proto.Snapshot.Blob> blobs = 11;
*/
@kotlin.jvm.JvmSynthetic
@JvmName("clearBlobs")
@@ -365,24 +382,7 @@ public object SnapshotKt {
}
/**
- * string state = 2;
- */
- public var state: kotlin.String
- @JvmName("getState")
- get() = _builder.getState()
- @JvmName("setState")
- set(value) {
- _builder.setState(value)
- }
- /**
- * string state = 2;
- */
- public fun clearState() {
- _builder.clearState()
- }
-
- /**
- * .com.stevesoltys.seedvault.proto.Snapshot.BackupType type = 3;
+ * .com.stevesoltys.seedvault.proto.Snapshot.BackupType type = 2;
*/
public var type: com.stevesoltys.seedvault.proto.Snapshot.BackupType
@JvmName("getType")
@@ -392,14 +392,14 @@ public object SnapshotKt {
_builder.setType(value)
}
/**
- * .com.stevesoltys.seedvault.proto.Snapshot.BackupType type = 3;
+ * .com.stevesoltys.seedvault.proto.Snapshot.BackupType type = 2;
*/
public fun clearType() {
_builder.clearType()
}
/**
- * string name = 4;
+ * string name = 3;
*/
public var name: kotlin.String
@JvmName("getName")
@@ -409,14 +409,14 @@ public object SnapshotKt {
_builder.setName(value)
}
/**
- * string name = 4;
+ * string name = 3;
*/
public fun clearName() {
_builder.clearName()
}
/**
- * bool system = 5;
+ * bool system = 4;
*/
public var system: kotlin.Boolean
@JvmName("getSystem")
@@ -426,14 +426,14 @@ public object SnapshotKt {
_builder.setSystem(value)
}
/**
- * bool system = 5;
+ * bool system = 4;
*/
public fun clearSystem() {
_builder.clearSystem()
}
/**
- * bool launchableSystemApp = 6;
+ * bool launchableSystemApp = 5;
*/
public var launchableSystemApp: kotlin.Boolean
@JvmName("getLaunchableSystemApp")
@@ -443,7 +443,7 @@ public object SnapshotKt {
_builder.setLaunchableSystemApp(value)
}
/**
- * bool launchableSystemApp = 6;
+ * bool launchableSystemApp = 5;
*/
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()
/**
- * repeated bytes chunkIds = 7;
+ * repeated bytes chunkIds = 6;
*/
public val chunkIds: com.google.protobuf.kotlin.DslList
@kotlin.jvm.JvmSynthetic
@@ -464,7 +464,7 @@ public object SnapshotKt {
_builder.getChunkIdsList()
)
/**
- * repeated bytes chunkIds = 7;
+ * repeated bytes chunkIds = 6;
* @param value The chunkIds to add.
*/
@kotlin.jvm.JvmSynthetic
@@ -472,7 +472,7 @@ public object SnapshotKt {
public fun com.google.protobuf.kotlin.DslList.add(value: com.google.protobuf.ByteString) {
_builder.addChunkIds(value)
}/**
- * repeated bytes chunkIds = 7;
+ * repeated bytes chunkIds = 6;
* @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.plusAssign(value: com.google.protobuf.ByteString) {
add(value)
}/**
- * repeated bytes chunkIds = 7;
+ * repeated bytes chunkIds = 6;
* @param values The chunkIds to add.
*/
@kotlin.jvm.JvmSynthetic
@@ -489,7 +489,7 @@ public object SnapshotKt {
public fun com.google.protobuf.kotlin.DslList.addAll(values: kotlin.collections.Iterable) {
_builder.addAllChunkIds(values)
}/**
- * repeated bytes chunkIds = 7;
+ * repeated bytes chunkIds = 6;
* @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.plusAssign(values: kotlin.collections.Iterable) {
addAll(values)
}/**
- * repeated bytes chunkIds = 7;
+ * repeated bytes chunkIds = 6;
* @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.set(index: kotlin.Int, value: com.google.protobuf.ByteString) {
_builder.setChunkIds(index, value)
}/**
- * repeated bytes chunkIds = 7;
+ * repeated bytes chunkIds = 6;
*/
@kotlin.jvm.JvmSynthetic
@kotlin.jvm.JvmName("clearChunkIds")
@@ -515,7 +515,7 @@ public object SnapshotKt {
_builder.clearChunkIds()
}
/**
- * .com.stevesoltys.seedvault.proto.Snapshot.Apk apk = 8;
+ * .com.stevesoltys.seedvault.proto.Snapshot.Apk apk = 7;
*/
public var apk: com.stevesoltys.seedvault.proto.Snapshot.Apk
@JvmName("getApk")
@@ -525,13 +525,13 @@ public object SnapshotKt {
_builder.setApk(value)
}
/**
- * .com.stevesoltys.seedvault.proto.Snapshot.Apk apk = 8;
+ * .com.stevesoltys.seedvault.proto.Snapshot.Apk apk = 7;
*/
public fun clearApk() {
_builder.clearApk()
}
/**
- * .com.stevesoltys.seedvault.proto.Snapshot.Apk apk = 8;
+ * .com.stevesoltys.seedvault.proto.Snapshot.Apk apk = 7;
* @return Whether the apk field is set.
*/
public fun hasApk(): kotlin.Boolean {
diff --git a/app/src/main/java/com/stevesoltys/seedvault/metadata/Metadata.kt b/app/src/main/java/com/stevesoltys/seedvault/metadata/Metadata.kt
index 0a40f2ad..d1362e1d 100644
--- a/app/src/main/java/com/stevesoltys/seedvault/metadata/Metadata.kt
+++ b/app/src/main/java/com/stevesoltys/seedvault/metadata/Metadata.kt
@@ -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
diff --git a/app/src/main/java/com/stevesoltys/seedvault/metadata/MetadataManager.kt b/app/src/main/java/com/stevesoltys/seedvault/metadata/MetadataManager.kt
index cc9655a6..bab7ca72 100644
--- a/app/src/main/java/com/stevesoltys/seedvault/metadata/MetadataManager.kt
+++ b/app/src/main/java/com/stevesoltys/seedvault/metadata/MetadataManager.kt
@@ -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
diff --git a/app/src/main/java/com/stevesoltys/seedvault/metadata/MetadataModule.kt b/app/src/main/java/com/stevesoltys/seedvault/metadata/MetadataModule.kt
index 003fff7e..b3473794 100644
--- a/app/src/main/java/com/stevesoltys/seedvault/metadata/MetadataModule.kt
+++ b/app/src/main/java/com/stevesoltys/seedvault/metadata/MetadataModule.kt
@@ -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 { MetadataWriterImpl() }
single { MetadataReaderImpl(get()) }
}
diff --git a/app/src/main/java/com/stevesoltys/seedvault/transport/backup/SnapshotCreator.kt b/app/src/main/java/com/stevesoltys/seedvault/transport/backup/SnapshotCreator.kt
index 1cd5a0c9..be8c6f21 100644
--- a/app/src/main/java/com/stevesoltys/seedvault/transport/backup/SnapshotCreator.kt
+++ b/app/src/main/java/com/stevesoltys/seedvault/transport/backup/SnapshotCreator.kt
@@ -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()
private val blobsMap = mutableMapOf()
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
diff --git a/app/src/main/proto/snapshot.proto b/app/src/main/proto/snapshot.proto
index e5e20823..142a3ea7 100644
--- a/app/src/main/proto/snapshot.proto
+++ b/app/src/main/proto/snapshot.proto
@@ -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 apps = 8;
- repeated bytes iconChunkIds = 9;
- map blobs = 10;
+ string user = 4;
+ string androidId = 5;
+ uint32 sdkInt = 6;
+ string androidIncremental = 7;
+ bool d2d = 8;
+ map apps = 9;
+ repeated bytes iconChunkIds = 10;
+ map 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 {
diff --git a/app/src/test/java/com/stevesoltys/seedvault/metadata/MetadataManagerTest.kt b/app/src/test/java/com/stevesoltys/seedvault/metadata/MetadataManagerTest.kt
index 368777ca..974a9bce 100644
--- a/app/src/test/java/com/stevesoltys/seedvault/metadata/MetadataManagerTest.kt
+++ b/app/src/test/java/com/stevesoltys/seedvault/metadata/MetadataManagerTest.kt
@@ -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)
)
diff --git a/app/src/test/java/com/stevesoltys/seedvault/transport/backup/SnapshotCreatorTest.kt b/app/src/test/java/com/stevesoltys/seedvault/transport/backup/SnapshotCreatorTest.kt
new file mode 100644
index 00000000..720cd522
--- /dev/null
+++ b/app/src/test/java/com/stevesoltys/seedvault/transport/backup/SnapshotCreatorTest.kt
@@ -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 {
+ 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(), s.blobsMap)
+ }
+
+}