Local backups upgrade UI.

This commit is contained in:
Alex Hart
2026-01-30 10:13:36 -04:00
committed by Greyson Parrelli
parent 2e70ed14dd
commit 6c30f3d573
30 changed files with 2007 additions and 202 deletions

View File

@@ -37,6 +37,7 @@ class ArchiveFileSystem private constructor(private val context: Context, root:
companion object {
val TAG = Log.tag(ArchiveFileSystem::class.java)
const val MAIN_DIRECTORY_NAME = "SignalBackups"
const val BACKUP_DIRECTORY_PREFIX: String = "signal-backup"
const val TEMP_BACKUP_DIRECTORY_SUFFIX: String = "tmp"
@@ -75,7 +76,7 @@ class ArchiveFileSystem private constructor(private val context: Context, root:
val filesFileSystem: FilesFileSystem
init {
signalBackups = root.mkdirp("SignalBackups") ?: throw IOException("Unable to create main backups directory")
signalBackups = root.mkdirp(MAIN_DIRECTORY_NAME) ?: throw IOException("Unable to create main backups directory")
val filesDirectory = signalBackups.mkdirp("files") ?: throw IOException("Unable to create files directory")
filesFileSystem = FilesFileSystem(context, filesDirectory)
}

View File

@@ -40,6 +40,7 @@ import org.thoughtcrime.securesms.compose.ComposeFragment
import org.thoughtcrime.securesms.compose.Nav
import org.thoughtcrime.securesms.database.InAppPaymentTable
import org.thoughtcrime.securesms.dependencies.AppDependencies
import org.thoughtcrime.securesms.keyvalue.SignalStore
import org.thoughtcrime.securesms.util.CommunicationActions
import org.thoughtcrime.securesms.util.PlayStoreUtil
import org.thoughtcrime.securesms.util.Util
@@ -159,7 +160,8 @@ class MessageBackupsFlowFragment : ComposeFragment(), InAppPaymentCheckoutDelega
onRequestSaveToPasswordManager = viewModel::onBackupKeySaveRequested,
onConfirmSaveToPasswordManager = viewModel::onBackupKeySaveConfirmed,
onSaveToPasswordManagerComplete = viewModel::onBackupKeySaveCompleted,
onGoToPasswordManagerSettingsClick = { requireContext().startActivity(passwordManagerSettingsIntent) }
onGoToPasswordManagerSettingsClick = { requireContext().startActivity(passwordManagerSettingsIntent) },
notifyKeyIsSameAsOnDeviceBackupKey = SignalStore.backup.newLocalBackupsEnabled
)
}

View File

@@ -8,8 +8,10 @@ package org.thoughtcrime.securesms.backup.v2.ui.subscription
import androidx.annotation.DrawableRes
import androidx.annotation.StringRes
import androidx.compose.foundation.Image
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.ColumnScope
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize
@@ -17,6 +19,7 @@ import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.foundation.verticalScroll
import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme
@@ -29,23 +32,47 @@ import androidx.compose.ui.res.dimensionResource
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.res.vectorResource
import androidx.compose.ui.text.SpanStyle
import androidx.compose.ui.text.buildAnnotatedString
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.text.withStyle
import androidx.compose.ui.unit.dp
import org.signal.core.ui.compose.Buttons
import org.signal.core.ui.compose.DayNightPreviews
import org.signal.core.ui.compose.Previews
import org.signal.core.ui.compose.Scaffolds
import org.signal.core.ui.compose.SignalIcons
import org.signal.core.ui.compose.horizontalGutters
import org.thoughtcrime.securesms.R
import org.thoughtcrime.securesms.compose.SignalTheme
import org.signal.core.ui.R as CoreUiR
enum class MessageBackupsKeyEducationScreenMode {
/**
* Displayed when the user is enabling remote backups and does not have unified local backups enabled
*/
REMOTE_BACKUP_WITH_LOCAL_DISABLED,
/**
* Displayed when the user is upgrading legacy to unified local backup
*/
LOCAL_BACKUP_UPGRADE,
/**
* Displayed when the user has unified local backup and is enabling remote backups
*/
REMOTE_BACKUP_WITH_LOCAL_ENABLED
}
/**
* Screen detailing how a backups key is used to restore a backup
*/
@Composable
fun MessageBackupsKeyEducationScreen(
onNavigationClick: () -> Unit = {},
onNextClick: () -> Unit = {}
onNextClick: () -> Unit = {},
mode: MessageBackupsKeyEducationScreenMode = MessageBackupsKeyEducationScreenMode.REMOTE_BACKUP_WITH_LOCAL_DISABLED
) {
val scrollState = rememberScrollState()
@@ -71,26 +98,24 @@ fun MessageBackupsKeyEducationScreen(
)
Text(
text = stringResource(R.string.MessageBackupsKeyEducationScreen__your_backup_key),
text = getTitleText(mode),
textAlign = TextAlign.Center,
style = MaterialTheme.typography.headlineMedium,
modifier = Modifier.padding(top = 16.dp)
)
InfoRow(
R.drawable.symbol_number_24,
R.string.MessageBackupsKeyEducationScreen__your_backup_key_is_a
)
when (mode) {
MessageBackupsKeyEducationScreenMode.REMOTE_BACKUP_WITH_LOCAL_DISABLED -> {
RemoteBackupWithLocalDisabledInfo()
}
InfoRow(
CoreUiR.drawable.symbol_lock_24,
R.string.MessageBackupsKeyEducationScreen__store_your_recovery
)
InfoRow(
R.drawable.symbol_error_circle_24,
R.string.MessageBackupsKeyEducationScreen__if_you_lose_it
)
MessageBackupsKeyEducationScreenMode.LOCAL_BACKUP_UPGRADE -> {
LocalBackupUpgradeInfo()
}
MessageBackupsKeyEducationScreenMode.REMOTE_BACKUP_WITH_LOCAL_ENABLED -> {
RemoteBackupWithLocalEnabledInfo()
}
}
Spacer(
modifier = Modifier
@@ -117,6 +142,154 @@ fun MessageBackupsKeyEducationScreen(
}
}
@Composable
private fun getTitleText(mode: MessageBackupsKeyEducationScreenMode): String {
return when (mode) {
MessageBackupsKeyEducationScreenMode.REMOTE_BACKUP_WITH_LOCAL_DISABLED -> stringResource(R.string.MessageBackupsKeyEducationScreen__your_backup_key)
MessageBackupsKeyEducationScreenMode.LOCAL_BACKUP_UPGRADE -> stringResource(R.string.MessageBackupsKeyEducationScreen__your_new_recovery_key)
MessageBackupsKeyEducationScreenMode.REMOTE_BACKUP_WITH_LOCAL_ENABLED -> stringResource(R.string.MessageBackupsKeyEducationScreen__your_recovery_key)
}
}
@Composable
private fun LocalBackupUpgradeInfo() {
val normalText = stringResource(R.string.MessageBackupsKeyEducationScreen__local_backup_upgrade_description)
val boldText = stringResource(R.string.MessageBackupsKeyEducationScreen__local_backup_upgrade_description_bold)
DescriptionText(
normalText = normalText,
boldText = boldText
)
UseThisKeyToContainer {
UseThisKeyToRow(
icon = ImageVector.vectorResource(R.drawable.symbol_folder_24),
text = stringResource(R.string.MessageBackupsKeyEducationScreen__restore_on_device_backup)
)
Spacer(modifier = Modifier.padding(vertical = 16.dp))
UseThisKeyToRow(
icon = ImageVector.vectorResource(CoreUiR.drawable.symbol_backup_24),
text = stringResource(R.string.MessageBackupsKeyEducationScreen__restore_a_signal_secure_backup)
)
}
}
@Composable
private fun RemoteBackupWithLocalEnabledInfo() {
val normalText = stringResource(R.string.MessageBackupsKeyEducationScreen__remote_backup_with_local_enabled_description)
val boldText = stringResource(R.string.MessageBackupsKeyEducationScreen__remote_backup_with_local_enabled_description_bold)
DescriptionText(
normalText = normalText,
boldText = boldText
)
UseThisKeyToContainer {
UseThisKeyToRow(
icon = ImageVector.vectorResource(CoreUiR.drawable.symbol_backup_24),
text = stringResource(R.string.MessageBackupsKeyEducationScreen__restore_your_signal_secure_backup)
)
Spacer(modifier = Modifier.padding(vertical = 16.dp))
UseThisKeyToRow(
icon = ImageVector.vectorResource(R.drawable.symbol_folder_24),
text = stringResource(R.string.MessageBackupsKeyEducationScreen__restore_on_device_backup)
)
}
}
@Composable
private fun DescriptionText(
normalText: String,
boldText: String
) {
Text(
text = buildAnnotatedString {
append(normalText)
append(" ")
withStyle(style = SpanStyle(fontWeight = FontWeight.Bold)) {
append(boldText)
}
},
textAlign = TextAlign.Center,
modifier = Modifier
.padding(top = 12.dp)
.horizontalGutters()
)
}
@Composable
private fun UseThisKeyToContainer(
modifier: Modifier = Modifier,
content: @Composable ColumnScope.() -> Unit
) {
Column(
modifier = modifier
.padding(top = 28.dp)
.horizontalGutters()
.fillMaxWidth()
.background(color = SignalTheme.colors.colorSurface1, shape = RoundedCornerShape(10.dp))
.padding(24.dp)
) {
Text(
text = stringResource(R.string.MessageBackupsKeyEducationScreen__use_this_key_to),
style = MaterialTheme.typography.titleSmall,
color = MaterialTheme.colorScheme.onSurfaceVariant,
modifier = Modifier.padding(bottom = 14.dp)
)
content()
}
}
@Composable
private fun UseThisKeyToRow(
icon: ImageVector,
text: String,
modifier: Modifier = Modifier
) {
Row(
verticalAlignment = Alignment.CenterVertically,
modifier = modifier
.fillMaxWidth()
.padding(start = 12.dp)
) {
Icon(
imageVector = icon,
tint = MaterialTheme.colorScheme.onSurfaceVariant,
contentDescription = null,
modifier = Modifier.size(24.dp)
)
Text(
text = text,
style = MaterialTheme.typography.bodyLarge,
color = MaterialTheme.colorScheme.onSurfaceVariant,
modifier = Modifier.padding(start = 16.dp)
)
}
}
@Composable
private fun RemoteBackupWithLocalDisabledInfo() {
InfoRow(
R.drawable.symbol_number_24,
R.string.MessageBackupsKeyEducationScreen__your_backup_key_is_a
)
InfoRow(
CoreUiR.drawable.symbol_lock_24,
R.string.MessageBackupsKeyEducationScreen__store_your_recovery
)
InfoRow(
R.drawable.symbol_error_circle_24,
R.string.MessageBackupsKeyEducationScreen__if_you_lose_it
)
}
@Composable
private fun InfoRow(@DrawableRes iconId: Int, @StringRes textId: Int) {
Row(
@@ -140,8 +313,30 @@ private fun InfoRow(@DrawableRes iconId: Int, @StringRes textId: Int) {
@DayNightPreviews
@Composable
private fun MessageBackupsKeyEducationScreenPreview() {
private fun MessageBackupsKeyEducationScreenRemoteBackupWithLocalDisabledPreview() {
Previews.Preview {
MessageBackupsKeyEducationScreen()
MessageBackupsKeyEducationScreen(
mode = MessageBackupsKeyEducationScreenMode.REMOTE_BACKUP_WITH_LOCAL_DISABLED
)
}
}
@DayNightPreviews
@Composable
private fun MessageBackupsKeyEducationScreenLocalBackupUpgradePreview() {
Previews.Preview {
MessageBackupsKeyEducationScreen(
mode = MessageBackupsKeyEducationScreenMode.LOCAL_BACKUP_UPGRADE
)
}
}
@DayNightPreviews
@Composable
private fun MessageBackupsKeyEducationScreenRemoteBackupWithLocalEnabledPreview() {
Previews.Preview {
MessageBackupsKeyEducationScreen(
mode = MessageBackupsKeyEducationScreenMode.REMOTE_BACKUP_WITH_LOCAL_ENABLED
)
}
}

View File

@@ -58,13 +58,18 @@ import org.signal.core.ui.compose.Snackbars
import org.signal.core.ui.compose.horizontalGutters
import org.signal.core.ui.compose.theme.SignalTheme
import org.thoughtcrime.securesms.R
import org.thoughtcrime.securesms.components.settings.app.backups.remote.BackupKeyCredentialManagerHandler
import org.thoughtcrime.securesms.components.settings.app.backups.remote.BackupKeySaveState
import org.thoughtcrime.securesms.fonts.MonoTypeface
import org.thoughtcrime.securesms.keyvalue.SignalStore
import org.thoughtcrime.securesms.util.Util
import org.thoughtcrime.securesms.util.storage.AndroidCredentialRepository
import org.thoughtcrime.securesms.util.storage.CredentialManagerError
import org.thoughtcrime.securesms.util.storage.CredentialManagerResult
import org.signal.core.ui.R as CoreUiR
private const val CLIPBOARD_TIMEOUT_SECONDS = 60
@Stable
sealed interface MessageBackupsKeyRecordMode {
data class Next(val onNextClick: () -> Unit) : MessageBackupsKeyRecordMode
@@ -76,6 +81,40 @@ sealed interface MessageBackupsKeyRecordMode {
) : MessageBackupsKeyRecordMode
}
/**
* More self-contained version of [MessageBackupsKeyRecordScreen] to try to improve reusability.
* This version is not built to be previewed but covers a lot of the repetitive boilerplate seen
* elsewhere.
*/
@Composable
fun MessageBackupsKeyRecordScreen(
backupKey: String,
keySaveState: BackupKeySaveState?,
backupKeyCredentialManagerHandler: BackupKeyCredentialManagerHandler,
mode: MessageBackupsKeyRecordMode
) {
val context = LocalContext.current
val passwordManagerSettingsIntent = remember {
AndroidCredentialRepository.getCredentialManagerSettingsIntent(context)
}
val onBackPressedDispatcher = LocalOnBackPressedDispatcherOwner.current?.onBackPressedDispatcher
MessageBackupsKeyRecordScreen(
backupKey = backupKey,
keySaveState = keySaveState,
canOpenPasswordManagerSettings = passwordManagerSettingsIntent != null,
onNavigationClick = { onBackPressedDispatcher?.onBackPressed() },
onCopyToClipboardClick = { Util.copyToClipboard(context, it, CLIPBOARD_TIMEOUT_SECONDS) },
onRequestSaveToPasswordManager = backupKeyCredentialManagerHandler::onBackupKeySaveRequested,
onConfirmSaveToPasswordManager = backupKeyCredentialManagerHandler::onBackupKeySaveConfirmed,
onSaveToPasswordManagerComplete = backupKeyCredentialManagerHandler::onBackupKeySaveCompleted,
mode = mode,
onGoToPasswordManagerSettingsClick = { context.startActivity(passwordManagerSettingsIntent) },
notifyKeyIsSameAsOnDeviceBackupKey = SignalStore.backup.newLocalBackupsEnabled
)
}
/**
* Screen displaying the backup key allowing the user to write it down
* or copy it.
@@ -91,7 +130,8 @@ fun MessageBackupsKeyRecordScreen(
onConfirmSaveToPasswordManager: () -> Unit = {},
onSaveToPasswordManagerComplete: (CredentialManagerResult) -> Unit = {},
onGoToPasswordManagerSettingsClick: () -> Unit = {},
mode: MessageBackupsKeyRecordMode = MessageBackupsKeyRecordMode.Next(onNextClick = {})
mode: MessageBackupsKeyRecordMode = MessageBackupsKeyRecordMode.Next(onNextClick = {}),
notifyKeyIsSameAsOnDeviceBackupKey: Boolean = false
) {
val snackbarHostState = remember { SnackbarHostState() }
val backupKeyString = remember(backupKey) {
@@ -142,8 +182,14 @@ fun MessageBackupsKeyRecordScreen(
}
item {
val text = if (notifyKeyIsSameAsOnDeviceBackupKey) {
stringResource(R.string.MessageBackupsKeyRecordScreen__this_key_is_the_same_as_your_on_device_backup_key)
} else {
stringResource(R.string.MessageBackupsKeyRecordScreen__this_key_is_required_to_recover)
}
Text(
text = stringResource(R.string.MessageBackupsKeyRecordScreen__this_key_is_required_to_recover),
text = text,
textAlign = TextAlign.Center,
style = MaterialTheme.typography.bodyLarge,
color = MaterialTheme.colorScheme.onSurfaceVariant,
@@ -199,23 +245,14 @@ fun MessageBackupsKeyRecordScreen(
}
}
if (mode is MessageBackupsKeyRecordMode.Next) {
Box(
modifier = Modifier
.fillMaxWidth()
.padding(bottom = 24.dp)
) {
Buttons.LargeTonal(
onClick = mode.onNextClick,
modifier = Modifier.align(Alignment.BottomEnd)
) {
Text(
text = stringResource(R.string.MessageBackupsKeyRecordScreen__next)
)
}
when (mode) {
is MessageBackupsKeyRecordMode.Next -> {
NextButton(onNextClick = mode.onNextClick)
}
is MessageBackupsKeyRecordMode.CreateNewKey -> {
CreateNewKeyButton(mode)
}
} else if (mode is MessageBackupsKeyRecordMode.CreateNewKey) {
CreateNewKeyButton(mode)
}
}
@@ -259,6 +296,24 @@ fun MessageBackupsKeyRecordScreen(
}
}
@Composable
private fun NextButton(onNextClick: () -> Unit) {
Box(
modifier = Modifier
.fillMaxWidth()
.padding(bottom = 24.dp)
) {
Buttons.LargeTonal(
onClick = onNextClick,
modifier = Modifier.align(Alignment.BottomEnd)
) {
Text(
text = stringResource(R.string.MessageBackupsKeyRecordScreen__next)
)
}
}
}
@OptIn(ExperimentalMaterial3Api::class)
@Composable
private fun CreateNewKeyButton(
@@ -505,6 +560,20 @@ private fun MessageBackupsKeyRecordScreenPreview() {
}
}
@DayNightPreviews
@Composable
private fun MessageBackupsKeyRecordScreenSameAsOnDeviceKeyPreview() {
Previews.Preview {
MessageBackupsKeyRecordScreen(
backupKey = (0 until 63).map { (('A'..'Z') + ('0'..'9')).random() }.joinToString("") + "0",
keySaveState = null,
canOpenPasswordManagerSettings = true,
mode = MessageBackupsKeyRecordMode.Next(onNextClick = {}),
notifyKeyIsSameAsOnDeviceBackupKey = true
)
}
}
@DayNightPreviews
@Composable
private fun SaveKeyConfirmationDialogPreview() {

View File

@@ -9,8 +9,6 @@ import org.thoughtcrime.securesms.backup.v2.ui.subscription.MessageBackupsKeyRec
import org.thoughtcrime.securesms.backup.v2.ui.subscription.MessageBackupsKeyRecordScreen
import org.thoughtcrime.securesms.compose.ComposeFragment
import org.thoughtcrime.securesms.keyvalue.SignalStore
import org.thoughtcrime.securesms.util.Util
import org.thoughtcrime.securesms.util.storage.AndroidCredentialRepository
import org.thoughtcrime.securesms.util.viewModel
/**
@@ -27,17 +25,11 @@ class ForgotBackupKeyFragment : ComposeFragment() {
@Composable
override fun FragmentContent() {
val state by viewModel.uiState.collectAsStateWithLifecycle()
val passwordManagerSettingsIntent = AndroidCredentialRepository.getCredentialManagerSettingsIntent(requireContext())
MessageBackupsKeyRecordScreen(
backupKey = SignalStore.account.accountEntropyPool.displayValue,
keySaveState = state.keySaveState,
canOpenPasswordManagerSettings = passwordManagerSettingsIntent != null,
onNavigationClick = { requireActivity().supportFragmentManager.popBackStack() },
onCopyToClipboardClick = { Util.copyToClipboard(requireContext(), it, CLIPBOARD_TIMEOUT_SECONDS) },
onRequestSaveToPasswordManager = viewModel::onBackupKeySaveRequested,
onConfirmSaveToPasswordManager = viewModel::onBackupKeySaveConfirmed,
onSaveToPasswordManagerComplete = viewModel::onBackupKeySaveCompleted,
backupKeyCredentialManagerHandler = viewModel,
mode = remember {
MessageBackupsKeyRecordMode.Next(onNextClick = {
requireActivity()
@@ -47,8 +39,7 @@ class ForgotBackupKeyFragment : ComposeFragment() {
.addToBackStack(null)
.commit()
})
},
onGoToPasswordManagerSettingsClick = { requireContext().startActivity(passwordManagerSettingsIntent) }
}
)
}
}

View File

@@ -6,9 +6,6 @@ import android.os.Bundle
import android.widget.Toast
import androidx.activity.compose.setContent
import androidx.activity.enableEdgeToEdge
import androidx.activity.result.ActivityResultLauncher
import androidx.biometric.BiometricManager
import androidx.biometric.BiometricPrompt
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Scaffold
@@ -27,13 +24,10 @@ import androidx.compose.ui.text.withStyle
import androidx.compose.ui.unit.dp
import org.signal.core.ui.compose.DayNightPreviews
import org.signal.core.ui.compose.Previews
import org.signal.core.util.logging.Log
import org.thoughtcrime.securesms.BiometricDeviceAuthentication
import org.thoughtcrime.securesms.BiometricDeviceLockContract
import org.thoughtcrime.securesms.DevicePinAuthEducationSheet
import org.thoughtcrime.securesms.PassphraseRequiredActivity
import org.thoughtcrime.securesms.R
import org.thoughtcrime.securesms.backup.v2.ui.subscription.EnterKeyScreen
import org.thoughtcrime.securesms.components.compose.rememberBiometricsAuthentication
import org.thoughtcrime.securesms.compose.SignalTheme
import org.thoughtcrime.securesms.keyvalue.SignalStore
import org.thoughtcrime.securesms.util.CommunicationActions
@@ -46,8 +40,6 @@ import kotlin.random.nextInt
class VerifyBackupKeyActivity : PassphraseRequiredActivity() {
companion object {
private val TAG = Log.tag(VerifyBackupKeyActivity::class)
@JvmStatic
fun createIntent(context: Context): Intent {
return Intent(context, VerifyBackupKeyActivity::class.java)
@@ -56,25 +48,29 @@ class VerifyBackupKeyActivity : PassphraseRequiredActivity() {
const val REQUEST_CODE = 1204
}
private lateinit var biometricDeviceAuthentication: BiometricDeviceAuthentication
private lateinit var biometricDeviceLockLauncher: ActivityResultLauncher<String>
override fun onCreate(savedInstanceState: Bundle?, ready: Boolean) {
enableEdgeToEdge()
setContent {
SignalTheme {
val context = LocalContext.current
val biometrics = rememberBiometricsAuthentication(
promptTitle = stringResource(R.string.RemoteBackupsSettingsFragment__unlock_to_view_backup_key),
educationSheetMessage = stringResource(R.string.RemoteBackupsSettingsFragment__to_view_your_key),
onAuthenticationFailed = {
// Matches existing behavior: show a generic "authentication required" toast.
Toast.makeText(
context,
R.string.RemoteBackupsSettingsFragment__authenticatino_required,
Toast.LENGTH_SHORT
).show()
}
)
VerifyBackupPinScreen(
backupKey = SignalStore.account.accountEntropyPool.displayValue,
onForgotKeyClick = {
if (biometricDeviceAuthentication.shouldShowEducationSheet(this)) {
DevicePinAuthEducationSheet.show(getString(R.string.RemoteBackupsSettingsFragment__to_view_your_key), supportFragmentManager)
supportFragmentManager.setFragmentResultListener(DevicePinAuthEducationSheet.REQUEST_KEY, this) { _, _ ->
if (!biometricDeviceAuthentication.authenticate(this, true) { biometricDeviceLockLauncher.launch(getString(R.string.RemoteBackupsSettingsFragment__unlock_to_view_backup_key)) }) {
displayBackupKey()
}
}
} else if (!biometricDeviceAuthentication.authenticate(this, true) { biometricDeviceLockLauncher.launch(getString(R.string.RemoteBackupsSettingsFragment__unlock_to_view_backup_key)) }) {
biometrics.withBiometricsAuthentication {
displayBackupKey()
}
},
@@ -88,23 +84,6 @@ class VerifyBackupKeyActivity : PassphraseRequiredActivity() {
)
}
}
initializeBiometricAuth()
}
private fun initializeBiometricAuth() {
val biometricPrompt = BiometricPrompt(this, AuthListener())
val promptInfo: BiometricPrompt.PromptInfo = BiometricPrompt.PromptInfo.Builder()
.setAllowedAuthenticators(BiometricDeviceAuthentication.ALLOWED_AUTHENTICATORS)
.setTitle(getString(R.string.RemoteBackupsSettingsFragment__unlock_to_view_backup_key))
.build()
biometricDeviceAuthentication = BiometricDeviceAuthentication(BiometricManager.from(this), biometricPrompt, promptInfo)
biometricDeviceLockLauncher = registerForActivityResult(BiometricDeviceLockContract()) { result: Int ->
if (result == BiometricDeviceAuthentication.AUTHENTICATED) {
displayBackupKey()
}
}
}
private fun displayBackupKey() {
@@ -114,23 +93,6 @@ class VerifyBackupKeyActivity : PassphraseRequiredActivity() {
.addToBackStack(null)
.commit()
}
private inner class AuthListener : BiometricPrompt.AuthenticationCallback() {
override fun onAuthenticationFailed() {
Log.w(TAG, "onAuthenticationFailed")
Toast.makeText(this@VerifyBackupKeyActivity, R.string.RemoteBackupsSettingsFragment__authenticatino_required, Toast.LENGTH_SHORT).show()
}
override fun onAuthenticationSucceeded(result: BiometricPrompt.AuthenticationResult) {
Log.i(TAG, "onAuthenticationSucceeded")
displayBackupKey()
}
override fun onAuthenticationError(errorCode: Int, errString: CharSequence) {
Log.w(TAG, "onAuthenticationError: $errorCode, $errString")
onAuthenticationFailed()
}
}
}
@Composable