Ensure that we have the main key for v1 crypto
We ask the user to generate a new key, because actively asking for the old one is training bad security habits, but technically verifying the old key will also work.
diff --git a/app/src/main/java/com/stevesoltys/seedvault/App.kt b/app/src/main/java/com/stevesoltys/seedvault/App.kt
index a443b3b..9ac333e 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 @@
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 2b4e398..845b627 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 @@
@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 0057d9c..2cc8719 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 @@
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 @@
.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 @@
.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 7ab65f4..143b47a 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.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 @@
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 @@
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 a38147f..ea8606a 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_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_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 @@
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 d851ea9..ff649ee 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.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 @@
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 @@
// 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 @@
keyManager.storeBackupKey(seed)
keyManager.storeMainKey(seed)
mRecoveryCodeSaved.setEvent(true)
+ notificationManager.onNoMainKeyErrorFixed()
}
/**
@@ -109,7 +113,7 @@
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 aa8a0bb..91a68b7 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 @@
// 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 eaa3c7d..1b2fdaf 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -38,9 +38,9 @@
<string name="settings_backup_storage_dialog_title">Experimental feature</string>
<string name="settings_backup_storage_dialog_message">Backing up files is still experimental and might not work. Do not rely on it for important data.</string>
<string name="settings_backup_storage_dialog_ok">Enable anyway</string>
- <string name="settings_backup_storage_code_dialog_title">Recovery code verification required</string>
- <string name="settings_backup_storage_code_dialog_message">To enable storage backup, you need to first verify your recovery code or generate a new one.</string>
- <string name="settings_backup_storage_code_dialog_ok">Verify code</string>
+ <string name="settings_backup_new_code_dialog_title">New recovery code required</string>
+ <string name="settings_backup_new_code_dialog_message">To continue using app backups, you need to generate a new recovery code.\n\nWe are sorry for the inconvenience.</string>
+ <string name="settings_backup_new_code_code_dialog_ok">New code</string>
<string name="settings_expert_title">Expert settings</string>
<string name="settings_expert_quota_title">Unlimited app quota</string>
@@ -120,6 +120,9 @@
<string name="notification_restore_error_text">Plug in your %1$s before installing the app to restore its data from backup.</string>
<string name="notification_restore_error_action">Uninstall app</string>
+ <string name="notification_error_no_main_key_title">Backups disabled</string>
+ <string name="notification_error_no_main_key_text">Generate a new recovery code to complete upgrade and continue to use backups.</string>
+
<!-- App Backup and Restore State -->
<string name="backup_section_system">System apps</string>