Add system app flag to metadata and check before installing system apps
that they are present as an older system app on the restore system. Also ignore system apps without data to backup.
This commit is contained in:
parent
2a4ff39531
commit
74183d40d6
14 changed files with 195 additions and 52 deletions
|
@ -57,6 +57,7 @@ data class PackageMetadata(
|
||||||
*/
|
*/
|
||||||
internal var time: Long = 0L,
|
internal var time: Long = 0L,
|
||||||
internal var state: PackageState = UNKNOWN_ERROR,
|
internal var state: PackageState = UNKNOWN_ERROR,
|
||||||
|
internal val system: Boolean = false,
|
||||||
internal val version: Long? = null,
|
internal val version: Long? = null,
|
||||||
internal val installer: String? = null,
|
internal val installer: String? = null,
|
||||||
internal val sha256: String? = null,
|
internal val sha256: String? = null,
|
||||||
|
@ -69,6 +70,7 @@ data class PackageMetadata(
|
||||||
|
|
||||||
internal const val JSON_PACKAGE_TIME = "time"
|
internal const val JSON_PACKAGE_TIME = "time"
|
||||||
internal const val JSON_PACKAGE_STATE = "state"
|
internal const val JSON_PACKAGE_STATE = "state"
|
||||||
|
internal const val JSON_PACKAGE_SYSTEM = "system"
|
||||||
internal const val JSON_PACKAGE_VERSION = "version"
|
internal const val JSON_PACKAGE_VERSION = "version"
|
||||||
internal const val JSON_PACKAGE_INSTALLER = "installer"
|
internal const val JSON_PACKAGE_INSTALLER = "installer"
|
||||||
internal const val JSON_PACKAGE_SHA256 = "sha256"
|
internal const val JSON_PACKAGE_SHA256 = "sha256"
|
||||||
|
|
|
@ -2,10 +2,14 @@ package com.stevesoltys.seedvault.metadata
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.Context.MODE_PRIVATE
|
import android.content.Context.MODE_PRIVATE
|
||||||
|
import android.content.pm.ApplicationInfo.FLAG_SYSTEM
|
||||||
|
import android.content.pm.ApplicationInfo.FLAG_UPDATED_SYSTEM_APP
|
||||||
|
import android.content.pm.PackageInfo
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import androidx.annotation.VisibleForTesting
|
import androidx.annotation.VisibleForTesting
|
||||||
import androidx.annotation.WorkerThread
|
import androidx.annotation.WorkerThread
|
||||||
import com.stevesoltys.seedvault.Clock
|
import com.stevesoltys.seedvault.Clock
|
||||||
|
import com.stevesoltys.seedvault.MAGIC_PACKAGE_MANAGER
|
||||||
import com.stevesoltys.seedvault.metadata.PackageState.APK_AND_DATA
|
import com.stevesoltys.seedvault.metadata.PackageState.APK_AND_DATA
|
||||||
import com.stevesoltys.seedvault.metadata.PackageState.NOT_ALLOWED
|
import com.stevesoltys.seedvault.metadata.PackageState.NOT_ALLOWED
|
||||||
import java.io.FileNotFoundException
|
import java.io.FileNotFoundException
|
||||||
|
@ -59,7 +63,8 @@ class MetadataManager(
|
||||||
*/
|
*/
|
||||||
@Synchronized
|
@Synchronized
|
||||||
@Throws(IOException::class)
|
@Throws(IOException::class)
|
||||||
fun onApkBackedUp(packageName: String, packageMetadata: PackageMetadata, metadataOutputStream: OutputStream) {
|
fun onApkBackedUp(packageInfo: PackageInfo, packageMetadata: PackageMetadata, metadataOutputStream: OutputStream) {
|
||||||
|
val packageName = packageInfo.packageName
|
||||||
metadata.packageMetadataMap[packageName]?.let {
|
metadata.packageMetadataMap[packageName]?.let {
|
||||||
check(packageMetadata.version != null) {
|
check(packageMetadata.version != null) {
|
||||||
"APK backup returned version null"
|
"APK backup returned version null"
|
||||||
|
@ -78,6 +83,7 @@ class MetadataManager(
|
||||||
modifyMetadata(metadataOutputStream) {
|
modifyMetadata(metadataOutputStream) {
|
||||||
metadata.packageMetadataMap[packageName] = oldPackageMetadata.copy(
|
metadata.packageMetadataMap[packageName] = oldPackageMetadata.copy(
|
||||||
state = newState,
|
state = newState,
|
||||||
|
system = packageInfo.isSystemApp(),
|
||||||
version = packageMetadata.version,
|
version = packageMetadata.version,
|
||||||
installer = packageMetadata.installer,
|
installer = packageMetadata.installer,
|
||||||
sha256 = packageMetadata.sha256,
|
sha256 = packageMetadata.sha256,
|
||||||
|
@ -94,7 +100,8 @@ class MetadataManager(
|
||||||
*/
|
*/
|
||||||
@Synchronized
|
@Synchronized
|
||||||
@Throws(IOException::class)
|
@Throws(IOException::class)
|
||||||
fun onPackageBackedUp(packageName: String, metadataOutputStream: OutputStream) {
|
fun onPackageBackedUp(packageInfo: PackageInfo, metadataOutputStream: OutputStream) {
|
||||||
|
val packageName = packageInfo.packageName
|
||||||
modifyMetadata(metadataOutputStream) {
|
modifyMetadata(metadataOutputStream) {
|
||||||
val now = clock.time()
|
val now = clock.time()
|
||||||
metadata.time = now
|
metadata.time = now
|
||||||
|
@ -104,7 +111,8 @@ class MetadataManager(
|
||||||
} else {
|
} else {
|
||||||
metadata.packageMetadataMap[packageName] = PackageMetadata(
|
metadata.packageMetadataMap[packageName] = PackageMetadata(
|
||||||
time = now,
|
time = now,
|
||||||
state = APK_AND_DATA
|
state = APK_AND_DATA,
|
||||||
|
system = packageInfo.isSystemApp()
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -118,15 +126,17 @@ class MetadataManager(
|
||||||
*/
|
*/
|
||||||
@Synchronized
|
@Synchronized
|
||||||
@Throws(IOException::class)
|
@Throws(IOException::class)
|
||||||
internal fun onPackageBackupError(packageName: String, packageState: PackageState, metadataOutputStream: OutputStream) {
|
internal fun onPackageBackupError(packageInfo: PackageInfo, packageState: PackageState, metadataOutputStream: OutputStream) {
|
||||||
check(packageState != APK_AND_DATA) { "Backup Error with non-error package state." }
|
check(packageState != APK_AND_DATA) { "Backup Error with non-error package state." }
|
||||||
|
val packageName = packageInfo.packageName
|
||||||
modifyMetadata(metadataOutputStream) {
|
modifyMetadata(metadataOutputStream) {
|
||||||
if (metadata.packageMetadataMap.containsKey(packageName)) {
|
if (metadata.packageMetadataMap.containsKey(packageName)) {
|
||||||
metadata.packageMetadataMap[packageName]!!.state = packageState
|
metadata.packageMetadataMap[packageName]!!.state = packageState
|
||||||
} else {
|
} else {
|
||||||
metadata.packageMetadataMap[packageName] = PackageMetadata(
|
metadata.packageMetadataMap[packageName] = PackageMetadata(
|
||||||
time = 0L,
|
time = 0L,
|
||||||
state = packageState
|
state = packageState,
|
||||||
|
system = packageInfo.isSystemApp()
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -194,3 +204,13 @@ class MetadataManager(
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun PackageInfo.isSystemApp(): Boolean {
|
||||||
|
if (packageName == MAGIC_PACKAGE_MANAGER || applicationInfo == null) return true
|
||||||
|
return applicationInfo.flags and FLAG_SYSTEM != 0
|
||||||
|
}
|
||||||
|
|
||||||
|
fun PackageInfo.isUpdatedSystemApp(): Boolean {
|
||||||
|
if (packageName == MAGIC_PACKAGE_MANAGER || applicationInfo == null) return false
|
||||||
|
return applicationInfo.flags and FLAG_UPDATED_SYSTEM_APP != 0
|
||||||
|
}
|
||||||
|
|
|
@ -67,6 +67,7 @@ internal class MetadataReaderImpl(private val crypto: Crypto) : MetadataReader {
|
||||||
NOT_ALLOWED.name -> NOT_ALLOWED
|
NOT_ALLOWED.name -> NOT_ALLOWED
|
||||||
else -> UNKNOWN_ERROR
|
else -> UNKNOWN_ERROR
|
||||||
}
|
}
|
||||||
|
val pSystem = p.optBoolean(JSON_PACKAGE_SYSTEM, false)
|
||||||
val pVersion = p.optLong(JSON_PACKAGE_VERSION, 0L)
|
val pVersion = p.optLong(JSON_PACKAGE_VERSION, 0L)
|
||||||
val pInstaller = p.optString(JSON_PACKAGE_INSTALLER)
|
val pInstaller = p.optString(JSON_PACKAGE_INSTALLER)
|
||||||
val pSha256 = p.optString(JSON_PACKAGE_SHA256)
|
val pSha256 = p.optString(JSON_PACKAGE_SHA256)
|
||||||
|
@ -80,6 +81,7 @@ internal class MetadataReaderImpl(private val crypto: Crypto) : MetadataReader {
|
||||||
packageMetadataMap[packageName] = PackageMetadata(
|
packageMetadataMap[packageName] = PackageMetadata(
|
||||||
time = p.getLong(JSON_PACKAGE_TIME),
|
time = p.getLong(JSON_PACKAGE_TIME),
|
||||||
state = pState,
|
state = pState,
|
||||||
|
system = pSystem,
|
||||||
version = if (pVersion == 0L) null else pVersion,
|
version = if (pVersion == 0L) null else pVersion,
|
||||||
installer = if (pInstaller == "") null else pInstaller,
|
installer = if (pInstaller == "") null else pInstaller,
|
||||||
sha256 = if (pSha256 == "") null else pSha256,
|
sha256 = if (pSha256 == "") null else pSha256,
|
||||||
|
|
|
@ -40,6 +40,9 @@ internal class MetadataWriterImpl(private val crypto: Crypto) : MetadataWriter {
|
||||||
if (packageMetadata.state != APK_AND_DATA) {
|
if (packageMetadata.state != APK_AND_DATA) {
|
||||||
put(JSON_PACKAGE_STATE, packageMetadata.state.name)
|
put(JSON_PACKAGE_STATE, packageMetadata.state.name)
|
||||||
}
|
}
|
||||||
|
if (packageMetadata.system) {
|
||||||
|
put(JSON_PACKAGE_SYSTEM, packageMetadata.system)
|
||||||
|
}
|
||||||
packageMetadata.version?.let { put(JSON_PACKAGE_VERSION, it) }
|
packageMetadata.version?.let { put(JSON_PACKAGE_VERSION, it) }
|
||||||
packageMetadata.installer?.let { put(JSON_PACKAGE_INSTALLER, it) }
|
packageMetadata.installer?.let { put(JSON_PACKAGE_INSTALLER, it) }
|
||||||
packageMetadata.sha256?.let { put(JSON_PACKAGE_SHA256, it) }
|
packageMetadata.sha256?.let { put(JSON_PACKAGE_SHA256, it) }
|
||||||
|
|
|
@ -1,7 +1,5 @@
|
||||||
package com.stevesoltys.seedvault.transport.backup
|
package com.stevesoltys.seedvault.transport.backup
|
||||||
|
|
||||||
import android.content.pm.ApplicationInfo.FLAG_SYSTEM
|
|
||||||
import android.content.pm.ApplicationInfo.FLAG_UPDATED_SYSTEM_APP
|
|
||||||
import android.content.pm.PackageInfo
|
import android.content.pm.PackageInfo
|
||||||
import android.content.pm.PackageManager
|
import android.content.pm.PackageManager
|
||||||
import android.content.pm.Signature
|
import android.content.pm.Signature
|
||||||
|
@ -10,9 +8,7 @@ import android.util.Log
|
||||||
import android.util.PackageUtils.computeSha256DigestBytes
|
import android.util.PackageUtils.computeSha256DigestBytes
|
||||||
import com.stevesoltys.seedvault.MAGIC_PACKAGE_MANAGER
|
import com.stevesoltys.seedvault.MAGIC_PACKAGE_MANAGER
|
||||||
import com.stevesoltys.seedvault.encodeBase64
|
import com.stevesoltys.seedvault.encodeBase64
|
||||||
import com.stevesoltys.seedvault.metadata.MetadataManager
|
import com.stevesoltys.seedvault.metadata.*
|
||||||
import com.stevesoltys.seedvault.metadata.PackageMetadata
|
|
||||||
import com.stevesoltys.seedvault.metadata.PackageState
|
|
||||||
import com.stevesoltys.seedvault.settings.SettingsManager
|
import com.stevesoltys.seedvault.settings.SettingsManager
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import java.io.FileNotFoundException
|
import java.io.FileNotFoundException
|
||||||
|
@ -45,9 +41,7 @@ class ApkBackup(
|
||||||
if (!settingsManager.backupApks()) return null
|
if (!settingsManager.backupApks()) return null
|
||||||
|
|
||||||
// do not back up system apps that haven't been updated
|
// do not back up system apps that haven't been updated
|
||||||
val isSystemApp = packageInfo.applicationInfo.flags and FLAG_SYSTEM != 0
|
if (packageInfo.isSystemApp() && !packageInfo.isUpdatedSystemApp()) {
|
||||||
val isUpdatedSystemApp = packageInfo.applicationInfo.flags and FLAG_UPDATED_SYSTEM_APP != 0
|
|
||||||
if (isSystemApp && !isUpdatedSystemApp) {
|
|
||||||
Log.d(TAG, "Package $packageName is vanilla system app. Not backing it up.")
|
Log.d(TAG, "Package $packageName is vanilla system app. Not backing it up.")
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,6 +11,7 @@ import com.stevesoltys.seedvault.MAGIC_PACKAGE_MANAGER
|
||||||
import com.stevesoltys.seedvault.metadata.MetadataManager
|
import com.stevesoltys.seedvault.metadata.MetadataManager
|
||||||
import com.stevesoltys.seedvault.metadata.PackageState
|
import com.stevesoltys.seedvault.metadata.PackageState
|
||||||
import com.stevesoltys.seedvault.metadata.PackageState.*
|
import com.stevesoltys.seedvault.metadata.PackageState.*
|
||||||
|
import com.stevesoltys.seedvault.metadata.isSystemApp
|
||||||
import com.stevesoltys.seedvault.settings.SettingsManager
|
import com.stevesoltys.seedvault.settings.SettingsManager
|
||||||
import java.io.IOException
|
import java.io.IOException
|
||||||
import java.util.concurrent.TimeUnit.DAYS
|
import java.util.concurrent.TimeUnit.DAYS
|
||||||
|
@ -262,7 +263,7 @@ internal class BackupCoordinator(
|
||||||
plugin.getApkOutputStream(packageInfo)
|
plugin.getApkOutputStream(packageInfo)
|
||||||
}?.let { packageMetadata ->
|
}?.let { packageMetadata ->
|
||||||
val outputStream = plugin.getMetadataOutputStream()
|
val outputStream = plugin.getMetadataOutputStream()
|
||||||
metadataManager.onApkBackedUp(packageName, packageMetadata, outputStream)
|
metadataManager.onApkBackedUp(packageInfo, packageMetadata, outputStream)
|
||||||
}
|
}
|
||||||
result
|
result
|
||||||
} catch (e: IOException) {
|
} catch (e: IOException) {
|
||||||
|
@ -275,17 +276,19 @@ internal class BackupCoordinator(
|
||||||
val packageName = packageInfo.packageName
|
val packageName = packageInfo.packageName
|
||||||
try {
|
try {
|
||||||
val outputStream = plugin.getMetadataOutputStream()
|
val outputStream = plugin.getMetadataOutputStream()
|
||||||
metadataManager.onPackageBackedUp(packageName, outputStream)
|
metadataManager.onPackageBackedUp(packageInfo, outputStream)
|
||||||
} catch (e: IOException) {
|
} catch (e: IOException) {
|
||||||
Log.e(TAG, "Error while writing metadata for $packageName", e)
|
Log.e(TAG, "Error while writing metadata for $packageName", e)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun onPackageBackupError(packageInfo: PackageInfo) {
|
private fun onPackageBackupError(packageInfo: PackageInfo) {
|
||||||
|
// don't bother with system apps that have no data
|
||||||
|
if (cancelReason == NO_DATA && packageInfo.isSystemApp()) return
|
||||||
val packageName = packageInfo.packageName
|
val packageName = packageInfo.packageName
|
||||||
try {
|
try {
|
||||||
val outputStream = plugin.getMetadataOutputStream()
|
val outputStream = plugin.getMetadataOutputStream()
|
||||||
metadataManager.onPackageBackupError(packageName, cancelReason, outputStream)
|
metadataManager.onPackageBackupError(packageInfo, cancelReason, outputStream)
|
||||||
} catch (e: IOException) {
|
} catch (e: IOException) {
|
||||||
Log.e(TAG, "Error while writing metadata for $packageName", e)
|
Log.e(TAG, "Error while writing metadata for $packageName", e)
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,13 +1,13 @@
|
||||||
package com.stevesoltys.seedvault.transport.restore
|
package com.stevesoltys.seedvault.transport.restore
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.pm.PackageManager.GET_SIGNATURES
|
import android.content.pm.PackageManager.*
|
||||||
import android.content.pm.PackageManager.GET_SIGNING_CERTIFICATES
|
|
||||||
import android.graphics.drawable.Drawable
|
import android.graphics.drawable.Drawable
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import com.stevesoltys.seedvault.encodeBase64
|
import com.stevesoltys.seedvault.encodeBase64
|
||||||
import com.stevesoltys.seedvault.metadata.PackageMetadata
|
import com.stevesoltys.seedvault.metadata.PackageMetadata
|
||||||
import com.stevesoltys.seedvault.metadata.PackageMetadataMap
|
import com.stevesoltys.seedvault.metadata.PackageMetadataMap
|
||||||
|
import com.stevesoltys.seedvault.metadata.isSystemApp
|
||||||
import com.stevesoltys.seedvault.transport.backup.getSignatures
|
import com.stevesoltys.seedvault.transport.backup.getSignatures
|
||||||
import com.stevesoltys.seedvault.transport.restore.ApkRestoreStatus.*
|
import com.stevesoltys.seedvault.transport.restore.ApkRestoreStatus.*
|
||||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||||
|
@ -124,6 +124,20 @@ internal class ApkRestore(
|
||||||
installResult.update(packageName) { it.copy(status = IN_PROGRESS, name = name, icon = icon) }
|
installResult.update(packageName) { it.copy(status = IN_PROGRESS, name = name, icon = icon) }
|
||||||
emit(installResult)
|
emit(installResult)
|
||||||
|
|
||||||
|
// ensure system apps are actually installed and newer system apps as well
|
||||||
|
if (metadata.system) {
|
||||||
|
try {
|
||||||
|
val installedPackageInfo = pm.getPackageInfo(packageName, 0)
|
||||||
|
// metadata.version is not null, because here hasApk() must be true
|
||||||
|
val isOlder = metadata.version!! <= installedPackageInfo.longVersionCode
|
||||||
|
if (isOlder || !installedPackageInfo.isSystemApp()) throw NameNotFoundException()
|
||||||
|
} catch (e: NameNotFoundException) {
|
||||||
|
Log.w(TAG, "Not installing $packageName because older or not a system app here.")
|
||||||
|
fail(installResult, packageName)
|
||||||
|
return@flow
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// install APK and emit updates from it
|
// install APK and emit updates from it
|
||||||
apkInstaller.install(cachedApk, packageName, metadata.installer, installResult).collect { result ->
|
apkInstaller.install(cachedApk, packageName, metadata.installer, installResult).collect { result ->
|
||||||
emit(result)
|
emit(result)
|
||||||
|
|
|
@ -2,6 +2,10 @@ package com.stevesoltys.seedvault.metadata
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.Context.MODE_PRIVATE
|
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 androidx.test.ext.junit.runners.AndroidJUnit4
|
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||||
import com.stevesoltys.seedvault.Clock
|
import com.stevesoltys.seedvault.Clock
|
||||||
import com.stevesoltys.seedvault.getRandomByteArray
|
import com.stevesoltys.seedvault.getRandomByteArray
|
||||||
|
@ -33,6 +37,10 @@ class MetadataManagerTest {
|
||||||
private val time = 42L
|
private val time = 42L
|
||||||
private val token = Random.nextLong()
|
private val token = Random.nextLong()
|
||||||
private val packageName = getRandomString()
|
private val packageName = getRandomString()
|
||||||
|
private val packageInfo = PackageInfo().apply {
|
||||||
|
packageName = this@MetadataManagerTest.packageName
|
||||||
|
applicationInfo = ApplicationInfo().apply { flags = FLAG_ALLOW_BACKUP }
|
||||||
|
}
|
||||||
private val initialMetadata = BackupMetadata(token = token)
|
private val initialMetadata = BackupMetadata(token = token)
|
||||||
private val storageOutputStream = ByteArrayOutputStream()
|
private val storageOutputStream = ByteArrayOutputStream()
|
||||||
private val cacheOutputStream: FileOutputStream = mockk()
|
private val cacheOutputStream: FileOutputStream = mockk()
|
||||||
|
@ -68,11 +76,29 @@ class MetadataManagerTest {
|
||||||
expectReadFromCache()
|
expectReadFromCache()
|
||||||
expectModifyMetadata(initialMetadata)
|
expectModifyMetadata(initialMetadata)
|
||||||
|
|
||||||
manager.onApkBackedUp(packageName, packageMetadata, storageOutputStream)
|
manager.onApkBackedUp(packageInfo, packageMetadata, storageOutputStream)
|
||||||
|
|
||||||
assertEquals(packageMetadata, manager.getPackageMetadata(packageName))
|
assertEquals(packageMetadata, manager.getPackageMetadata(packageName))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `test onApkBackedUp() sets system metadata`() {
|
||||||
|
packageInfo.applicationInfo = ApplicationInfo().apply { flags = FLAG_SYSTEM }
|
||||||
|
val packageMetadata = PackageMetadata(
|
||||||
|
time = 0L,
|
||||||
|
version = Random.nextLong(Long.MAX_VALUE),
|
||||||
|
installer = getRandomString(),
|
||||||
|
signatures = listOf("sig")
|
||||||
|
)
|
||||||
|
|
||||||
|
expectReadFromCache()
|
||||||
|
expectModifyMetadata(initialMetadata)
|
||||||
|
|
||||||
|
manager.onApkBackedUp(packageInfo, packageMetadata, storageOutputStream)
|
||||||
|
|
||||||
|
assertEquals(packageMetadata.copy(system = true), manager.getPackageMetadata(packageName))
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `test onApkBackedUp() with existing package metadata`() {
|
fun `test onApkBackedUp() with existing package metadata`() {
|
||||||
val packageMetadata = PackageMetadata(
|
val packageMetadata = PackageMetadata(
|
||||||
|
@ -92,7 +118,7 @@ class MetadataManagerTest {
|
||||||
expectReadFromCache()
|
expectReadFromCache()
|
||||||
expectModifyMetadata(initialMetadata)
|
expectModifyMetadata(initialMetadata)
|
||||||
|
|
||||||
manager.onApkBackedUp(packageName, updatedPackageMetadata, storageOutputStream)
|
manager.onApkBackedUp(packageInfo, updatedPackageMetadata, storageOutputStream)
|
||||||
|
|
||||||
assertEquals(updatedPackageMetadata, manager.getPackageMetadata(packageName))
|
assertEquals(updatedPackageMetadata, manager.getPackageMetadata(packageName))
|
||||||
}
|
}
|
||||||
|
@ -112,53 +138,60 @@ class MetadataManagerTest {
|
||||||
|
|
||||||
// state doesn't change for APK_AND_DATA
|
// state doesn't change for APK_AND_DATA
|
||||||
packageMetadata = packageMetadata.copy(version = ++version, state = APK_AND_DATA)
|
packageMetadata = packageMetadata.copy(version = ++version, state = APK_AND_DATA)
|
||||||
manager.onApkBackedUp(packageName, packageMetadata, storageOutputStream)
|
manager.onApkBackedUp(packageInfo, packageMetadata, storageOutputStream)
|
||||||
assertEquals(packageMetadata.copy(state = oldState), manager.getPackageMetadata(packageName))
|
assertEquals(packageMetadata.copy(state = oldState), manager.getPackageMetadata(packageName))
|
||||||
|
|
||||||
// state doesn't change for QUOTA_EXCEEDED
|
// state doesn't change for QUOTA_EXCEEDED
|
||||||
packageMetadata = packageMetadata.copy(version = ++version, state = QUOTA_EXCEEDED)
|
packageMetadata = packageMetadata.copy(version = ++version, state = QUOTA_EXCEEDED)
|
||||||
manager.onApkBackedUp(packageName, packageMetadata, storageOutputStream)
|
manager.onApkBackedUp(packageInfo, packageMetadata, storageOutputStream)
|
||||||
assertEquals(packageMetadata.copy(state = oldState), manager.getPackageMetadata(packageName))
|
assertEquals(packageMetadata.copy(state = oldState), manager.getPackageMetadata(packageName))
|
||||||
|
|
||||||
// state doesn't change for NO_DATA
|
// state doesn't change for NO_DATA
|
||||||
packageMetadata = packageMetadata.copy(version = ++version, state = NO_DATA)
|
packageMetadata = packageMetadata.copy(version = ++version, state = NO_DATA)
|
||||||
manager.onApkBackedUp(packageName, packageMetadata, storageOutputStream)
|
manager.onApkBackedUp(packageInfo, packageMetadata, storageOutputStream)
|
||||||
assertEquals(packageMetadata.copy(state = oldState), manager.getPackageMetadata(packageName))
|
assertEquals(packageMetadata.copy(state = oldState), manager.getPackageMetadata(packageName))
|
||||||
|
|
||||||
// state DOES change for NOT_ALLOWED
|
// state DOES change for NOT_ALLOWED
|
||||||
packageMetadata = packageMetadata.copy(version = ++version, state = NOT_ALLOWED)
|
packageMetadata = packageMetadata.copy(version = ++version, state = NOT_ALLOWED)
|
||||||
manager.onApkBackedUp(packageName, packageMetadata, storageOutputStream)
|
manager.onApkBackedUp(packageInfo, packageMetadata, storageOutputStream)
|
||||||
assertEquals(packageMetadata.copy(state = NOT_ALLOWED), manager.getPackageMetadata(packageName))
|
assertEquals(packageMetadata.copy(state = NOT_ALLOWED), manager.getPackageMetadata(packageName))
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `test onPackageBackedUp()`() {
|
fun `test onPackageBackedUp()`() {
|
||||||
val updatedMetadata = initialMetadata.copy()
|
packageInfo.applicationInfo.flags = FLAG_SYSTEM
|
||||||
updatedMetadata.time = time
|
val updatedMetadata = initialMetadata.copy(
|
||||||
updatedMetadata.packageMetadataMap[packageName] = PackageMetadata(time)
|
time = time,
|
||||||
|
packageMetadataMap = PackageMetadataMap() // otherwise this isn't copied, but referenced
|
||||||
|
)
|
||||||
|
val packageMetadata = PackageMetadata(time)
|
||||||
|
updatedMetadata.packageMetadataMap[packageName] = packageMetadata
|
||||||
|
|
||||||
expectReadFromCache()
|
expectReadFromCache()
|
||||||
every { clock.time() } returns time
|
every { clock.time() } returns time
|
||||||
expectModifyMetadata(updatedMetadata)
|
expectModifyMetadata(initialMetadata)
|
||||||
|
|
||||||
manager.onPackageBackedUp(packageName, storageOutputStream)
|
manager.onPackageBackedUp(packageInfo, storageOutputStream)
|
||||||
|
|
||||||
|
assertEquals(packageMetadata.copy(state = APK_AND_DATA, system = true), manager.getPackageMetadata(packageName))
|
||||||
assertEquals(time, manager.getLastBackupTime())
|
assertEquals(time, manager.getLastBackupTime())
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `test onPackageBackedUp() fails to write to storage`() {
|
fun `test onPackageBackedUp() fails to write to storage`() {
|
||||||
val updateTime = time + 1
|
val updateTime = time + 1
|
||||||
val updatedMetadata = initialMetadata.copy()
|
val updatedMetadata = initialMetadata.copy(
|
||||||
updatedMetadata.time = updateTime
|
time = updateTime,
|
||||||
updatedMetadata.packageMetadataMap[packageName] = PackageMetadata(updateTime)
|
packageMetadataMap = PackageMetadataMap() // otherwise this isn't copied, but referenced
|
||||||
|
)
|
||||||
|
updatedMetadata.packageMetadataMap[packageName] = PackageMetadata(updateTime, APK_AND_DATA)
|
||||||
|
|
||||||
expectReadFromCache()
|
expectReadFromCache()
|
||||||
every { clock.time() } returns updateTime
|
every { clock.time() } returns updateTime
|
||||||
every { metadataWriter.write(updatedMetadata, storageOutputStream) } throws IOException()
|
every { metadataWriter.write(updatedMetadata, storageOutputStream) } throws IOException()
|
||||||
|
|
||||||
try {
|
try {
|
||||||
manager.onPackageBackedUp(packageName, storageOutputStream)
|
manager.onPackageBackedUp(packageInfo, storageOutputStream)
|
||||||
fail()
|
fail()
|
||||||
} catch (e: IOException) {
|
} catch (e: IOException) {
|
||||||
// expected
|
// expected
|
||||||
|
@ -185,7 +218,7 @@ class MetadataManagerTest {
|
||||||
every { clock.time() } returns time
|
every { clock.time() } returns time
|
||||||
expectModifyMetadata(updatedMetadata)
|
expectModifyMetadata(updatedMetadata)
|
||||||
|
|
||||||
manager.onPackageBackedUp(packageName, storageOutputStream)
|
manager.onPackageBackedUp(packageInfo, storageOutputStream)
|
||||||
|
|
||||||
assertEquals(time, manager.getLastBackupTime())
|
assertEquals(time, manager.getLastBackupTime())
|
||||||
assertEquals(PackageMetadata(time), manager.getPackageMetadata(cachedPackageName))
|
assertEquals(PackageMetadata(time), manager.getPackageMetadata(cachedPackageName))
|
||||||
|
|
|
@ -127,6 +127,17 @@ class MetadataReaderTest {
|
||||||
assertEquals(UNKNOWN_ERROR, metadata.packageMetadataMap["org.example"]!!.state)
|
assertEquals(UNKNOWN_ERROR, metadata.packageMetadataMap["org.example"]!!.state)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `package metadata missing system gets mapped to false`() {
|
||||||
|
val json = JSONObject(metadataByteArray.toString(Utf8))
|
||||||
|
json.put("org.example", JSONObject().apply {
|
||||||
|
put(JSON_PACKAGE_TIME, Random.nextLong())
|
||||||
|
})
|
||||||
|
val jsonBytes = json.toString().toByteArray(Utf8)
|
||||||
|
val metadata = decoder.decode(jsonBytes, metadata.version, metadata.token)
|
||||||
|
assertFalse(metadata.packageMetadataMap["org.example"]!!.system)
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `package metadata can only include time`() {
|
fun `package metadata can only include time`() {
|
||||||
val json = JSONObject(metadataByteArray.toString(Utf8))
|
val json = JSONObject(metadataByteArray.toString(Utf8))
|
||||||
|
|
|
@ -55,6 +55,7 @@ internal class MetadataWriterDecoderTest {
|
||||||
put(getRandomString(), PackageMetadata(
|
put(getRandomString(), PackageMetadata(
|
||||||
time = Random.nextLong(),
|
time = Random.nextLong(),
|
||||||
state = QUOTA_EXCEEDED,
|
state = QUOTA_EXCEEDED,
|
||||||
|
system = Random.nextBoolean(),
|
||||||
version = Random.nextLong(),
|
version = Random.nextLong(),
|
||||||
installer = getRandomString(),
|
installer = getRandomString(),
|
||||||
sha256 = getRandomString(),
|
sha256 = getRandomString(),
|
||||||
|
@ -63,6 +64,7 @@ internal class MetadataWriterDecoderTest {
|
||||||
put(getRandomString(), PackageMetadata(
|
put(getRandomString(), PackageMetadata(
|
||||||
time = Random.nextLong(),
|
time = Random.nextLong(),
|
||||||
state = NO_DATA,
|
state = NO_DATA,
|
||||||
|
system = Random.nextBoolean(),
|
||||||
version = Random.nextLong(),
|
version = Random.nextLong(),
|
||||||
installer = getRandomString(),
|
installer = getRandomString(),
|
||||||
sha256 = getRandomString(),
|
sha256 = getRandomString(),
|
||||||
|
@ -71,6 +73,7 @@ internal class MetadataWriterDecoderTest {
|
||||||
put(getRandomString(), PackageMetadata(
|
put(getRandomString(), PackageMetadata(
|
||||||
time = 0L,
|
time = 0L,
|
||||||
state = NOT_ALLOWED,
|
state = NOT_ALLOWED,
|
||||||
|
system = Random.nextBoolean(),
|
||||||
version = Random.nextLong(),
|
version = Random.nextLong(),
|
||||||
installer = getRandomString(),
|
installer = getRandomString(),
|
||||||
sha256 = getRandomString(),
|
sha256 = getRandomString(),
|
||||||
|
|
|
@ -98,8 +98,8 @@ internal class CoordinatorIntegrationTest : TransportTest() {
|
||||||
every { kvBackupPlugin.getOutputStreamForRecord(packageInfo, key264) } returns bOutputStream2
|
every { kvBackupPlugin.getOutputStreamForRecord(packageInfo, key264) } returns bOutputStream2
|
||||||
every { apkBackup.backupApkIfNecessary(packageInfo, UNKNOWN_ERROR, any()) } returns packageMetadata
|
every { apkBackup.backupApkIfNecessary(packageInfo, UNKNOWN_ERROR, any()) } returns packageMetadata
|
||||||
every { backupPlugin.getMetadataOutputStream() } returns metadataOutputStream
|
every { backupPlugin.getMetadataOutputStream() } returns metadataOutputStream
|
||||||
every { metadataManager.onApkBackedUp(packageInfo.packageName, packageMetadata, metadataOutputStream) } just Runs
|
every { metadataManager.onApkBackedUp(packageInfo, packageMetadata, metadataOutputStream) } just Runs
|
||||||
every { metadataManager.onPackageBackedUp(packageInfo.packageName, metadataOutputStream) } just Runs
|
every { metadataManager.onPackageBackedUp(packageInfo, metadataOutputStream) } just Runs
|
||||||
|
|
||||||
// start and finish K/V backup
|
// start and finish K/V backup
|
||||||
assertEquals(TRANSPORT_OK, backup.performIncrementalBackup(packageInfo, fileDescriptor, 0))
|
assertEquals(TRANSPORT_OK, backup.performIncrementalBackup(packageInfo, fileDescriptor, 0))
|
||||||
|
@ -152,7 +152,7 @@ internal class CoordinatorIntegrationTest : TransportTest() {
|
||||||
every { kvBackupPlugin.getOutputStreamForRecord(packageInfo, key64) } returns bOutputStream
|
every { kvBackupPlugin.getOutputStreamForRecord(packageInfo, key64) } returns bOutputStream
|
||||||
every { apkBackup.backupApkIfNecessary(packageInfo, UNKNOWN_ERROR, any()) } returns null
|
every { apkBackup.backupApkIfNecessary(packageInfo, UNKNOWN_ERROR, any()) } returns null
|
||||||
every { backupPlugin.getMetadataOutputStream() } returns metadataOutputStream
|
every { backupPlugin.getMetadataOutputStream() } returns metadataOutputStream
|
||||||
every { metadataManager.onPackageBackedUp(packageInfo.packageName, metadataOutputStream) } just Runs
|
every { metadataManager.onPackageBackedUp(packageInfo, metadataOutputStream) } just Runs
|
||||||
|
|
||||||
// start and finish K/V backup
|
// start and finish K/V backup
|
||||||
assertEquals(TRANSPORT_OK, backup.performIncrementalBackup(packageInfo, fileDescriptor, 0))
|
assertEquals(TRANSPORT_OK, backup.performIncrementalBackup(packageInfo, fileDescriptor, 0))
|
||||||
|
@ -190,8 +190,8 @@ internal class CoordinatorIntegrationTest : TransportTest() {
|
||||||
every { fullBackupPlugin.getQuota() } returns DEFAULT_QUOTA_FULL_BACKUP
|
every { fullBackupPlugin.getQuota() } returns DEFAULT_QUOTA_FULL_BACKUP
|
||||||
every { apkBackup.backupApkIfNecessary(packageInfo, UNKNOWN_ERROR, any()) } returns packageMetadata
|
every { apkBackup.backupApkIfNecessary(packageInfo, UNKNOWN_ERROR, any()) } returns packageMetadata
|
||||||
every { backupPlugin.getMetadataOutputStream() } returns metadataOutputStream
|
every { backupPlugin.getMetadataOutputStream() } returns metadataOutputStream
|
||||||
every { metadataManager.onApkBackedUp(packageInfo.packageName, packageMetadata, metadataOutputStream) } just Runs
|
every { metadataManager.onApkBackedUp(packageInfo, packageMetadata, metadataOutputStream) } just Runs
|
||||||
every { metadataManager.onPackageBackedUp(packageInfo.packageName, metadataOutputStream) } just Runs
|
every { metadataManager.onPackageBackedUp(packageInfo, metadataOutputStream) } just Runs
|
||||||
|
|
||||||
// perform backup to output stream
|
// perform backup to output stream
|
||||||
assertEquals(TRANSPORT_OK, backup.performFullBackup(packageInfo, fileDescriptor, 0))
|
assertEquals(TRANSPORT_OK, backup.performFullBackup(packageInfo, fileDescriptor, 0))
|
||||||
|
|
|
@ -118,7 +118,7 @@ internal class ApkBackupTest : BackupTest() {
|
||||||
expectChecks()
|
expectChecks()
|
||||||
every { streamGetter.invoke() } returns apkOutputStream
|
every { streamGetter.invoke() } returns apkOutputStream
|
||||||
every { pm.getInstallerPackageName(packageInfo.packageName) } returns updatedMetadata.installer
|
every { pm.getInstallerPackageName(packageInfo.packageName) } returns updatedMetadata.installer
|
||||||
every { metadataManager.onApkBackedUp(packageInfo.packageName, updatedMetadata, outputStream) } just Runs
|
every { metadataManager.onApkBackedUp(packageInfo, updatedMetadata, outputStream) } just Runs
|
||||||
|
|
||||||
assertEquals(updatedMetadata, apkBackup.backupApkIfNecessary(packageInfo, UNKNOWN_ERROR, streamGetter))
|
assertEquals(updatedMetadata, apkBackup.backupApkIfNecessary(packageInfo, UNKNOWN_ERROR, streamGetter))
|
||||||
assertArrayEquals(apkBytes, apkOutputStream.toByteArray())
|
assertArrayEquals(apkBytes, apkOutputStream.toByteArray())
|
||||||
|
|
|
@ -147,7 +147,7 @@ internal class BackupCoordinatorTest : BackupTest() {
|
||||||
every { full.hasState() } returns false
|
every { full.hasState() } returns false
|
||||||
every { kv.getCurrentPackage() } returns packageInfo
|
every { kv.getCurrentPackage() } returns packageInfo
|
||||||
every { plugin.getMetadataOutputStream() } returns metadataOutputStream
|
every { plugin.getMetadataOutputStream() } returns metadataOutputStream
|
||||||
every { metadataManager.onPackageBackedUp(packageInfo.packageName, metadataOutputStream) } just Runs
|
every { metadataManager.onPackageBackedUp(packageInfo, metadataOutputStream) } just Runs
|
||||||
every { kv.finishBackup() } returns result
|
every { kv.finishBackup() } returns result
|
||||||
|
|
||||||
assertEquals(result, backup.finishBackup())
|
assertEquals(result, backup.finishBackup())
|
||||||
|
@ -161,7 +161,7 @@ internal class BackupCoordinatorTest : BackupTest() {
|
||||||
every { full.hasState() } returns true
|
every { full.hasState() } returns true
|
||||||
every { full.getCurrentPackage() } returns packageInfo
|
every { full.getCurrentPackage() } returns packageInfo
|
||||||
every { plugin.getMetadataOutputStream() } returns metadataOutputStream
|
every { plugin.getMetadataOutputStream() } returns metadataOutputStream
|
||||||
every { metadataManager.onPackageBackedUp(packageInfo.packageName, metadataOutputStream) } just Runs
|
every { metadataManager.onPackageBackedUp(packageInfo, metadataOutputStream) } just Runs
|
||||||
every { full.finishBackup() } returns result
|
every { full.finishBackup() } returns result
|
||||||
|
|
||||||
assertEquals(result, backup.finishBackup())
|
assertEquals(result, backup.finishBackup())
|
||||||
|
@ -182,7 +182,7 @@ internal class BackupCoordinatorTest : BackupTest() {
|
||||||
every { full.getQuota() } returns DEFAULT_QUOTA_FULL_BACKUP
|
every { full.getQuota() } returns DEFAULT_QUOTA_FULL_BACKUP
|
||||||
every { full.checkFullBackupSize(DEFAULT_QUOTA_FULL_BACKUP + 1) } returns TRANSPORT_QUOTA_EXCEEDED
|
every { full.checkFullBackupSize(DEFAULT_QUOTA_FULL_BACKUP + 1) } returns TRANSPORT_QUOTA_EXCEEDED
|
||||||
every { full.getCurrentPackage() } returns packageInfo
|
every { full.getCurrentPackage() } returns packageInfo
|
||||||
every { metadataManager.onPackageBackupError(packageInfo.packageName, QUOTA_EXCEEDED, metadataOutputStream) } just Runs
|
every { metadataManager.onPackageBackupError(packageInfo, QUOTA_EXCEEDED, metadataOutputStream) } just Runs
|
||||||
every { full.cancelFullBackup() } just Runs
|
every { full.cancelFullBackup() } just Runs
|
||||||
every { settingsManager.getStorage() } returns storage
|
every { settingsManager.getStorage() } returns storage
|
||||||
|
|
||||||
|
@ -196,7 +196,7 @@ internal class BackupCoordinatorTest : BackupTest() {
|
||||||
assertEquals(0L, backup.requestFullBackupTime())
|
assertEquals(0L, backup.requestFullBackupTime())
|
||||||
|
|
||||||
verify(exactly = 1) {
|
verify(exactly = 1) {
|
||||||
metadataManager.onPackageBackupError(packageInfo.packageName, QUOTA_EXCEEDED, metadataOutputStream)
|
metadataManager.onPackageBackupError(packageInfo, QUOTA_EXCEEDED, metadataOutputStream)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -207,7 +207,7 @@ internal class BackupCoordinatorTest : BackupTest() {
|
||||||
every { full.getQuota() } returns DEFAULT_QUOTA_FULL_BACKUP
|
every { full.getQuota() } returns DEFAULT_QUOTA_FULL_BACKUP
|
||||||
every { full.checkFullBackupSize(0) } returns TRANSPORT_PACKAGE_REJECTED
|
every { full.checkFullBackupSize(0) } returns TRANSPORT_PACKAGE_REJECTED
|
||||||
every { full.getCurrentPackage() } returns packageInfo
|
every { full.getCurrentPackage() } returns packageInfo
|
||||||
every { metadataManager.onPackageBackupError(packageInfo.packageName, NO_DATA, metadataOutputStream) } just Runs
|
every { metadataManager.onPackageBackupError(packageInfo, NO_DATA, metadataOutputStream) } just Runs
|
||||||
every { full.cancelFullBackup() } just Runs
|
every { full.cancelFullBackup() } just Runs
|
||||||
every { settingsManager.getStorage() } returns storage
|
every { settingsManager.getStorage() } returns storage
|
||||||
|
|
||||||
|
@ -220,18 +220,16 @@ internal class BackupCoordinatorTest : BackupTest() {
|
||||||
assertEquals(0L, backup.requestFullBackupTime())
|
assertEquals(0L, backup.requestFullBackupTime())
|
||||||
|
|
||||||
verify(exactly = 1) {
|
verify(exactly = 1) {
|
||||||
metadataManager.onPackageBackupError(packageInfo.packageName, NO_DATA, metadataOutputStream)
|
metadataManager.onPackageBackupError(packageInfo, NO_DATA, metadataOutputStream)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `not allowed apps get their APKs backed up during @pm@ backup`() {
|
fun `not allowed apps get their APKs backed up during @pm@ backup`() {
|
||||||
val packageInfo = PackageInfo().apply { packageName = MAGIC_PACKAGE_MANAGER }
|
val packageInfo = PackageInfo().apply { packageName = MAGIC_PACKAGE_MANAGER }
|
||||||
val packageName1 = "org.example.1"
|
|
||||||
val packageName2 = "org.example.2"
|
|
||||||
val notAllowedPackages = listOf(
|
val notAllowedPackages = listOf(
|
||||||
PackageInfo().apply { packageName = packageName1 },
|
PackageInfo().apply { packageName = "org.example.1" },
|
||||||
PackageInfo().apply { packageName = packageName2 }
|
PackageInfo().apply { packageName = "org.example.2" }
|
||||||
)
|
)
|
||||||
val packageMetadata: PackageMetadata = mockk()
|
val packageMetadata: PackageMetadata = mockk()
|
||||||
|
|
||||||
|
@ -242,7 +240,7 @@ internal class BackupCoordinatorTest : BackupTest() {
|
||||||
// was backed up, get new packageMetadata
|
// was backed up, get new packageMetadata
|
||||||
every { apkBackup.backupApkIfNecessary(notAllowedPackages[1], NOT_ALLOWED, any()) } returns packageMetadata
|
every { apkBackup.backupApkIfNecessary(notAllowedPackages[1], NOT_ALLOWED, any()) } returns packageMetadata
|
||||||
every { plugin.getMetadataOutputStream() } returns metadataOutputStream
|
every { plugin.getMetadataOutputStream() } returns metadataOutputStream
|
||||||
every { metadataManager.onApkBackedUp(packageName2, packageMetadata, metadataOutputStream) } just Runs
|
every { metadataManager.onApkBackedUp(notAllowedPackages[1], packageMetadata, metadataOutputStream) } just Runs
|
||||||
// do actual @pm@ backup
|
// do actual @pm@ backup
|
||||||
every { kv.performBackup(packageInfo, fileDescriptor, 0) } returns TRANSPORT_OK
|
every { kv.performBackup(packageInfo, fileDescriptor, 0) } returns TRANSPORT_OK
|
||||||
|
|
||||||
|
@ -258,7 +256,7 @@ internal class BackupCoordinatorTest : BackupTest() {
|
||||||
private fun expectApkBackupAndMetadataWrite() {
|
private fun expectApkBackupAndMetadataWrite() {
|
||||||
every { apkBackup.backupApkIfNecessary(packageInfo, UNKNOWN_ERROR, any()) } returns packageMetadata
|
every { apkBackup.backupApkIfNecessary(packageInfo, UNKNOWN_ERROR, any()) } returns packageMetadata
|
||||||
every { plugin.getMetadataOutputStream() } returns metadataOutputStream
|
every { plugin.getMetadataOutputStream() } returns metadataOutputStream
|
||||||
every { metadataManager.onApkBackedUp(packageInfo.packageName, packageMetadata, metadataOutputStream) } just Runs
|
every { metadataManager.onApkBackedUp(packageInfo, packageMetadata, metadataOutputStream) } just Runs
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,9 @@
|
||||||
package com.stevesoltys.seedvault.transport.restore
|
package com.stevesoltys.seedvault.transport.restore
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
|
import android.content.pm.ApplicationInfo
|
||||||
|
import android.content.pm.ApplicationInfo.*
|
||||||
|
import android.content.pm.PackageInfo
|
||||||
import android.content.pm.PackageManager
|
import android.content.pm.PackageManager
|
||||||
import android.graphics.drawable.Drawable
|
import android.graphics.drawable.Drawable
|
||||||
import com.stevesoltys.seedvault.getRandomString
|
import com.stevesoltys.seedvault.getRandomString
|
||||||
|
@ -21,6 +24,7 @@ import org.junit.jupiter.api.io.TempDir
|
||||||
import java.io.ByteArrayInputStream
|
import java.io.ByteArrayInputStream
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import java.nio.file.Path
|
import java.nio.file.Path
|
||||||
|
import java.util.logging.Logger.getLogger
|
||||||
import kotlin.random.Random
|
import kotlin.random.Random
|
||||||
|
|
||||||
@ExperimentalCoroutinesApi
|
@ExperimentalCoroutinesApi
|
||||||
|
@ -178,4 +182,60 @@ internal class ApkRestoreTest : RestoreTest() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `test system apps only get reinstalled when older system apps exist`(@TempDir tmpDir: Path) = runBlocking {
|
||||||
|
val packageMetadata = this@ApkRestoreTest.packageMetadata.copy(system = true)
|
||||||
|
packageMetadataMap[packageName] = packageMetadata
|
||||||
|
packageInfo.applicationInfo = mockk()
|
||||||
|
val installedPackageInfo: PackageInfo = mockk()
|
||||||
|
val willFail = Random.nextBoolean()
|
||||||
|
installedPackageInfo.applicationInfo = ApplicationInfo().apply {
|
||||||
|
// will not fail when app really is a system app
|
||||||
|
flags = if (willFail) FLAG_INSTALLED else FLAG_SYSTEM or FLAG_UPDATED_SYSTEM_APP
|
||||||
|
}
|
||||||
|
|
||||||
|
every { strictContext.cacheDir } returns File(tmpDir.toString())
|
||||||
|
every { restorePlugin.getApkInputStream(token, packageName) } returns apkInputStream
|
||||||
|
every { pm.getPackageArchiveInfo(any(), any()) } returns packageInfo
|
||||||
|
every { pm.loadItemIcon(packageInfo.applicationInfo, packageInfo.applicationInfo) } returns icon
|
||||||
|
every { packageInfo.applicationInfo.loadIcon(pm) } returns icon
|
||||||
|
every { pm.getApplicationLabel(packageInfo.applicationInfo) } returns appName
|
||||||
|
every { pm.getPackageInfo(packageName, 0) } returns installedPackageInfo
|
||||||
|
every { installedPackageInfo.longVersionCode } returns packageMetadata.version!! - 1
|
||||||
|
if (!willFail) {
|
||||||
|
val installResult = MutableInstallResult(1).apply {
|
||||||
|
put(packageName, ApkRestoreResult(packageName, progress = 1, total = 1, status = SUCCEEDED))
|
||||||
|
}
|
||||||
|
every { apkInstaller.install(any(), packageName, installerName, any()) } returns flowOf(installResult)
|
||||||
|
}
|
||||||
|
|
||||||
|
var i = 0
|
||||||
|
apkRestore.restore(token, packageMetadataMap).collect { value ->
|
||||||
|
when (i) {
|
||||||
|
0 -> {
|
||||||
|
val result = value[packageName] ?: fail()
|
||||||
|
assertEquals(QUEUED, result.status)
|
||||||
|
assertEquals(1, result.progress)
|
||||||
|
assertEquals(1, result.total)
|
||||||
|
}
|
||||||
|
1 -> {
|
||||||
|
val result = value[packageName] ?: fail()
|
||||||
|
assertEquals(IN_PROGRESS, result.status)
|
||||||
|
assertEquals(appName, result.name)
|
||||||
|
assertEquals(icon, result.icon)
|
||||||
|
}
|
||||||
|
2 -> {
|
||||||
|
val result = value[packageName] ?: fail()
|
||||||
|
if (willFail) {
|
||||||
|
assertEquals(FAILED, result.status)
|
||||||
|
} else {
|
||||||
|
assertEquals(SUCCEEDED, result.status)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else -> fail()
|
||||||
|
}
|
||||||
|
i++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue