Always run storage backups in a foreground service

otherwise we get killed for using too much CPU
diff --git a/app/src/main/java/com/stevesoltys/seedvault/UsbIntentReceiver.kt b/app/src/main/java/com/stevesoltys/seedvault/UsbIntentReceiver.kt
index a4738fd..8f175a3 100644
--- a/app/src/main/java/com/stevesoltys/seedvault/UsbIntentReceiver.kt
+++ b/app/src/main/java/com/stevesoltys/seedvault/UsbIntentReceiver.kt
@@ -19,6 +19,7 @@
 import com.stevesoltys.seedvault.settings.FlashDrive
 import com.stevesoltys.seedvault.settings.SettingsManager
 import com.stevesoltys.seedvault.storage.StorageBackupService
+import com.stevesoltys.seedvault.storage.StorageBackupService.Companion.EXTRA_START_APP_BACKUP
 import com.stevesoltys.seedvault.transport.requestBackup
 import com.stevesoltys.seedvault.ui.storage.AUTHORITY_STORAGE
 import org.koin.core.context.KoinContextHandler.get
@@ -57,14 +58,15 @@
 
     override fun onStatusChanged(context: Context, action: String, device: UsbDevice) {
         if (settingsManager.isStorageBackupEnabled()) {
-            // TODO is it safe to start this at the same time as app backup?
             val i = Intent(context, StorageBackupService::class.java)
+            // this starts an app backup afterwards
+            i.putExtra(EXTRA_START_APP_BACKUP, true)
             startForegroundService(context, i)
+        } else {
+            Thread {
+                requestBackup(context)
+            }.start()
         }
-
-        Thread {
-            requestBackup(context)
-        }.start()
     }
 
 }
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 1179c80..e393a7d 100644
--- a/app/src/main/java/com/stevesoltys/seedvault/settings/SettingsViewModel.kt
+++ b/app/src/main/java/com/stevesoltys/seedvault/settings/SettingsViewModel.kt
@@ -3,6 +3,7 @@
 import android.app.Application
 import android.app.job.JobInfo.NETWORK_TYPE_NONE
 import android.app.job.JobInfo.NETWORK_TYPE_UNMETERED
+import android.content.Intent
 import android.database.ContentObserver
 import android.net.ConnectivityManager
 import android.net.Network
@@ -14,6 +15,7 @@
 import android.widget.Toast
 import android.widget.Toast.LENGTH_LONG
 import androidx.annotation.UiThread
+import androidx.core.content.ContextCompat.startForegroundService
 import androidx.lifecycle.LiveData
 import androidx.lifecycle.MutableLiveData
 import androidx.lifecycle.Transformations.switchMap
@@ -25,6 +27,8 @@
 import com.stevesoltys.seedvault.metadata.MetadataManager
 import com.stevesoltys.seedvault.permitDiskReads
 import com.stevesoltys.seedvault.storage.StorageBackupJobService
+import com.stevesoltys.seedvault.storage.StorageBackupService
+import com.stevesoltys.seedvault.storage.StorageBackupService.Companion.EXTRA_START_APP_BACKUP
 import com.stevesoltys.seedvault.transport.requestBackup
 import com.stevesoltys.seedvault.ui.RequireProvisioningViewModel
 import com.stevesoltys.seedvault.ui.notification.BackupNotificationManager
@@ -32,7 +36,6 @@
 import kotlinx.coroutines.launch
 import org.calyxos.backup.storage.api.StorageBackup
 import org.calyxos.backup.storage.backup.BackupJobService
-import org.calyxos.backup.storage.backup.NotificationBackupObserver
 import java.util.concurrent.TimeUnit.HOURS
 
 private const val TAG = "SettingsViewModel"
@@ -156,10 +159,13 @@
             Toast.makeText(app, R.string.notification_backup_already_running, LENGTH_LONG).show()
         } else viewModelScope.launch(Dispatchers.IO) {
             if (settingsManager.isStorageBackupEnabled()) {
-                val backupObserver = NotificationBackupObserver(app)
-                storageBackup.runBackup(backupObserver)
+                val i = Intent(app, StorageBackupService::class.java)
+                // this starts an app backup afterwards
+                i.putExtra(EXTRA_START_APP_BACKUP, true)
+                startForegroundService(app, i)
+            } else {
+                requestBackup(app)
             }
-            requestBackup(app)
         }
     }
 
