Only consider apps that really opt-out of backup for early APK backup

This commit is contained in:
Torsten Grote 2020-08-13 14:18:36 -03:00 committed by Chirayu Desai
parent a63a893a61
commit 0b6742df44
8 changed files with 156 additions and 81 deletions

View file

@ -0,0 +1,23 @@
package com.stevesoltys.seedvault.transport.backup
import android.util.Log
import androidx.test.ext.junit.runners.AndroidJUnit4
import org.junit.Assert.assertTrue
import org.junit.Test
import org.junit.runner.RunWith
import org.koin.core.KoinComponent
import org.koin.core.inject
@RunWith(AndroidJUnit4::class)
class PackageServiceTest : KoinComponent {
private val packageService: PackageService by inject()
@Test
fun testNotAllowedPackages() {
val packages = packageService.notAllowedPackages
assertTrue(packages.isNotEmpty())
Log.e("TEST", "Packages: $packages")
}
}

View file

@ -14,9 +14,10 @@ import org.koin.core.inject
private val TAG = NotificationBackupObserver::class.java.simpleName private val TAG = NotificationBackupObserver::class.java.simpleName
class NotificationBackupObserver( class NotificationBackupObserver(
private val context: Context, private val context: Context,
private val expectedPackages: Int, private val expectedPackages: Int,
private val userInitiated: Boolean) : IBackupObserver.Stub(), KoinComponent { private val userInitiated: Boolean
) : IBackupObserver.Stub(), KoinComponent {
private val nm: BackupNotificationManager by inject() private val nm: BackupNotificationManager by inject()
private val metadataManager: MetadataManager by inject() private val metadataManager: MetadataManager by inject()
@ -25,6 +26,12 @@ class NotificationBackupObserver(
init { init {
// we need to show this manually as [onUpdate] isn't called for first @pm@ package // we need to show this manually as [onUpdate] isn't called for first @pm@ package
// TODO consider showing something else for MAGIC_PACKAGE_MANAGER,
// because we also back up APKs at the beginning and this can take quite some time.
// Therefore, also consider showing a more fine-grained progress bar
// by (roughly) doubling the number [expectedPackages] (probably -3)
// and calling back here from KvBackup and ApkBackup to update progress.
// We will also need to take [PackageService#notAllowedPackages] into account.
nm.onBackupUpdate(getAppName(MAGIC_PACKAGE_MANAGER), 0, expectedPackages, userInitiated) nm.onBackupUpdate(getAppName(MAGIC_PACKAGE_MANAGER), 0, expectedPackages, userInitiated)
} }
@ -77,7 +84,9 @@ class NotificationBackupObserver(
if (currentPackage == packageName) return if (currentPackage == packageName) return
if (isLoggable(TAG, INFO)) { if (isLoggable(TAG, INFO)) {
Log.i(TAG, "Showing progress notification for $currentPackage $numPackages/$expectedPackages") "Showing progress notification for $currentPackage $numPackages/$expectedPackages".let {
Log.i(TAG, it)
}
} }
currentPackage = packageName currentPackage = packageName
val app = getAppName(packageName) val app = getAppName(packageName)

View file

@ -2,8 +2,6 @@ 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.content.pm.PackageInfo
import android.util.Log import android.util.Log
import androidx.annotation.VisibleForTesting import androidx.annotation.VisibleForTesting
@ -12,23 +10,25 @@ import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.distinctUntilChanged import androidx.lifecycle.distinctUntilChanged
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 com.stevesoltys.seedvault.transport.backup.isSystemApp
import java.io.FileNotFoundException import java.io.FileNotFoundException
import java.io.IOException import java.io.IOException
import java.io.OutputStream import java.io.OutputStream
private val TAG = MetadataManager::class.java.simpleName private val TAG = MetadataManager::class.java.simpleName
@VisibleForTesting @VisibleForTesting
internal const val METADATA_CACHE_FILE = "metadata.cache" internal const val METADATA_CACHE_FILE = "metadata.cache"
@WorkerThread @WorkerThread
class MetadataManager( class MetadataManager(
private val context: Context, private val context: Context,
private val clock: Clock, private val clock: Clock,
private val metadataWriter: MetadataWriter, private val metadataWriter: MetadataWriter,
private val metadataReader: MetadataReader) { private val metadataReader: MetadataReader
) {
private val uninitializedMetadata = BackupMetadata(token = 0L) private val uninitializedMetadata = BackupMetadata(token = 0L)
private var metadata: BackupMetadata = uninitializedMetadata private var metadata: BackupMetadata = uninitializedMetadata
@ -67,7 +67,11 @@ class MetadataManager(
*/ */
@Synchronized @Synchronized
@Throws(IOException::class) @Throws(IOException::class)
fun onApkBackedUp(packageInfo: PackageInfo, packageMetadata: PackageMetadata, metadataOutputStream: OutputStream) { fun onApkBackedUp(
packageInfo: PackageInfo,
packageMetadata: PackageMetadata,
metadataOutputStream: OutputStream
) {
val packageName = packageInfo.packageName val packageName = packageInfo.packageName
metadata.packageMetadataMap[packageName]?.let { metadata.packageMetadataMap[packageName]?.let {
check(packageMetadata.version != null) { check(packageMetadata.version != null) {
@ -78,20 +82,21 @@ class MetadataManager(
} }
} }
val oldPackageMetadata = metadata.packageMetadataMap[packageName] val oldPackageMetadata = metadata.packageMetadataMap[packageName]
?: PackageMetadata() ?: PackageMetadata()
// only allow state change if backup of this package is not allowed // only allow state change if backup of this package is not allowed
val newState = if (packageMetadata.state == NOT_ALLOWED) val newState = if (packageMetadata.state == NOT_ALLOWED) {
packageMetadata.state packageMetadata.state
else } else {
oldPackageMetadata.state oldPackageMetadata.state
}
modifyMetadata(metadataOutputStream) { modifyMetadata(metadataOutputStream) {
metadata.packageMetadataMap[packageName] = oldPackageMetadata.copy( metadata.packageMetadataMap[packageName] = oldPackageMetadata.copy(
state = newState, state = newState,
system = packageInfo.isSystemApp(), system = packageInfo.isSystemApp(),
version = packageMetadata.version, version = packageMetadata.version,
installer = packageMetadata.installer, installer = packageMetadata.installer,
sha256 = packageMetadata.sha256, sha256 = packageMetadata.sha256,
signatures = packageMetadata.signatures signatures = packageMetadata.signatures
) )
} }
} }
@ -114,9 +119,9 @@ class MetadataManager(
metadata.packageMetadataMap[packageName]!!.state = APK_AND_DATA metadata.packageMetadataMap[packageName]!!.state = APK_AND_DATA
} 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() system = packageInfo.isSystemApp()
) )
} }
} }
@ -130,7 +135,11 @@ class MetadataManager(
*/ */
@Synchronized @Synchronized
@Throws(IOException::class) @Throws(IOException::class)
internal fun onPackageBackupError(packageInfo: PackageInfo, 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 val packageName = packageInfo.packageName
modifyMetadata(metadataOutputStream) { modifyMetadata(metadataOutputStream) {
@ -138,9 +147,9 @@ class MetadataManager(
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() system = packageInfo.isSystemApp()
) )
} }
} }
@ -219,13 +228,3 @@ 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
}

View file

@ -21,25 +21,25 @@ import com.stevesoltys.seedvault.metadata.PackageState.NOT_ALLOWED
import com.stevesoltys.seedvault.metadata.PackageState.NO_DATA import com.stevesoltys.seedvault.metadata.PackageState.NO_DATA
import com.stevesoltys.seedvault.metadata.PackageState.QUOTA_EXCEEDED import com.stevesoltys.seedvault.metadata.PackageState.QUOTA_EXCEEDED
import com.stevesoltys.seedvault.metadata.PackageState.UNKNOWN_ERROR import com.stevesoltys.seedvault.metadata.PackageState.UNKNOWN_ERROR
import com.stevesoltys.seedvault.metadata.isSystemApp
import com.stevesoltys.seedvault.restore.AppRestoreStatus.FAILED import com.stevesoltys.seedvault.restore.AppRestoreStatus.FAILED
import com.stevesoltys.seedvault.restore.AppRestoreStatus.FAILED_NOT_ALLOWED import com.stevesoltys.seedvault.restore.AppRestoreStatus.FAILED_NOT_ALLOWED
import com.stevesoltys.seedvault.restore.AppRestoreStatus.FAILED_NO_DATA import com.stevesoltys.seedvault.restore.AppRestoreStatus.FAILED_NO_DATA
import com.stevesoltys.seedvault.restore.AppRestoreStatus.FAILED_QUOTA_EXCEEDED import com.stevesoltys.seedvault.restore.AppRestoreStatus.FAILED_QUOTA_EXCEEDED
import com.stevesoltys.seedvault.restore.AppRestoreStatus.SUCCEEDED import com.stevesoltys.seedvault.restore.AppRestoreStatus.SUCCEEDED
import com.stevesoltys.seedvault.transport.backup.isSystemApp
import com.stevesoltys.seedvault.transport.requestBackup import com.stevesoltys.seedvault.transport.requestBackup
import com.stevesoltys.seedvault.ui.RequireProvisioningViewModel import com.stevesoltys.seedvault.ui.RequireProvisioningViewModel
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import java.util.* import java.util.Locale
private val TAG = SettingsViewModel::class.java.simpleName private val TAG = SettingsViewModel::class.java.simpleName
class SettingsViewModel( class SettingsViewModel(
app: Application, app: Application,
settingsManager: SettingsManager, settingsManager: SettingsManager,
keyManager: KeyManager, keyManager: KeyManager,
private val metadataManager: MetadataManager private val metadataManager: MetadataManager
) : RequireProvisioningViewModel(app, settingsManager, keyManager) { ) : RequireProvisioningViewModel(app, settingsManager, keyManager) {
override val isRestoreOperation = false override val isRestoreOperation = false

View file

@ -11,8 +11,6 @@ import com.stevesoltys.seedvault.encodeBase64
import com.stevesoltys.seedvault.metadata.MetadataManager import com.stevesoltys.seedvault.metadata.MetadataManager
import com.stevesoltys.seedvault.metadata.PackageMetadata import com.stevesoltys.seedvault.metadata.PackageMetadata
import com.stevesoltys.seedvault.metadata.PackageState import com.stevesoltys.seedvault.metadata.PackageState
import com.stevesoltys.seedvault.metadata.isSystemApp
import com.stevesoltys.seedvault.metadata.isUpdatedSystemApp
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
@ -23,9 +21,10 @@ import java.security.MessageDigest
private val TAG = ApkBackup::class.java.simpleName private val TAG = ApkBackup::class.java.simpleName
class ApkBackup( class ApkBackup(
private val pm: PackageManager, private val pm: PackageManager,
private val settingsManager: SettingsManager, private val settingsManager: SettingsManager,
private val metadataManager: MetadataManager) { private val metadataManager: MetadataManager
) {
/** /**
* Checks if a new APK needs to get backed up, * Checks if a new APK needs to get backed up,
@ -36,7 +35,11 @@ class ApkBackup(
* @return new [PackageMetadata] if an APK backup was made or null if no backup was made. * @return new [PackageMetadata] if an APK backup was made or null if no backup was made.
*/ */
@Throws(IOException::class) @Throws(IOException::class)
suspend fun backupApkIfNecessary(packageInfo: PackageInfo, packageState: PackageState, streamGetter: suspend () -> OutputStream): PackageMetadata? { suspend fun backupApkIfNecessary(
packageInfo: PackageInfo,
packageState: PackageState,
streamGetter: suspend () -> OutputStream
): PackageMetadata? {
// do not back up @pm@ // do not back up @pm@
val packageName = packageInfo.packageName val packageName = packageInfo.packageName
if (packageName == MAGIC_PACKAGE_MANAGER) return null if (packageName == MAGIC_PACKAGE_MANAGER) return null
@ -45,7 +48,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
if (packageInfo.isSystemApp() && !packageInfo.isUpdatedSystemApp()) { if (packageInfo.isNotUpdatedSystemApp()) {
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
} }
@ -65,15 +68,19 @@ class ApkBackup(
// get cached metadata about package // get cached metadata about package
val packageMetadata = metadataManager.getPackageMetadata(packageName) val packageMetadata = metadataManager.getPackageMetadata(packageName)
?: PackageMetadata() ?: PackageMetadata()
// get version codes // get version codes
val version = packageInfo.longVersionCode val version = packageInfo.longVersionCode
val backedUpVersion = packageMetadata.version ?: 0L // no version will cause backup val backedUpVersion = packageMetadata.version ?: 0L // no version will cause backup
// do not backup if we have the version already and signatures did not change // do not backup if we have the version already and signatures did not change
if (version <= backedUpVersion && !signaturesChanged(packageMetadata, signatures)) { if (version <= backedUpVersion && !signaturesChanged(packageMetadata, signatures)) {
Log.d(TAG, "Package $packageName with version $version already has a backup ($backedUpVersion) with the same signature. Not backing it up.") Log.d(
TAG,
"Package $packageName with version $version already has a backup ($backedUpVersion)" +
" with the same signature. Not backing it up."
)
return null return null
} }
@ -91,7 +98,7 @@ class ApkBackup(
// copy the APK to the storage's output and calculate SHA-256 hash while at it // copy the APK to the storage's output and calculate SHA-256 hash while at it
val messageDigest = MessageDigest.getInstance("SHA-256") val messageDigest = MessageDigest.getInstance("SHA-256")
streamGetter.invoke().use { outputStream -> streamGetter().use { outputStream ->
inputStream.use { inputStream -> inputStream.use { inputStream ->
val buffer = ByteArray(DEFAULT_BUFFER_SIZE) val buffer = ByteArray(DEFAULT_BUFFER_SIZE)
var bytes = inputStream.read(buffer) var bytes = inputStream.read(buffer)
@ -107,15 +114,18 @@ class ApkBackup(
// return updated metadata // return updated metadata
return PackageMetadata( return PackageMetadata(
state = packageState, state = packageState,
version = version, version = version,
installer = pm.getInstallerPackageName(packageName), installer = pm.getInstallerPackageName(packageName),
sha256 = sha256, sha256 = sha256,
signatures = signatures signatures = signatures
) )
} }
private fun signaturesChanged(packageMetadata: PackageMetadata, signatures: List<String>): Boolean { private fun signaturesChanged(
packageMetadata: PackageMetadata,
signatures: List<String>
): Boolean {
// no signatures in package metadata counts as them not having changed // no signatures in package metadata counts as them not having changed
if (packageMetadata.signatures == null) return false if (packageMetadata.signatures == null) return false
// TODO to support multiple signers check if lists differ // TODO to support multiple signers check if lists differ

View file

@ -19,7 +19,6 @@ import com.stevesoltys.seedvault.metadata.PackageState.NOT_ALLOWED
import com.stevesoltys.seedvault.metadata.PackageState.NO_DATA import com.stevesoltys.seedvault.metadata.PackageState.NO_DATA
import com.stevesoltys.seedvault.metadata.PackageState.QUOTA_EXCEEDED import com.stevesoltys.seedvault.metadata.PackageState.QUOTA_EXCEEDED
import com.stevesoltys.seedvault.metadata.PackageState.UNKNOWN_ERROR import com.stevesoltys.seedvault.metadata.PackageState.UNKNOWN_ERROR
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

View file

@ -1,6 +1,9 @@
package com.stevesoltys.seedvault.transport.backup package com.stevesoltys.seedvault.transport.backup
import android.app.backup.IBackupManager import android.app.backup.IBackupManager
import android.content.pm.ApplicationInfo.FLAG_ALLOW_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.PackageManager.GET_SIGNING_CERTIFICATES import android.content.pm.PackageManager.GET_SIGNING_CERTIFICATES
@ -20,8 +23,9 @@ private const val LOG_MAX_PACKAGES = 100
* @author Torsten Grote * @author Torsten Grote
*/ */
internal class PackageService( internal class PackageService(
private val packageManager: PackageManager, private val packageManager: PackageManager,
private val backupManager: IBackupManager) { private val backupManager: IBackupManager
) {
private val myUserId = UserHandle.myUserId() private val myUserId = UserHandle.myUserId()
@ -30,8 +34,8 @@ internal class PackageService(
@Throws(RemoteException::class) @Throws(RemoteException::class)
get() { get() {
val packages = packageManager.getInstalledPackages(0) val packages = packageManager.getInstalledPackages(0)
.map { packageInfo -> packageInfo.packageName } .map { packageInfo -> packageInfo.packageName }
.sorted() .sorted()
// log packages // log packages
if (Log.isLoggable(TAG, INFO)) { if (Log.isLoggable(TAG, INFO)) {
@ -41,14 +45,13 @@ internal class PackageService(
} }
} }
val eligibleApps = backupManager.filterAppsEligibleForBackupForUser(myUserId, packages.toTypedArray()) val eligibleApps =
backupManager.filterAppsEligibleForBackupForUser(myUserId, packages.toTypedArray())
// log eligible packages // log eligible packages
if (Log.isLoggable(TAG, INFO)) { if (Log.isLoggable(TAG, INFO)) {
Log.i(TAG, "Filtering left ${eligibleApps.size} eligible packages:") Log.i(TAG, "Filtering left ${eligibleApps.size} eligible packages:")
eligibleApps.toList().chunked(LOG_MAX_PACKAGES).forEach { logPackages(eligibleApps.toList())
Log.i(TAG, it.toString())
}
} }
// add magic @pm@ package (PACKAGE_MANAGER_SENTINEL) which holds package manager data // add magic @pm@ package (PACKAGE_MANAGER_SENTINEL) which holds package manager data
@ -61,16 +64,48 @@ internal class PackageService(
val notAllowedPackages: List<PackageInfo> val notAllowedPackages: List<PackageInfo>
@WorkerThread @WorkerThread
get() { get() {
val installed = packageManager.getInstalledPackages(GET_SIGNING_CERTIFICATES) // We need the GET_SIGNING_CERTIFICATES flag here,
val installedArray = installed.map { packageInfo -> // because the package info is used by [ApkBackup] which needs signing info.
packageInfo.packageName return packageManager.getInstalledPackages(GET_SIGNING_CERTIFICATES)
}.toTypedArray() .filter { packageInfo ->
!packageInfo.isBackupAllowed() && // only apps that do not allow backup
val eligible = backupManager.filterAppsEligibleForBackupForUser(myUserId, installedArray) !packageInfo.isNotUpdatedSystemApp() // and are not vanilla system apps
}.sortedBy { packageInfo ->
return installed.filter { packageInfo -> packageInfo.packageName
packageInfo.packageName !in eligible }.also { notAllowed ->
}.sortedBy { it.packageName } // log eligible packages
if (Log.isLoggable(TAG, INFO)) {
Log.i(TAG, "${notAllowed.size} apps do not allow backup:")
logPackages(notAllowed.map { it.packageName })
}
}
} }
private fun logPackages(packages: List<String>) {
packages.chunked(LOG_MAX_PACKAGES).forEach {
Log.i(TAG, it.toString())
}
}
}
internal fun PackageInfo.isSystemApp(): Boolean {
if (packageName == MAGIC_PACKAGE_MANAGER || applicationInfo == null) return true
return applicationInfo.flags and FLAG_SYSTEM != 0
}
/**
* Returns true if this is a system app that hasn't been updated.
* We don't back up those APKs.
*/
internal fun PackageInfo.isNotUpdatedSystemApp(): Boolean {
if (packageName == MAGIC_PACKAGE_MANAGER || applicationInfo == null) return true
val isSystemApp = applicationInfo.flags and FLAG_SYSTEM != 0
val isUpdatedSystemApp = applicationInfo.flags and FLAG_UPDATED_SYSTEM_APP != 0
return isSystemApp && !isUpdatedSystemApp
}
internal fun PackageInfo.isBackupAllowed(): Boolean {
if (packageName == MAGIC_PACKAGE_MANAGER || applicationInfo == null) return false
return applicationInfo.flags and FLAG_ALLOW_BACKUP != 0
} }

View file

@ -9,8 +9,8 @@ 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.backup.isSystemApp
import com.stevesoltys.seedvault.transport.restore.ApkRestoreStatus.FAILED import com.stevesoltys.seedvault.transport.restore.ApkRestoreStatus.FAILED
import com.stevesoltys.seedvault.transport.restore.ApkRestoreStatus.IN_PROGRESS import com.stevesoltys.seedvault.transport.restore.ApkRestoreStatus.IN_PROGRESS
import com.stevesoltys.seedvault.transport.restore.ApkRestoreStatus.QUEUED import com.stevesoltys.seedvault.transport.restore.ApkRestoreStatus.QUEUED