Merge pull request #606 from seedvault-app/chore/version-bump

Bump version to 14-4.0 - D2D <3
diff --git a/.gitignore b/.gitignore
index 7688171..4a365e3 100644
--- a/.gitignore
+++ b/.gitignore
@@ -6,6 +6,9 @@
 
 ## Intellij
 out/
+build/
+storage/build/
+contactsbackup/build/
 /lib/
 .idea/*
 !.idea/runConfigurations*
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 c8ad6aa..01a1026 100644
--- a/app/src/main/java/com/stevesoltys/seedvault/metadata/Metadata.kt
+++ b/app/src/main/java/com/stevesoltys/seedvault/metadata/Metadata.kt
@@ -74,6 +74,7 @@
     internal var time: Long = 0L,
     internal var state: PackageState = UNKNOWN_ERROR,
     internal var backupType: BackupType? = null,
+    internal var size: Long? = null,
     internal val system: Boolean = false,
     internal val version: Long? = null,
     internal val installer: String? = null,
@@ -97,6 +98,7 @@
 internal const val JSON_PACKAGE_TIME = "time"
 internal const val JSON_PACKAGE_BACKUP_TYPE = "backupType"
 internal const val JSON_PACKAGE_STATE = "state"
+internal const val JSON_PACKAGE_SIZE = "size"
 internal const val JSON_PACKAGE_SYSTEM = "system"
 internal const val JSON_PACKAGE_VERSION = "version"
 internal const val JSON_PACKAGE_INSTALLER = "installer"
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 f58a29a..0d72253 100644
--- a/app/src/main/java/com/stevesoltys/seedvault/metadata/MetadataManager.kt
+++ b/app/src/main/java/com/stevesoltys/seedvault/metadata/MetadataManager.kt
@@ -131,6 +131,7 @@
     fun onPackageBackedUp(
         packageInfo: PackageInfo,
         type: BackupType,
+        size: Long?,
         metadataOutputStream: OutputStream,
     ) {
         val packageName = packageInfo.packageName
@@ -143,12 +144,15 @@
                 metadata.packageMetadataMap[packageName]!!.time = now
                 metadata.packageMetadataMap[packageName]!!.state = APK_AND_DATA
                 metadata.packageMetadataMap[packageName]!!.backupType = type
+                // don't override a previous K/V size, if there were no K/V changes
+                if (size != null) metadata.packageMetadataMap[packageName]!!.size = size
             } else {
                 metadata.packageMetadataMap[packageName] = PackageMetadata(
                     time = now,
                     state = APK_AND_DATA,
                     backupType = type,
-                    system = packageInfo.isSystemApp()
+                    size = size,
+                    system = packageInfo.isSystemApp(),
                 )
             }
         }
diff --git a/app/src/main/java/com/stevesoltys/seedvault/metadata/MetadataReader.kt b/app/src/main/java/com/stevesoltys/seedvault/metadata/MetadataReader.kt
index bbd6df1..fe1920b 100644
--- a/app/src/main/java/com/stevesoltys/seedvault/metadata/MetadataReader.kt
+++ b/app/src/main/java/com/stevesoltys/seedvault/metadata/MetadataReader.kt
@@ -120,6 +120,7 @@
                     // because when only backing up the APK for example, there's no type
                     else -> null
                 }
+                val pSize = p.optLong(JSON_PACKAGE_SIZE, -1L)
                 val pSystem = p.optBoolean(JSON_PACKAGE_SYSTEM, false)
                 val pVersion = p.optLong(JSON_PACKAGE_VERSION, 0L)
                 val pInstaller = p.optString(JSON_PACKAGE_INSTALLER)
@@ -136,6 +137,7 @@
                     time = p.getLong(JSON_PACKAGE_TIME),
                     state = pState,
                     backupType = pBackupType,
+                    size = if (pSize < 0L) null else pSize,
                     system = pSystem,
                     version = if (pVersion == 0L) null else pVersion,
                     installer = if (pInstaller == "") null else pInstaller,
diff --git a/app/src/main/java/com/stevesoltys/seedvault/metadata/MetadataWriter.kt b/app/src/main/java/com/stevesoltys/seedvault/metadata/MetadataWriter.kt
index bbed50c..ef9473b 100644
--- a/app/src/main/java/com/stevesoltys/seedvault/metadata/MetadataWriter.kt
+++ b/app/src/main/java/com/stevesoltys/seedvault/metadata/MetadataWriter.kt
@@ -49,6 +49,9 @@
                 if (packageMetadata.backupType != null) {
                     put(JSON_PACKAGE_BACKUP_TYPE, packageMetadata.backupType!!.name)
                 }
+                if (packageMetadata.size != null) {
+                    put(JSON_PACKAGE_SIZE, packageMetadata.size)
+                }
                 if (packageMetadata.system) {
                     put(JSON_PACKAGE_SYSTEM, packageMetadata.system)
                 }
diff --git a/app/src/main/java/com/stevesoltys/seedvault/settings/AppListRetriever.kt b/app/src/main/java/com/stevesoltys/seedvault/settings/AppListRetriever.kt
index e185b79..3bf9445 100644
--- a/app/src/main/java/com/stevesoltys/seedvault/settings/AppListRetriever.kt
+++ b/app/src/main/java/com/stevesoltys/seedvault/settings/AppListRetriever.kt
@@ -38,6 +38,7 @@
     val icon: Drawable,
     val name: String,
     val time: Long,
+    val size: Long?,
     val status: AppBackupState,
     val isSpecial: Boolean = false,
 ) : AppListItem()
@@ -87,6 +88,7 @@
                 icon = getIcon(packageName),
                 name = context.getString(stringId),
                 time = metadata?.time ?: 0,
+                size = metadata?.size,
                 status = status,
                 isSpecial = true
             )
@@ -111,6 +113,7 @@
                 icon = getIcon(it.packageName),
                 name = getAppName(context, it.packageName).toString(),
                 time = time,
+                size = metadata?.size,
                 status = status
             )
         }.sortedBy { it.name.lowercase(locale) }
@@ -125,6 +128,7 @@
                 icon = getIcon(it.packageName),
                 name = getAppName(context, it.packageName).toString(),
                 time = 0,
+                size = null,
                 status = FAILED_NOT_ALLOWED
             )
         }.sortedBy { it.name.lowercase(locale) }
diff --git a/app/src/main/java/com/stevesoltys/seedvault/settings/AppStatusAdapter.kt b/app/src/main/java/com/stevesoltys/seedvault/settings/AppStatusAdapter.kt
index 5536f6b..b4db433 100644
--- a/app/src/main/java/com/stevesoltys/seedvault/settings/AppStatusAdapter.kt
+++ b/app/src/main/java/com/stevesoltys/seedvault/settings/AppStatusAdapter.kt
@@ -3,6 +3,7 @@
 import android.content.Intent
 import android.net.Uri
 import android.provider.Settings.ACTION_APPLICATION_DETAILS_SETTINGS
+import android.text.format.Formatter.formatShortFileSize
 import android.view.LayoutInflater
 import android.view.View
 import android.view.View.GONE
@@ -116,7 +117,12 @@
                     setState(item.status, false)
                 }
                 if (item.status == SUCCEEDED) {
-                    appInfo.text = item.time.toRelativeTime(context)
+                    appInfo.text = if (item.size == null) {
+                        item.time.toRelativeTime(context)
+                    } else {
+                        item.time.toRelativeTime(context).toString() +
+                            " (${formatShortFileSize(v.context, item.size)})"
+                    }
                     appInfo.visibility = VISIBLE
                 }
                 switchView.visibility = INVISIBLE
diff --git a/app/src/main/java/com/stevesoltys/seedvault/transport/backup/ApkBackup.kt b/app/src/main/java/com/stevesoltys/seedvault/transport/backup/ApkBackup.kt
index 02208eb..1942f00 100644
--- a/app/src/main/java/com/stevesoltys/seedvault/transport/backup/ApkBackup.kt
+++ b/app/src/main/java/com/stevesoltys/seedvault/transport/backup/ApkBackup.kt
@@ -55,6 +55,12 @@
         // do not back up when setting is not enabled
         if (!settingsManager.backupApks()) return null
 
+        // do not back up if package is blacklisted
+        if (!settingsManager.isBackupEnabled(packageName)) {
+            Log.d(TAG, "Package $packageName is blacklisted. Not backing it up.")
+            return null
+        }
+
         // do not back up test-only apps as we can't re-install them anyway
         // see: https://commonsware.com/blog/2017/10/31/android-studio-3p0-flag-test-only.html
         if (packageInfo.isTestOnly()) {
diff --git a/app/src/main/java/com/stevesoltys/seedvault/transport/backup/BackupCoordinator.kt b/app/src/main/java/com/stevesoltys/seedvault/transport/backup/BackupCoordinator.kt
index 87a1302..7025501 100644
--- a/app/src/main/java/com/stevesoltys/seedvault/transport/backup/BackupCoordinator.kt
+++ b/app/src/main/java/com/stevesoltys/seedvault/transport/backup/BackupCoordinator.kt
@@ -363,6 +363,7 @@
             // getCurrentPackage() not-null because we have state, call before finishing
             val packageInfo = kv.getCurrentPackage()!!
             val packageName = packageInfo.packageName
+            val size = kv.getCurrentSize()
             // tell K/V backup to finish
             var result = kv.finishBackup()
             if (result == TRANSPORT_OK) {
@@ -370,7 +371,7 @@
                 // call onPackageBackedUp for @pm@ only if we can do backups right now
                 if (!isPmBackup || settingsManager.canDoBackupNow()) {
                     try {
-                        onPackageBackedUp(packageInfo, BackupType.KV)
+                        onPackageBackedUp(packageInfo, BackupType.KV, size)
                     } catch (e: Exception) {
                         Log.e(TAG, "Error calling onPackageBackedUp for $packageName", e)
                         result = TRANSPORT_PACKAGE_REJECTED
@@ -396,10 +397,11 @@
             // getCurrentPackage() not-null because we have state
             val packageInfo = full.getCurrentPackage()!!
             val packageName = packageInfo.packageName
+            val size = full.getCurrentSize()
             // tell full backup to finish
             var result = full.finishBackup()
             try {
-                onPackageBackedUp(packageInfo, BackupType.FULL)
+                onPackageBackedUp(packageInfo, BackupType.FULL, size)
             } catch (e: Exception) {
                 Log.e(TAG, "Error calling onPackageBackedUp for $packageName", e)
                 result = TRANSPORT_PACKAGE_REJECTED
@@ -470,9 +472,9 @@
         }
     }
 
-    private suspend fun onPackageBackedUp(packageInfo: PackageInfo, type: BackupType) {
+    private suspend fun onPackageBackedUp(packageInfo: PackageInfo, type: BackupType, size: Long?) {
         plugin.getMetadataOutputStream().use {
-            metadataManager.onPackageBackedUp(packageInfo, type, it)
+            metadataManager.onPackageBackedUp(packageInfo, type, size, it)
         }
     }
 
diff --git a/app/src/main/java/com/stevesoltys/seedvault/transport/backup/FullBackup.kt b/app/src/main/java/com/stevesoltys/seedvault/transport/backup/FullBackup.kt
index 48a42d6..d44cdeb 100644
--- a/app/src/main/java/com/stevesoltys/seedvault/transport/backup/FullBackup.kt
+++ b/app/src/main/java/com/stevesoltys/seedvault/transport/backup/FullBackup.kt
@@ -51,6 +51,8 @@
 
     fun getCurrentPackage() = state?.packageInfo
 
+    fun getCurrentSize() = state?.size
+
     fun getQuota(): Long {
         return if (settingsManager.isQuotaUnlimited()) Long.MAX_VALUE else DEFAULT_QUOTA_FULL_BACKUP
     }
@@ -190,7 +192,7 @@
     }
 
     fun finishBackup(): Int {
-        Log.i(TAG, "Finish full backup of ${state!!.packageName}.")
+        Log.i(TAG, "Finish full backup of ${state!!.packageName}. Wrote ${state!!.size} bytes")
         return clearState()
     }
 
diff --git a/app/src/main/java/com/stevesoltys/seedvault/transport/backup/KVBackup.kt b/app/src/main/java/com/stevesoltys/seedvault/transport/backup/KVBackup.kt
index 060f543..4467815 100644
--- a/app/src/main/java/com/stevesoltys/seedvault/transport/backup/KVBackup.kt
+++ b/app/src/main/java/com/stevesoltys/seedvault/transport/backup/KVBackup.kt
@@ -46,6 +46,10 @@
 
     fun getCurrentPackage() = state?.packageInfo
 
+    fun getCurrentSize() = getCurrentPackage()?.let {
+        dbManager.getDbSize(it.packageName)
+    }
+
     fun getQuota(): Long = if (settingsManager.isQuotaUnlimited()) {
         Long.MAX_VALUE
     } else {
@@ -252,7 +256,7 @@
                 }
             }
         }
-        Log.d(TAG, "Uploaded db file for $packageName")
+        Log.d(TAG, "Uploaded db file for $packageName.")
     }
 
     private class KVOperation(
diff --git a/app/src/main/java/com/stevesoltys/seedvault/transport/backup/KVDbManager.kt b/app/src/main/java/com/stevesoltys/seedvault/transport/backup/KVDbManager.kt
index 95a026f..2c2253b 100644
--- a/app/src/main/java/com/stevesoltys/seedvault/transport/backup/KVDbManager.kt
+++ b/app/src/main/java/com/stevesoltys/seedvault/transport/backup/KVDbManager.kt
@@ -29,6 +29,11 @@
      * Use only for backup.
      */
     fun existsDb(packageName: String): Boolean
+
+    /**
+     * Returns the current size of the DB in bytes or null, if no DB exists.
+     */
+    fun getDbSize(packageName: String): Long?
     fun deleteDb(packageName: String, isRestore: Boolean = false): Boolean
 }
 
@@ -59,6 +64,11 @@
         return getDbFile(packageName).isFile
     }
 
+    override fun getDbSize(packageName: String): Long? {
+        val file = getDbFile(packageName)
+        return if (file.isFile) file.length() else null
+    }
+
     override fun deleteDb(packageName: String, isRestore: Boolean): Boolean {
         return getDbFile(packageName, isRestore).delete()
     }
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 1a41a02..d127713 100644
--- a/app/src/test/java/com/stevesoltys/seedvault/metadata/MetadataManagerTest.kt
+++ b/app/src/test/java/com/stevesoltys/seedvault/metadata/MetadataManagerTest.kt
@@ -249,6 +249,7 @@
             time = time,
             packageMetadataMap = PackageMetadataMap() // otherwise this isn't copied, but referenced
         )
+        val size = Random.nextLong()
         val packageMetadata = PackageMetadata(time)
         updatedMetadata.packageMetadataMap[packageName] = packageMetadata
 
@@ -256,10 +257,15 @@
         every { clock.time() } returns time
         expectModifyMetadata(initialMetadata)
 