diff --git a/app/src/main/java/com/stevesoltys/seedvault/storage/Services.kt b/app/src/main/java/com/stevesoltys/seedvault/storage/Services.kt
index 5f55cae..1c54beb 100644
--- a/app/src/main/java/com/stevesoltys/seedvault/storage/Services.kt
+++ b/app/src/main/java/com/stevesoltys/seedvault/storage/Services.kt
@@ -1,5 +1,7 @@
 package com.stevesoltys.seedvault.storage
 
+import android.content.Intent
+import com.stevesoltys.seedvault.transport.requestBackup
 import org.calyxos.backup.storage.api.BackupObserver
 import org.calyxos.backup.storage.api.RestoreObserver
 import org.calyxos.backup.storage.api.StorageBackup
@@ -24,12 +26,23 @@
 internal class StorageBackupJobService : BackupJobService(StorageBackupService::class.java)
 
 internal class StorageBackupService : BackupService() {
+
+    companion object {
+        internal const val EXTRA_START_APP_BACKUP = "startAppBackup"
+    }
+
     override val storageBackup: StorageBackup by inject()
 
     // use lazy delegate because context isn't available during construction time
     override val backupObserver: BackupObserver by lazy {
         NotificationBackupObserver(applicationContext)
     }
+
+    override fun onBackupFinished(intent: Intent, success: Boolean) {
+        if (intent.getBooleanExtra(EXTRA_START_APP_BACKUP, false)) {
+            requestBackup(applicationContext)
+        }
+    }
 }
 
 internal class StorageRestoreService : RestoreService() {
diff --git a/storage/demo/src/main/java/de/grobox/storagebackuptester/MainViewModel.kt b/storage/demo/src/main/java/de/grobox/storagebackuptester/MainViewModel.kt
index d68d76c..777247b 100644
--- a/storage/demo/src/main/java/de/grobox/storagebackuptester/MainViewModel.kt
+++ b/storage/demo/src/main/java/de/grobox/storagebackuptester/MainViewModel.kt
@@ -68,6 +68,8 @@
         viewModelScope.launch(Dispatchers.IO) {
             val text = storageBackup.getUriSummaryString()
             _backupLog.postValue(BackupProgress(0, 0, "Scanning: $text\n"))
+            // FIXME: This might get killed if we navigate away from the activity.
+            //  A foreground service would avoid that.
             if (storageBackup.runBackup(backupObserver)) {
                 // only prune old backups when backup run was successful
                 storageBackup.pruneOldBackups(backupObserver)
diff --git a/storage/lib/src/main/java/org/calyxos/backup/storage/backup/BackupService.kt b/storage/lib/src/main/java/org/calyxos/backup/storage/backup/BackupService.kt
index 17b772e..318bcbd 100644
--- a/storage/lib/src/main/java/org/calyxos/backup/storage/backup/BackupService.kt
+++ b/storage/lib/src/main/java/org/calyxos/backup/storage/backup/BackupService.kt
@@ -21,14 +21,15 @@
     protected abstract val storageBackup: StorageBackup
     protected abstract val backupObserver: BackupObserver?
 
-    override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
+    override fun onStartCommand(intent: Intent, flags: Int, startId: Int): Int {
         Log.d(TAG, "onStartCommand $intent $flags $startId")
         startForeground(
             NOTIFICATION_ID_BACKUP,
             n.getBackupNotification(R.string.notification_backup_scanning)
         )
         GlobalScope.launch {
-            if (storageBackup.runBackup(backupObserver)) {
+            val success = storageBackup.runBackup(backupObserver)
+            if (success) {
                 // only prune old backups when backup run was successful
                 startForeground(
                     NOTIFICATION_ID_PRUNE,
@@ -36,11 +37,15 @@
                 )
                 storageBackup.pruneOldBackups(backupObserver)
             }
+            onBackupFinished(intent, success)
             stopSelf(startId)
         }
         return super.onStartCommand(intent, flags, startId)
     }
 
+    protected open fun onBackupFinished(intent: Intent, success: Boolean) {
+    }
+
     override fun onBind(intent: Intent?): IBinder? {
         return null
     }