Auto-disable apps that cancel the entire backup
This can happen when the app process gets killed while its BackupAgent is running. There are several qcom apps in the wild that have this issue. These are DoSing our backups and are non-free, so we are defending ourselves against them.
This commit is contained in:
parent
499126c459
commit
c8d21fcf34
3 changed files with 38 additions and 1 deletions
|
@ -2,6 +2,7 @@ package com.stevesoltys.seedvault
|
||||||
|
|
||||||
import android.Manifest.permission.INTERACT_ACROSS_USERS_FULL
|
import android.Manifest.permission.INTERACT_ACROSS_USERS_FULL
|
||||||
import android.app.Application
|
import android.app.Application
|
||||||
|
import android.app.backup.BackupManager
|
||||||
import android.app.backup.BackupManager.PACKAGE_MANAGER_SENTINEL
|
import android.app.backup.BackupManager.PACKAGE_MANAGER_SENTINEL
|
||||||
import android.app.backup.IBackupManager
|
import android.app.backup.IBackupManager
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
|
@ -147,9 +148,10 @@ open class App : Application() {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const val MAGIC_PACKAGE_MANAGER = PACKAGE_MANAGER_SENTINEL
|
const val MAGIC_PACKAGE_MANAGER: String = PACKAGE_MANAGER_SENTINEL
|
||||||
const val ANCESTRAL_RECORD_KEY = "@ancestral_record@"
|
const val ANCESTRAL_RECORD_KEY = "@ancestral_record@"
|
||||||
const val GLOBAL_METADATA_KEY = "@meta@"
|
const val GLOBAL_METADATA_KEY = "@meta@"
|
||||||
|
const val ERROR_BACKUP_CANCELLED: Int = BackupManager.ERROR_BACKUP_CANCELLED
|
||||||
|
|
||||||
// TODO this doesn't work for LineageOS as they do public debug builds
|
// TODO this doesn't work for LineageOS as they do public debug builds
|
||||||
fun isDebugBuild() = Build.TYPE == "userdebug"
|
fun isDebugBuild() = Build.TYPE == "userdebug"
|
||||||
|
|
|
@ -166,6 +166,15 @@ class SettingsManager(private val context: Context) {
|
||||||
|
|
||||||
fun isBackupEnabled(packageName: String) = !blacklistedApps.contains(packageName)
|
fun isBackupEnabled(packageName: String) = !blacklistedApps.contains(packageName)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Disables backup for an app. Similar to [onAppBackupStatusChanged].
|
||||||
|
*/
|
||||||
|
fun disableBackup(packageName: String) {
|
||||||
|
if (blacklistedApps.add(packageName)) {
|
||||||
|
prefs.edit().putStringSet(PREF_KEY_BACKUP_APP_BLACKLIST, blacklistedApps).apply()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fun isStorageBackupEnabled() = prefs.getBoolean(PREF_KEY_BACKUP_STORAGE, false)
|
fun isStorageBackupEnabled() = prefs.getBoolean(PREF_KEY_BACKUP_STORAGE, false)
|
||||||
|
|
||||||
@UiThread
|
@UiThread
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
package com.stevesoltys.seedvault.ui.notification
|
package com.stevesoltys.seedvault.ui.notification
|
||||||
|
|
||||||
import android.app.backup.BackupProgress
|
import android.app.backup.BackupProgress
|
||||||
|
import android.app.backup.BackupTransport.AGENT_ERROR
|
||||||
import android.app.backup.IBackupObserver
|
import android.app.backup.IBackupObserver
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.pm.ApplicationInfo.FLAG_SYSTEM
|
import android.content.pm.ApplicationInfo.FLAG_SYSTEM
|
||||||
|
@ -8,9 +9,11 @@ import android.content.pm.PackageManager.NameNotFoundException
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import android.util.Log.INFO
|
import android.util.Log.INFO
|
||||||
import android.util.Log.isLoggable
|
import android.util.Log.isLoggable
|
||||||
|
import com.stevesoltys.seedvault.ERROR_BACKUP_CANCELLED
|
||||||
import com.stevesoltys.seedvault.MAGIC_PACKAGE_MANAGER
|
import com.stevesoltys.seedvault.MAGIC_PACKAGE_MANAGER
|
||||||
import com.stevesoltys.seedvault.R
|
import com.stevesoltys.seedvault.R
|
||||||
import com.stevesoltys.seedvault.metadata.MetadataManager
|
import com.stevesoltys.seedvault.metadata.MetadataManager
|
||||||
|
import com.stevesoltys.seedvault.settings.SettingsManager
|
||||||
import com.stevesoltys.seedvault.transport.backup.PackageService
|
import com.stevesoltys.seedvault.transport.backup.PackageService
|
||||||
import com.stevesoltys.seedvault.worker.BackupRequester
|
import com.stevesoltys.seedvault.worker.BackupRequester
|
||||||
import org.koin.core.component.KoinComponent
|
import org.koin.core.component.KoinComponent
|
||||||
|
@ -27,11 +30,14 @@ internal class NotificationBackupObserver(
|
||||||
private val nm: BackupNotificationManager by inject()
|
private val nm: BackupNotificationManager by inject()
|
||||||
private val metadataManager: MetadataManager by inject()
|
private val metadataManager: MetadataManager by inject()
|
||||||
private val packageService: PackageService by inject()
|
private val packageService: PackageService by inject()
|
||||||
|
private val settingsManager: SettingsManager by inject()
|
||||||
private var currentPackage: String? = null
|
private var currentPackage: String? = null
|
||||||
private var numPackages: Int = 0
|
private var numPackages: Int = 0
|
||||||
private var numPackagesToReport: Int = 0
|
private var numPackagesToReport: Int = 0
|
||||||
private var pmCounted: Boolean = false
|
private var pmCounted: Boolean = false
|
||||||
|
|
||||||
|
private var errorPackageName: String? = null
|
||||||
|
|
||||||
init {
|
init {
|
||||||
// Inform the notification manager that a backup has started
|
// Inform the notification manager that a backup has started
|
||||||
// and inform about the expected numbers of apps.
|
// and inform about the expected numbers of apps.
|
||||||
|
@ -86,6 +92,17 @@ internal class NotificationBackupObserver(
|
||||||
// should only happen for MAGIC_PACKAGE_MANAGER, but better save than sorry
|
// should only happen for MAGIC_PACKAGE_MANAGER, but better save than sorry
|
||||||
Log.e(TAG, "Error getting ApplicationInfo: ", e)
|
Log.e(TAG, "Error getting ApplicationInfo: ", e)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Apps that get killed while interacting with their [BackupAgent] cancel the entire backup.
|
||||||
|
// In order to prevent them from DoSing us, we remember them here to auto-disable them.
|
||||||
|
// We noticed that the same app behavior can cause a status of
|
||||||
|
// either AGENT_ERROR or ERROR_BACKUP_CANCELLED, so we need to handle both.
|
||||||
|
errorPackageName = if (status == AGENT_ERROR || status == ERROR_BACKUP_CANCELLED) {
|
||||||
|
target
|
||||||
|
} else {
|
||||||
|
null // To not disable apps by mistake, we reset it when getting a new non-error result.
|
||||||
|
}
|
||||||
|
|
||||||
// often [onResult] gets called right away without any [onUpdate] call
|
// often [onResult] gets called right away without any [onUpdate] call
|
||||||
showProgressNotification(target)
|
showProgressNotification(target)
|
||||||
}
|
}
|
||||||
|
@ -98,6 +115,15 @@ internal class NotificationBackupObserver(
|
||||||
* as a whole failed.
|
* as a whole failed.
|
||||||
*/
|
*/
|
||||||
override fun backupFinished(status: Int) {
|
override fun backupFinished(status: Int) {
|
||||||
|
if (status == ERROR_BACKUP_CANCELLED) {
|
||||||
|
val packageName = errorPackageName
|
||||||
|
if (packageName == null) {
|
||||||
|
Log.e(TAG, "Backup got cancelled, but there we have no culprit :(")
|
||||||
|
} else {
|
||||||
|
Log.w(TAG, "App $packageName misbehaved, will disable backup for it...")
|
||||||
|
settingsManager.disableBackup(packageName)
|
||||||
|
}
|
||||||
|
}
|
||||||
if (backupRequester.requestNext()) {
|
if (backupRequester.requestNext()) {
|
||||||
if (isLoggable(TAG, INFO)) {
|
if (isLoggable(TAG, INFO)) {
|
||||||
Log.i(TAG, "Backup finished $numPackages/$requestedPackages. Status: $status")
|
Log.i(TAG, "Backup finished $numPackages/$requestedPackages. Status: $status")
|
||||||
|
|
Loading…
Reference in a new issue