From 30e70527fb6789239470f91b0528832465cd1b1b Mon Sep 17 00:00:00 2001
From: Torsten Grote <t@grobox.de>
Date: Fri, 4 Sep 2020 15:29:36 -0300
Subject: [PATCH] Don't let the user start a new backup when one is already in
 progress

---
 app/src/main/java/com/stevesoltys/seedvault/App.kt     |  2 +-
 .../stevesoltys/seedvault/metadata/MetadataManager.kt  |  2 ++
 .../seedvault/settings/SettingsViewModel.kt            | 10 +++++++++-
 .../ui/notification/BackupNotificationManager.kt       | 10 ++++++++++
 app/src/main/res/values/strings.xml                    |  1 +
 5 files changed, 23 insertions(+), 2 deletions(-)

diff --git a/app/src/main/java/com/stevesoltys/seedvault/App.kt b/app/src/main/java/com/stevesoltys/seedvault/App.kt
index afff3eed..d5dde682 100644
--- a/app/src/main/java/com/stevesoltys/seedvault/App.kt
+++ b/app/src/main/java/com/stevesoltys/seedvault/App.kt
@@ -39,7 +39,7 @@ class App : Application() {
         single { Clock() }
         factory<IBackupManager> { IBackupManager.Stub.asInterface(getService(BACKUP_SERVICE)) }
 
-        viewModel { SettingsViewModel(this@App, get(), get(), get(), get()) }
+        viewModel { SettingsViewModel(this@App, get(), get(), get(), get(), get()) }
         viewModel { RecoveryCodeViewModel(this@App, get()) }
         viewModel { BackupStorageViewModel(this@App, get(), get(), get()) }
         viewModel { RestoreStorageViewModel(this@App, get(), get()) }
diff --git a/app/src/main/java/com/stevesoltys/seedvault/metadata/MetadataManager.kt b/app/src/main/java/com/stevesoltys/seedvault/metadata/MetadataManager.kt
index bc3d37c5..a398b56d 100644
--- a/app/src/main/java/com/stevesoltys/seedvault/metadata/MetadataManager.kt
+++ b/app/src/main/java/com/stevesoltys/seedvault/metadata/MetadataManager.kt
@@ -199,6 +199,8 @@ class MetadataManager(
 
     @Synchronized
     fun getPackagesNumBackedUp(): Int {
+        // FIXME we are under-reporting packages here,
+        //  because we have no way to also include upgraded system apps
         return metadata.packageMetadataMap.filter { (_, packageMetadata) ->
             !packageMetadata.system && ( // ignore system apps
                     packageMetadata.state == APK_AND_DATA || // either full success
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 3b11506f..859b016d 100644
--- a/app/src/main/java/com/stevesoltys/seedvault/settings/SettingsViewModel.kt
+++ b/app/src/main/java/com/stevesoltys/seedvault/settings/SettingsViewModel.kt
@@ -4,6 +4,8 @@ import android.app.Application
 import android.content.pm.PackageManager.NameNotFoundException
 import android.provider.Settings
 import android.util.Log
+import android.widget.Toast
+import android.widget.Toast.LENGTH_LONG
 import androidx.annotation.UiThread
 import androidx.core.content.ContextCompat.getDrawable
 import androidx.lifecycle.LiveData
@@ -30,6 +32,7 @@ import com.stevesoltys.seedvault.restore.AppRestoreStatus.SUCCEEDED
 import com.stevesoltys.seedvault.transport.backup.PackageService
 import com.stevesoltys.seedvault.transport.requestBackup
 import com.stevesoltys.seedvault.ui.RequireProvisioningViewModel
+import com.stevesoltys.seedvault.ui.notification.BackupNotificationManager
 import com.stevesoltys.seedvault.ui.notification.getAppName
 import kotlinx.coroutines.Dispatchers
 import kotlinx.coroutines.launch
@@ -43,6 +46,7 @@ internal class SettingsViewModel(
     app: Application,
     settingsManager: SettingsManager,
     keyManager: KeyManager,
+    private val notificationManager: BackupNotificationManager,
     private val metadataManager: MetadataManager,
     private val packageService: PackageService
 ) : RequireProvisioningViewModel(app, settingsManager, keyManager) {
@@ -68,7 +72,11 @@ internal class SettingsViewModel(
     }
 
     internal fun backupNow() {
-        Thread { requestBackup(app) }.start()
+        if (notificationManager.hasActiveBackupNotifications()) {
+            Toast.makeText(app, R.string.notification_backup_already_running, LENGTH_LONG).show()
+        } else {
+            Thread { requestBackup(app) }.start()
+        }
     }
 
     private fun getAppStatusResult(): LiveData<AppStatusResult> = liveData {
diff --git a/app/src/main/java/com/stevesoltys/seedvault/ui/notification/BackupNotificationManager.kt b/app/src/main/java/com/stevesoltys/seedvault/ui/notification/BackupNotificationManager.kt
index 7225c5db..98e70619 100644
--- a/app/src/main/java/com/stevesoltys/seedvault/ui/notification/BackupNotificationManager.kt
+++ b/app/src/main/java/com/stevesoltys/seedvault/ui/notification/BackupNotificationManager.kt
@@ -16,6 +16,7 @@ import androidx.core.app.NotificationCompat.Builder
 import androidx.core.app.NotificationCompat.PRIORITY_DEFAULT
 import androidx.core.app.NotificationCompat.PRIORITY_HIGH
 import androidx.core.app.NotificationCompat.PRIORITY_LOW
+import com.stevesoltys.seedvault.BuildConfig.APPLICATION_ID
 import com.stevesoltys.seedvault.MAGIC_PACKAGE_MANAGER
 import com.stevesoltys.seedvault.R
 import com.stevesoltys.seedvault.restore.ACTION_RESTORE_ERROR_UNINSTALL
@@ -204,6 +205,15 @@ internal class BackupNotificationManager(private val context: Context) {
         expectedAppTotals = null
     }
 
+    fun hasActiveBackupNotifications(): Boolean {
+        nm.activeNotifications.forEach {
+            if (it.packageName == APPLICATION_ID &&
+                (it.id == NOTIFICATION_ID_OBSERVER || it.id == NOTIFICATION_ID_BACKGROUND)
+            ) return true
+        }
+        return false
+    }
+
     fun onBackupError() {
         val intent = Intent(context, SettingsActivity::class.java)
         val pendingIntent = PendingIntent.getActivity(context, 0, intent, 0)
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 338053ec..d8fb40bb 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -76,6 +76,7 @@
     <string name="notification_backup_result_complete">Backup complete</string>
     <string name="notification_backup_result_rejected">Not backed up</string>
     <string name="notification_backup_result_error">Backup failed</string>
+    <string name="notification_backup_already_running">Backup already in progress</string>
 
     <string name="notification_success_title">Backup finished</string>
     <string name="notification_success_text">%1$d of %2$d apps backed up. Tap to learn more.</string>