diff --git a/app/src/main/java/com/stevesoltys/seedvault/App.kt b/app/src/main/java/com/stevesoltys/seedvault/App.kt index a51b5263..372c86fc 100644 --- a/app/src/main/java/com/stevesoltys/seedvault/App.kt +++ b/app/src/main/java/com/stevesoltys/seedvault/App.kt @@ -2,6 +2,7 @@ package com.stevesoltys.seedvault import android.Manifest.permission.INTERACT_ACROSS_USERS_FULL import android.app.Application +import android.app.backup.BackupManager import android.app.backup.BackupManager.PACKAGE_MANAGER_SENTINEL import android.app.backup.IBackupManager 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 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 fun isDebugBuild() = Build.TYPE == "userdebug" diff --git a/app/src/main/java/com/stevesoltys/seedvault/settings/SettingsManager.kt b/app/src/main/java/com/stevesoltys/seedvault/settings/SettingsManager.kt index 786f5781..9584ffea 100644 --- a/app/src/main/java/com/stevesoltys/seedvault/settings/SettingsManager.kt +++ b/app/src/main/java/com/stevesoltys/seedvault/settings/SettingsManager.kt @@ -166,6 +166,15 @@ class SettingsManager(private val context: Context) { 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) @UiThread diff --git a/app/src/main/java/com/stevesoltys/seedvault/ui/notification/NotificationBackupObserver.kt b/app/src/main/java/com/stevesoltys/seedvault/ui/notification/NotificationBackupObserver.kt index 30d93441..c507bd59 100644 --- a/app/src/main/java/com/stevesoltys/seedvault/ui/notification/NotificationBackupObserver.kt +++ b/app/src/main/java/com/stevesoltys/seedvault/ui/notification/NotificationBackupObserver.kt @@ -1,6 +1,7 @@ package com.stevesoltys.seedvault.ui.notification import android.app.backup.BackupProgress +import android.app.backup.BackupTransport.AGENT_ERROR import android.app.backup.IBackupObserver import android.content.Context 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.INFO import android.util.Log.isLoggable +import com.stevesoltys.seedvault.ERROR_BACKUP_CANCELLED import com.stevesoltys.seedvault.MAGIC_PACKAGE_MANAGER import com.stevesoltys.seedvault.R import com.stevesoltys.seedvault.metadata.MetadataManager +import com.stevesoltys.seedvault.settings.SettingsManager import com.stevesoltys.seedvault.transport.backup.PackageService import com.stevesoltys.seedvault.worker.BackupRequester import org.koin.core.component.KoinComponent @@ -27,11 +30,14 @@ internal class NotificationBackupObserver( private val nm: BackupNotificationManager by inject() private val metadataManager: MetadataManager by inject() private val packageService: PackageService by inject() + private val settingsManager: SettingsManager by inject() private var currentPackage: String? = null private var numPackages: Int = 0 private var numPackagesToReport: Int = 0 private var pmCounted: Boolean = false + private var errorPackageName: String? = null + init { // Inform the notification manager that a backup has started // 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 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 showProgressNotification(target) } @@ -98,6 +115,15 @@ internal class NotificationBackupObserver( * as a whole failed. */ 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 (isLoggable(TAG, INFO)) { Log.i(TAG, "Backup finished $numPackages/$requestedPackages. Status: $status")