-        manager.onPackageBackedUp(packageInfo, BackupType.FULL, storageOutputStream)
+        manager.onPackageBackedUp(packageInfo, BackupType.FULL, size, storageOutputStream)
 
         assertEquals(
-            packageMetadata.copy(state = APK_AND_DATA, backupType = BackupType.FULL, system = true),
+            packageMetadata.copy(
+                state = APK_AND_DATA,
+                backupType = BackupType.FULL,
+                size = size,
+                system = true,
+            ),
             manager.getPackageMetadata(packageName)
         )
         assertEquals(time, manager.getLastBackupTime())
@@ -270,6 +276,7 @@
             cacheOutputStream.close()
         }
     }
+
     @Test
     fun `test onPackageBackedUp() with D2D enabled`() {
         expectReadFromCache()
@@ -278,7 +285,7 @@
 
         every { settingsManager.d2dBackupsEnabled() } returns true
 
-        manager.onPackageBackedUp(packageInfo, BackupType.FULL, storageOutputStream)
+        manager.onPackageBackedUp(packageInfo, BackupType.FULL, 0L, storageOutputStream)
         assertTrue(initialMetadata.d2dBackup)
 
         verify {
@@ -290,19 +297,20 @@
     @Test
     fun `test onPackageBackedUp() fails to write to storage`() {
         val updateTime = time + 1
+        val size = Random.nextLong()
         val updatedMetadata = initialMetadata.copy(
             time = updateTime,
             packageMetadataMap = PackageMetadataMap() // otherwise this isn't copied, but referenced
         )
         updatedMetadata.packageMetadataMap[packageName] =
-            PackageMetadata(updateTime, APK_AND_DATA, BackupType.KV)
+            PackageMetadata(updateTime, APK_AND_DATA, BackupType.KV, size)
 
         expectReadFromCache()
         every { clock.time() } returns updateTime
         every { metadataWriter.write(updatedMetadata, storageOutputStream) } throws IOException()
 
         try {
-            manager.onPackageBackedUp(packageInfo, BackupType.KV, storageOutputStream)
+            manager.onPackageBackedUp(packageInfo, BackupType.KV, size, storageOutputStream)
             fail()
         } catch (e: IOException) {
             // expected
@@ -335,7 +343,7 @@
         every { clock.time() } returns time
         expectModifyMetadata(updatedMetadata)
 
-        manager.onPackageBackedUp(packageInfo, BackupType.FULL, storageOutputStream)
+        manager.onPackageBackedUp(packageInfo, BackupType.FULL, 0L, storageOutputStream)
 
         assertEquals(time, manager.getLastBackupTime())
         assertEquals(PackageMetadata(time), manager.getPackageMetadata(cachedPackageName))
diff --git a/app/src/test/java/com/stevesoltys/seedvault/metadata/MetadataWriterDecoderTest.kt b/app/src/test/java/com/stevesoltys/seedvault/metadata/MetadataWriterDecoderTest.kt
index 4712551..88a54d2 100644
--- a/app/src/test/java/com/stevesoltys/seedvault/metadata/MetadataWriterDecoderTest.kt
+++ b/app/src/test/java/com/stevesoltys/seedvault/metadata/MetadataWriterDecoderTest.kt
@@ -14,6 +14,7 @@
 import org.junit.jupiter.api.TestInstance
 import org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS
 import kotlin.random.Random
+import kotlin.random.nextLong
 
 @TestInstance(PER_CLASS)
 internal class MetadataWriterDecoderTest {
@@ -81,11 +82,12 @@
                     time = Random.nextLong(),
                     state = QUOTA_EXCEEDED,
                     backupType = BackupType.FULL,
+                    size = Random.nextLong(0..Long.MAX_VALUE),
                     system = Random.nextBoolean(),
                     version = Random.nextLong(),
                     installer = getRandomString(),
                     sha256 = getRandomString(),
-                    signatures = listOf(getRandomString())
+                    signatures = listOf(getRandomString()),
                 )
             )
             put(
@@ -93,22 +95,24 @@
                     time = Random.nextLong(),
                     state = NO_DATA,
                     backupType = BackupType.KV,
+                    size = null,
                     system = Random.nextBoolean(),
                     version = Random.nextLong(),
                     installer = getRandomString(),
                     sha256 = getRandomString(),
-                    signatures = listOf(getRandomString(), getRandomString())
+                    signatures = listOf(getRandomString(), getRandomString()),
                 )
             )
             put(
                 getRandomString(), PackageMetadata(
                     time = 0L,
                     state = NOT_ALLOWED,
+                    size = 0,
                     system = Random.nextBoolean(),
                     version = Random.nextLong(),
                     installer = getRandomString(),
                     sha256 = getRandomString(),
-                    signatures = listOf(getRandomString(), getRandomString())
+                    signatures = listOf(getRandomString(), getRandomString()),
                 )
             )
         }
diff --git a/app/src/test/java/com/stevesoltys/seedvault/restore/install/ApkBackupRestoreTest.kt b/app/src/test/java/com/stevesoltys/seedvault/restore/install/ApkBackupRestoreTest.kt
index f8805c7..f712807 100644
--- a/app/src/test/java/com/stevesoltys/seedvault/restore/install/ApkBackupRestoreTest.kt
+++ b/app/src/test/java/com/stevesoltys/seedvault/restore/install/ApkBackupRestoreTest.kt
@@ -107,6 +107,7 @@
             writeBytes(splitBytes)
         }.absolutePath)
 
+        every { settingsManager.isBackupEnabled(any()) } returns true
         every { settingsManager.backupApks() } returns true
         every { sigInfo.hasMultipleSigners() } returns false
         every { sigInfo.signingCertificateHistory } returns sigs
diff --git a/app/src/test/java/com/stevesoltys/seedvault/transport/CoordinatorIntegrationTest.kt b/app/src/test/java/com/stevesoltys/seedvault/transport/CoordinatorIntegrationTest.kt
index 824230e..0ff406d 100644
--- a/app/src/test/java/com/stevesoltys/seedvault/transport/CoordinatorIntegrationTest.kt
+++ b/app/src/test/java/com/stevesoltys/seedvault/transport/CoordinatorIntegrationTest.kt
@@ -147,7 +147,12 @@
             metadataManager.onApkBackedUp(packageInfo, packageMetadata, metadataOutputStream)
         } just Runs
         every {
-            metadataManager.onPackageBackedUp(packageInfo, BackupType.KV, metadataOutputStream)
+            metadataManager.onPackageBackedUp(
+                packageInfo = packageInfo,
+                type = BackupType.KV,
+                size = more((appData.size + appData2.size).toLong()), // more because DB overhead
+                metadataOutputStream = metadataOutputStream,
+            )
         } just Runs
 
         // start K/V backup
