diff --git a/app/src/main/java/com/stevesoltys/seedvault/App.kt b/app/src/main/java/com/stevesoltys/seedvault/App.kt
index a443b3bc..9ac333e7 100644
--- a/app/src/main/java/com/stevesoltys/seedvault/App.kt
+++ b/app/src/main/java/com/stevesoltys/seedvault/App.kt
@@ -46,7 +46,7 @@ open class App : Application() {
factory { AppListRetriever(this@App, get(), get(), get()) }
viewModel { SettingsViewModel(this@App, get(), get(), get(), get(), get(), get()) }
- viewModel { RecoveryCodeViewModel(this@App, get(), get(), get(), get(), get()) }
+ viewModel { RecoveryCodeViewModel(this@App, get(), get(), get(), get(), get(), get()) }
viewModel { BackupStorageViewModel(this@App, get(), get(), get(), get()) }
viewModel { RestoreStorageViewModel(this@App, get(), get()) }
viewModel { RestoreViewModel(this@App, get(), get(), get(), get(), get(), get()) }
diff --git a/app/src/main/java/com/stevesoltys/seedvault/restore/RestoreViewModel.kt b/app/src/main/java/com/stevesoltys/seedvault/restore/RestoreViewModel.kt
index 2b4e398f..845b6270 100644
--- a/app/src/main/java/com/stevesoltys/seedvault/restore/RestoreViewModel.kt
+++ b/app/src/main/java/com/stevesoltys/seedvault/restore/RestoreViewModel.kt
@@ -126,6 +126,8 @@ internal class RestoreViewModel(
@Throws(RemoteException::class)
private fun getOrStartSession(): IRestoreSession {
+ // TODO consider not using the BackupManager for this, but our own API directly
+ // this is less error-prone (hanging sessions) and can provide more data
val session = this.session
?: backupManager.beginRestoreSessionForUser(UserHandle.myUserId(), null, TRANSPORT_ID)
?: throw RemoteException("beginRestoreSessionForUser returned null")
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 0057d9c1..2cc8719d 100644
--- a/app/src/main/java/com/stevesoltys/seedvault/settings/SettingsFragment.kt
+++ b/app/src/main/java/com/stevesoltys/seedvault/settings/SettingsFragment.kt
@@ -54,12 +54,19 @@ class SettingsFragment : PreferenceFragmentCompat() {
backup = findPreference("backup")!!
backup.onPreferenceChangeListener = OnPreferenceChangeListener { _, newValue ->
val enabled = newValue as Boolean
+ // don't enable if we don't have the main key
+ if (enabled && !viewModel.hasMainKey()) {
+ showCodeRegenerationNeededDialog()
+ backup.isChecked = false
+ return@OnPreferenceChangeListener false
+ }
+ // main key is present, so enable or disable normally
try {
backupManager.isBackupEnabled = enabled
if (enabled) viewModel.enableCallLogBackup()
return@OnPreferenceChangeListener true
} catch (e: RemoteException) {
- e.printStackTrace()
+ Log.e(TAG, "Error setting backup enabled to $enabled", e)
backup.isChecked = !enabled
return@OnPreferenceChangeListener false
}
@@ -222,12 +229,8 @@ class SettingsFragment : PreferenceFragmentCompat() {
.setTitle(R.string.settings_backup_storage_dialog_title)
.setMessage(R.string.settings_backup_storage_dialog_message)
.setPositiveButton(R.string.settings_backup_storage_dialog_ok) { dialog, _ ->
- if (viewModel.hasMainKey()) {
- viewModel.enableStorageBackup()
- backupStorage.isChecked = true
- } else {
- showCodeVerificationNeededDialog()
- }
+ viewModel.enableStorageBackup()
+ backupStorage.isChecked = true
dialog.dismiss()
}
.setNegativeButton(R.string.settings_backup_apk_dialog_cancel) { dialog, _ ->
@@ -236,12 +239,12 @@ class SettingsFragment : PreferenceFragmentCompat() {
.show()
}
- private fun showCodeVerificationNeededDialog() {
+ private fun showCodeRegenerationNeededDialog() {
AlertDialog.Builder(requireContext())
.setIcon(R.drawable.ic_vpn_key)
- .setTitle(R.string.settings_backup_storage_code_dialog_title)
- .setMessage(R.string.settings_backup_storage_code_dialog_message)
- .setPositiveButton(R.string.settings_backup_storage_code_dialog_ok) { dialog, _ ->
+ .setTitle(R.string.settings_backup_new_code_dialog_title)
+ .setMessage(R.string.settings_backup_new_code_dialog_message)
+ .setPositiveButton(R.string.settings_backup_new_code_code_dialog_ok) { dialog, _ ->
val callback = (requireActivity() as OnPreferenceStartFragmentCallback)
callback.onPreferenceStartFragment(this, backupRecoveryCode)
dialog.dismiss()
diff --git a/app/src/main/java/com/stevesoltys/seedvault/transport/ConfigurableBackupTransportService.kt b/app/src/main/java/com/stevesoltys/seedvault/transport/ConfigurableBackupTransportService.kt
index 7ab65f46..143b47a2 100644
--- a/app/src/main/java/com/stevesoltys/seedvault/transport/ConfigurableBackupTransportService.kt
+++ b/app/src/main/java/com/stevesoltys/seedvault/transport/ConfigurableBackupTransportService.kt
@@ -10,6 +10,7 @@ import android.os.RemoteException
import android.util.Log
import androidx.annotation.WorkerThread
import com.stevesoltys.seedvault.BackupMonitor
+import com.stevesoltys.seedvault.crypto.KeyManager
import com.stevesoltys.seedvault.transport.backup.PackageService
import com.stevesoltys.seedvault.ui.notification.BackupNotificationManager
import com.stevesoltys.seedvault.ui.notification.NotificationBackupObserver
@@ -27,6 +28,8 @@ class ConfigurableBackupTransportService : Service(), KoinComponent {
private var transport: ConfigurableBackupTransport? = null
+ private val keyManager: KeyManager by inject()
+ private val backupManager: IBackupManager by inject()
private val notificationManager: BackupNotificationManager by inject()
override fun onCreate() {
@@ -35,7 +38,13 @@ class ConfigurableBackupTransportService : Service(), KoinComponent {
Log.d(TAG, "Service created.")
}
- override fun onBind(intent: Intent): IBinder {
+ override fun onBind(intent: Intent): IBinder? {
+ // refuse to work until we have the main key
+ val noMainKey = keyManager.hasBackupKey() && !keyManager.hasMainKey()
+ if (noMainKey && backupManager.currentTransport == TRANSPORT_ID) {
+ notificationManager.onNoMainKeyError()
+ backupManager.isBackupEnabled = false
+ }
val transport = this.transport ?: throw IllegalStateException("no transport in onBind()")
return transport.binder.apply {
Log.d(TAG, "Transport bound.")
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 a38147f4..ea8606a8 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
@@ -7,6 +7,7 @@ import android.app.NotificationManager.IMPORTANCE_DEFAULT
import android.app.NotificationManager.IMPORTANCE_HIGH
import android.app.NotificationManager.IMPORTANCE_LOW
import android.app.PendingIntent
+import android.app.PendingIntent.FLAG_IMMUTABLE
import android.app.PendingIntent.FLAG_UPDATE_CURRENT
import android.content.Context
import android.content.Intent
@@ -33,6 +34,7 @@ private const val NOTIFICATION_ID_OBSERVER = 1
private const val NOTIFICATION_ID_ERROR = 2
private const val NOTIFICATION_ID_RESTORE_ERROR = 3
private const val NOTIFICATION_ID_BACKGROUND = 4
+private const val NOTIFICATION_ID_NO_MAIN_KEY_ERROR = 5
private val TAG = BackupNotificationManager::class.java.simpleName
@@ -269,4 +271,28 @@ internal class BackupNotificationManager(private val context: Context) {
nm.cancel(NOTIFICATION_ID_RESTORE_ERROR)
}
+ @SuppressLint("RestrictedApi")
+ fun onNoMainKeyError() {
+ val intent = Intent(context, SettingsActivity::class.java)
+ val pendingIntent = PendingIntent.getActivity(context, 0, intent, FLAG_IMMUTABLE)
+ val actionText = context.getString(R.string.notification_error_action)
+ val action = Action(0, actionText, pendingIntent)
+ val notification = Builder(context, CHANNEL_ID_ERROR).apply {
+ setSmallIcon(R.drawable.ic_cloud_error)
+ setContentTitle(context.getString(R.string.notification_error_no_main_key_title))
+ setContentText(context.getString(R.string.notification_error_no_main_key_text))
+ setWhen(System.currentTimeMillis())
+ setOnlyAlertOnce(true)
+ setAutoCancel(false)
+ setOngoing(true)
+ setContentIntent(pendingIntent)
+ mActions = arrayListOf(action)
+ }.build()
+ nm.notify(NOTIFICATION_ID_NO_MAIN_KEY_ERROR, notification)
+ }
+
+ fun onNoMainKeyErrorFixed() {
+ nm.cancel(NOTIFICATION_ID_NO_MAIN_KEY_ERROR)
+ }
+
}
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 d851ea9e..ff649ee5 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
@@ -16,6 +16,7 @@ import com.stevesoltys.seedvault.transport.TRANSPORT_ID
import com.stevesoltys.seedvault.transport.backup.BackupCoordinator
import com.stevesoltys.seedvault.ui.LiveEvent
import com.stevesoltys.seedvault.ui.MutableLiveEvent
+import com.stevesoltys.seedvault.ui.notification.BackupNotificationManager
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch
@@ -33,6 +34,7 @@ internal class RecoveryCodeViewModel(
private val keyManager: KeyManager,
private val backupManager: IBackupManager,
private val backupCoordinator: BackupCoordinator,
+ private val notificationManager: BackupNotificationManager,
private val storageBackup: StorageBackup
) : AndroidViewModel(app) {
@@ -77,6 +79,7 @@ internal class RecoveryCodeViewModel(
// store main key at this opportunity if it is still missing
if (verified && !keyManager.hasMainKey()) keyManager.storeMainKey(seed)
mExistingCodeChecked.setEvent(verified)
+ if (verified) notificationManager.onNoMainKeyErrorFixed()
}
/**
@@ -88,6 +91,7 @@ internal class RecoveryCodeViewModel(
keyManager.storeBackupKey(seed)
keyManager.storeMainKey(seed)
mRecoveryCodeSaved.setEvent(true)
+ notificationManager.onNoMainKeyErrorFixed()
}
/**
@@ -109,7 +113,7 @@ internal class RecoveryCodeViewModel(
backupCoordinator.startNewRestoreSet()
// initialize the new location
- backupManager.initializeTransportsForUser(
+ if (backupManager.isBackupEnabled) backupManager.initializeTransportsForUser(
UserHandle.myUserId(),
arrayOf(TRANSPORT_ID),
null
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 aa8a0bb6..91a68b7e 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
@@ -41,13 +41,15 @@ internal class BackupStorageViewModel(
// will also generate a new backup token for the new restore set
backupCoordinator.startNewRestoreSet()
- // initialize the new location
- backupManager.initializeTransportsForUser(
+ // initialize the new location (if backups are enabled)
+ if (backupManager.isBackupEnabled) backupManager.initializeTransportsForUser(
UserHandle.myUserId(),
arrayOf(TRANSPORT_ID),
// if storage is on USB and this is not SetupWizard, do a backup right away
InitializationObserver(isUsb && !isSetupWizard)
- )
+ ) else {
+ InitializationObserver(false).backupFinished(0)
+ }
} catch (e: IOException) {
Log.e(TAG, "Error starting new RestoreSet", e)
onInitializationError()
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index eaa3c7d4..1b2fdafe 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -38,9 +38,9 @@
Experimental feature
Backing up files is still experimental and might not work. Do not rely on it for important data.
Enable anyway
- Recovery code verification required
- To enable storage backup, you need to first verify your recovery code or generate a new one.
- Verify code
+ New recovery code required
+ To continue using app backups, you need to generate a new recovery code.\n\nWe are sorry for the inconvenience.
+ New code
Expert settings
Unlimited app quota
@@ -120,6 +120,9 @@
Plug in your %1$s before installing the app to restore its data from backup.
Uninstall app
+ Backups disabled
+ Generate a new recovery code to complete upgrade and continue to use backups.
+
System apps