From 0c1898c198f428362564109a9ae56f8036ec1e35 Mon Sep 17 00:00:00 2001 From: Torsten Grote Date: Thu, 22 Feb 2024 11:37:16 -0300 Subject: [PATCH] Properly schedule/cancel backup workers when backup destination changes When the user changes to USB storage, we need to cancel current schedulings, because the storage is not always available (maybe can use a trigger URI?). And if moving to a non-USB storage, we need to schedule backups again. Unfortunately, there are two places in the code where we handle storage location changes. Ideally, those get unified at some point. --- .../seedvault/settings/SettingsFragment.kt | 11 +++--- .../seedvault/settings/SettingsViewModel.kt | 23 ++++++++---- .../ui/recoverycode/RecoveryCodeViewModel.kt | 1 + .../ui/storage/BackupStorageViewModel.kt | 35 +++++++++++++++++++ 4 files changed, 57 insertions(+), 13 deletions(-) diff --git a/app/src/main/java/com/stevesoltys/seedvault/settings/SettingsFragment.kt b/app/src/main/java/com/stevesoltys/seedvault/settings/SettingsFragment.kt index 08648a8e..559a9c44 100644 --- a/app/src/main/java/com/stevesoltys/seedvault/settings/SettingsFragment.kt +++ b/app/src/main/java/com/stevesoltys/seedvault/settings/SettingsFragment.kt @@ -23,7 +23,6 @@ import com.stevesoltys.seedvault.R import com.stevesoltys.seedvault.permitDiskReads import com.stevesoltys.seedvault.restore.RestoreActivity import com.stevesoltys.seedvault.ui.toRelativeTime -import com.stevesoltys.seedvault.worker.AppBackupWorker import org.koin.android.ext.android.inject import org.koin.androidx.viewmodel.ext.android.sharedViewModel @@ -128,7 +127,7 @@ class SettingsFragment : PreferenceFragmentCompat() { val disable = !(newValue as Boolean) // TODO this should really get moved out off the UI layer if (disable) { - viewModel.cancelBackupWorkers() + viewModel.cancelFilesBackup() return@OnPreferenceChangeListener true } onEnablingStorageBackup() @@ -215,10 +214,10 @@ class SettingsFragment : PreferenceFragmentCompat() { return try { backupManager.isBackupEnabled = enabled if (enabled) { - AppBackupWorker.schedule(requireContext()) + viewModel.scheduleAppBackup() viewModel.enableCallLogBackup() } else { - AppBackupWorker.unschedule(requireContext()) + viewModel.cancelAppBackup() } backup.isChecked = enabled true @@ -280,7 +279,7 @@ class SettingsFragment : PreferenceFragmentCompat() { } Long.MAX_VALUE -> { - val text = if (backupManager.isBackupEnabled) { + val text = if (backupManager.isBackupEnabled && storage?.isUsb != true) { getString(R.string.notification_title) } else { getString(R.string.settings_backup_last_backup_never) @@ -315,7 +314,7 @@ class SettingsFragment : PreferenceFragmentCompat() { LENGTH_LONG ).show() } - viewModel.scheduleBackupWorkers() + viewModel.scheduleFilesBackup() backupStorage.isChecked = true dialog.dismiss() } diff --git a/app/src/main/java/com/stevesoltys/seedvault/settings/SettingsViewModel.kt b/app/src/main/java/com/stevesoltys/seedvault/settings/SettingsViewModel.kt index 1fefc982..c0aedf7b 100644 --- a/app/src/main/java/com/stevesoltys/seedvault/settings/SettingsViewModel.kt +++ b/app/src/main/java/com/stevesoltys/seedvault/settings/SettingsViewModel.kt @@ -129,11 +129,13 @@ internal class SettingsViewModel( Log.i(TAG, "onStorageLocationChanged") if (storage.isUsb) { // disable storage backup if new storage is on USB - cancelBackupWorkers() + cancelAppBackup() + cancelFilesBackup() } else { // enable it, just in case the previous storage was on USB, // also to update the network requirement of the new storage - scheduleBackupWorkers() + scheduleAppBackup() + scheduleFilesBackup() } onStoragePropertiesChanged() } @@ -245,11 +247,15 @@ internal class SettingsViewModel( return keyManager.hasMainKey() } - fun scheduleBackupWorkers() { + fun scheduleAppBackup() { val storage = settingsManager.getStorage() ?: error("no storage available") - if (!storage.isUsb) { - if (backupManager.isBackupEnabled) AppBackupWorker.schedule(app) - if (settingsManager.isStorageBackupEnabled()) BackupJobService.scheduleJob( + if (!storage.isUsb && backupManager.isBackupEnabled) AppBackupWorker.schedule(app) + } + + fun scheduleFilesBackup() { + val storage = settingsManager.getStorage() ?: error("no storage available") + if (!storage.isUsb && settingsManager.isStorageBackupEnabled()) { + BackupJobService.scheduleJob( context = app, jobServiceClass = StorageBackupJobService::class.java, periodMillis = HOURS.toMillis(24), @@ -261,8 +267,11 @@ internal class SettingsViewModel( } } - fun cancelBackupWorkers() { + fun cancelAppBackup() { AppBackupWorker.unschedule(app) + } + + fun cancelFilesBackup() { BackupJobService.cancelJob(app) } diff --git a/app/src/main/java/com/stevesoltys/seedvault/ui/recoverycode/RecoveryCodeViewModel.kt b/app/src/main/java/com/stevesoltys/seedvault/ui/recoverycode/RecoveryCodeViewModel.kt index 5dbc82ef..ba126ab1 100644 --- a/app/src/main/java/com/stevesoltys/seedvault/ui/recoverycode/RecoveryCodeViewModel.kt +++ b/app/src/main/java/com/stevesoltys/seedvault/ui/recoverycode/RecoveryCodeViewModel.kt @@ -102,6 +102,7 @@ internal class RecoveryCodeViewModel( */ fun reinitializeBackupLocation() { Log.d(TAG, "Re-initializing backup location...") + // TODO this code is almost identical to BackupStorageViewModel#onLocationSet(), unify? GlobalScope.launch(Dispatchers.IO) { // remove old storage snapshots and clear cache storageBackup.deleteAllSnapshots() diff --git a/app/src/main/java/com/stevesoltys/seedvault/ui/storage/BackupStorageViewModel.kt b/app/src/main/java/com/stevesoltys/seedvault/ui/storage/BackupStorageViewModel.kt index 33fe51b8..01cc817f 100644 --- a/app/src/main/java/com/stevesoltys/seedvault/ui/storage/BackupStorageViewModel.kt +++ b/app/src/main/java/com/stevesoltys/seedvault/ui/storage/BackupStorageViewModel.kt @@ -4,6 +4,7 @@ import android.app.Application import android.app.backup.BackupProgress import android.app.backup.IBackupManager import android.app.backup.IBackupObserver +import android.app.job.JobInfo import android.net.Uri import android.os.UserHandle import android.util.Log @@ -11,12 +12,15 @@ import androidx.annotation.WorkerThread import androidx.lifecycle.viewModelScope import com.stevesoltys.seedvault.R import com.stevesoltys.seedvault.settings.SettingsManager +import com.stevesoltys.seedvault.storage.StorageBackupJobService import com.stevesoltys.seedvault.transport.TRANSPORT_ID import com.stevesoltys.seedvault.worker.AppBackupWorker import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch import org.calyxos.backup.storage.api.StorageBackup +import org.calyxos.backup.storage.backup.BackupJobService import java.io.IOException +import java.util.concurrent.TimeUnit private val TAG = BackupStorageViewModel::class.java.simpleName @@ -31,8 +35,18 @@ internal class BackupStorageViewModel( override fun onLocationSet(uri: Uri) { val isUsb = saveStorage(uri) + if (isUsb) { + // disable storage backup if new storage is on USB + cancelBackupWorkers() + } else { + // enable it, just in case the previous storage was on USB, + // also to update the network requirement of the new storage + scheduleBackupWorkers() + } viewModelScope.launch(Dispatchers.IO) { // remove old storage snapshots and clear cache + // TODO is this needed? It also does create all 255 chunk folders which takes time + // pass a flag to getCurrentBackupSnapshots() to not create missing folders? storageBackup.deleteAllSnapshots() storageBackup.clearCache() try { @@ -52,6 +66,27 @@ internal class BackupStorageViewModel( } } + private fun scheduleBackupWorkers() { + val storage = settingsManager.getStorage() ?: error("no storage available") + if (!storage.isUsb) { + if (backupManager.isBackupEnabled) AppBackupWorker.schedule(app) + if (settingsManager.isStorageBackupEnabled()) BackupJobService.scheduleJob( + context = app, + jobServiceClass = StorageBackupJobService::class.java, + periodMillis = TimeUnit.HOURS.toMillis(24), + networkType = if (storage.requiresNetwork) JobInfo.NETWORK_TYPE_UNMETERED + else JobInfo.NETWORK_TYPE_NONE, + deviceIdle = false, + charging = true + ) + } + } + + private fun cancelBackupWorkers() { + AppBackupWorker.unschedule(app) + BackupJobService.cancelJob(app) + } + @WorkerThread private inner class InitializationObserver(val requestBackup: Boolean) : IBackupObserver.Stub() {