@@ -216,7 +221,12 @@
             backupPlugin.getOutputStream(token, FILE_BACKUP_METADATA)
         } returns metadataOutputStream
         every {
-            metadataManager.onPackageBackedUp(packageInfo, BackupType.KV, metadataOutputStream)
+            metadataManager.onPackageBackedUp(
+                packageInfo = packageInfo,
+                type = BackupType.KV,
+                size = more(size.toLong()), // more than $size, because DB overhead
+                metadataOutputStream = metadataOutputStream,
+            )
         } just Runs
 
         // start K/V backup
@@ -289,7 +299,12 @@
             )
         } just Runs
         every {
-            metadataManager.onPackageBackedUp(packageInfo, BackupType.FULL, metadataOutputStream)
+            metadataManager.onPackageBackedUp(
+                packageInfo = packageInfo,
+                type = BackupType.FULL,
+                size = appData.size.toLong(),
+                metadataOutputStream = metadataOutputStream,
+            )
         } just Runs
 
         // perform backup to output stream
diff --git a/app/src/test/java/com/stevesoltys/seedvault/transport/backup/ApkBackupTest.kt b/app/src/test/java/com/stevesoltys/seedvault/transport/backup/ApkBackupTest.kt
index 2137e56..0cbbf9e 100644
--- a/app/src/test/java/com/stevesoltys/seedvault/transport/backup/ApkBackupTest.kt
+++ b/app/src/test/java/com/stevesoltys/seedvault/transport/backup/ApkBackupTest.kt
@@ -62,6 +62,15 @@
     @Test
     fun `does not back up when setting disabled`() = runBlocking {
         every { settingsManager.backupApks() } returns false
+        every { settingsManager.isBackupEnabled(any()) } returns true
+
+        assertNull(apkBackup.backupApkIfNecessary(packageInfo, UNKNOWN_ERROR, streamGetter))
+    }
+
+    @Test
+    fun `does not back up when app blacklisted`() = runBlocking {
+        every { settingsManager.backupApks() } returns true
+        every { settingsManager.isBackupEnabled(any()) } returns false
 
         assertNull(apkBackup.backupApkIfNecessary(packageInfo, UNKNOWN_ERROR, streamGetter))
     }
@@ -70,8 +79,8 @@
     fun `does not back up test-only apps`() = runBlocking {
         packageInfo.applicationInfo.flags = FLAG_TEST_ONLY
 
+        every { settingsManager.isBackupEnabled(any()) } returns true
         every { settingsManager.backupApks() } returns true
-
         assertNull(apkBackup.backupApkIfNecessary(packageInfo, UNKNOWN_ERROR, streamGetter))
     }
 
@@ -79,8 +88,8 @@
     fun `does not back up system apps`() = runBlocking {
         packageInfo.applicationInfo.flags = FLAG_SYSTEM
 
+        every { settingsManager.isBackupEnabled(any()) } returns true
         every { settingsManager.backupApks() } returns true
-
         assertNull(apkBackup.backupApkIfNecessary(packageInfo, UNKNOWN_ERROR, streamGetter))
     }
 
@@ -112,6 +121,7 @@
     @Test
     fun `do not accept empty signature`() = runBlocking {
         every { settingsManager.backupApks() } returns true
+        every { settingsManager.isBackupEnabled(any()) } returns true
         every {
             metadataManager.getPackageMetadata(packageInfo.packageName)
         } returns packageMetadata
@@ -229,6 +239,7 @@
     }
 
     private fun expectChecks(packageMetadata: PackageMetadata = this.packageMetadata) {
+        every { settingsManager.isBackupEnabled(any()) } returns true
         every { settingsManager.backupApks() } returns true
         every {
             metadataManager.getPackageMetadata(packageInfo.packageName)
diff --git a/app/src/test/java/com/stevesoltys/seedvault/transport/backup/BackupCoordinatorTest.kt b/app/src/test/java/com/stevesoltys/seedvault/transport/backup/BackupCoordinatorTest.kt
index 42c7348..30d2aa1 100644
--- a/app/src/test/java/com/stevesoltys/seedvault/transport/backup/BackupCoordinatorTest.kt
+++ b/app/src/test/java/com/stevesoltys/seedvault/transport/backup/BackupCoordinatorTest.kt
@@ -36,6 +36,7 @@
 import java.io.IOException
 import java.io.OutputStream
 import kotlin.random.Random
+import kotlin.random.nextLong
 
 @Suppress("BlockingMethodInNonBlockingContext")
 internal class BackupCoordinatorTest : BackupTest() {
@@ -204,14 +205,22 @@
 
     @Test
     fun `finish backup delegates to KV plugin if it has state`() = runBlocking {
+        val size = 0L
+
         every { kv.hasState() } returns true
         every { full.hasState() } returns false
         every { kv.getCurrentPackage() } returns packageInfo
         coEvery { kv.finishBackup() } returns TRANSPORT_OK
         every { settingsManager.getToken() } returns token
         coEvery { plugin.getOutputStream(token, FILE_BACKUP_METADATA) } returns metadataOutputStream
+        every { kv.getCurrentSize() } returns size
         every {
-            metadataManager.onPackageBackedUp(packageInfo, BackupType.KV, metadataOutputStream)
+            metadataManager.onPackageBackedUp(
+                packageInfo = packageInfo,
+                type = BackupType.KV,
+                size = size,
+                metadataOutputStream = metadataOutputStream,
+            )
         } just Runs
         every { metadataOutputStream.close() } just Runs
 
@@ -225,6 +234,7 @@
         every { kv.hasState() } returns true
         every { full.hasState() } returns false
         every { kv.getCurrentPackage() } returns pmPackageInfo
+        every { kv.getCurrentSize() } returns 42L
 
         coEvery { kv.finishBackup() } returns TRANSPORT_OK
         every { settingsManager.canDoBackupNow() } returns false
@@ -235,6 +245,7 @@
     @Test
     fun `finish backup delegates to full plugin if it has state`() = runBlocking {
         val result = Random.nextInt()
+        val size: Long? = null
 
         every { kv.hasState() } returns false
         every { full.hasState() } returns true
@@ -242,8 +253,14 @@
         every { full.finishBackup() } returns result
         every { settingsManager.getToken() } returns token
         coEvery { plugin.getOutputStream(token, FILE_BACKUP_METADATA) } returns metadataOutputStream
+        every { full.getCurrentSize() } returns size
         every {
-            metadataManager.onPackageBackedUp(packageInfo, BackupType.FULL, metadataOutputStream)
+            metadataManager.onPackageBackedUp(
+                packageInfo = packageInfo,
+                type = BackupType.FULL,
+                size = size,
+                metadataOutputStream = metadataOutputStream,
+            )
         } just Runs
         every { metadataOutputStream.close() } just Runs
 
@@ -375,6 +392,7 @@
             }
         )
         val packageMetadata: PackageMetadata = mockk()
+        val size = Random.nextLong(1L..Long.MAX_VALUE)
 
         every { settingsManager.canDoBackupNow() } returns true
         every { metadataManager.requiresInit } returns false
@@ -394,8 +412,14 @@
         every { kv.hasState() } returns true
         every { full.hasState() } returns false
         every { kv.getCurrentPackage() } returns pmPackageInfo
+        every { kv.getCurrentSize() } returns size
         every {
-            metadataManager.onPackageBackedUp(pmPackageInfo, BackupType.KV, metadataOutputStream)
+            metadataManager.onPackageBackedUp(
+                pmPackageInfo,
+                BackupType.KV,
+                size,
+                metadataOutputStream,
+            )
         } just Runs
         coEvery { kv.finishBackup() } returns TRANSPORT_OK
 
diff --git a/app/src/test/java/com/stevesoltys/seedvault/transport/backup/TestKvDbManager.kt b/app/src/test/java/com/stevesoltys/seedvault/transport/backup/TestKvDbManager.kt
index 7173f2f..9a31d18 100644
--- a/app/src/test/java/com/stevesoltys/seedvault/transport/backup/TestKvDbManager.kt
+++ b/app/src/test/java/com/stevesoltys/seedvault/transport/backup/TestKvDbManager.kt
@@ -42,6 +42,10 @@
         return db != null
     }
 
+    override fun getDbSize(packageName: String): Long? {
+        return db?.serialize()?.toByteArray()?.size?.toLong()
+    }
+
     override fun deleteDb(packageName: String, isRestore: Boolean): Boolean {
         clearDb()
         return true