mirror of
https://github.com/signalapp/Signal-Android.git
synced 2025-12-24 21:15:48 +00:00
Generalize device authentication education sheet for backups.
This commit is contained in:
@@ -45,8 +45,25 @@ class BiometricDeviceAuthentication(
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* From the docs on [BiometricManager.canAuthenticate]
|
||||
*
|
||||
* > Note that not all combinations of authenticator types are supported prior to Android 11 (API 30).
|
||||
* > Developers that wish to check for the presence of a PIN, pattern, or password on these versions should instead use KeyguardManager.isDeviceSecure().
|
||||
*/
|
||||
fun canAuthenticate(context: Context): Boolean {
|
||||
return isDeviceSecure(context) && biometricManager.canAuthenticate(ALLOWED_AUTHENTICATORS) == BiometricManager.BIOMETRIC_SUCCESS
|
||||
return if (Build.VERSION.SDK_INT >= 30) {
|
||||
biometricManager.canAuthenticate(ALLOWED_AUTHENTICATORS) == BiometricManager.BIOMETRIC_SUCCESS
|
||||
} else {
|
||||
biometricManager.canAuthenticate() == BiometricManager.BIOMETRIC_SUCCESS || isDeviceSecure(context)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the device credentials education sheet should be shown (only when biometrics is not enabled)
|
||||
*/
|
||||
fun shouldShowEducationSheet(context: Context): Boolean {
|
||||
return canAuthenticate(context) && biometricManager.canAuthenticate(BIOMETRIC_AUTHENTICATORS) != BiometricManager.BIOMETRIC_SUCCESS
|
||||
}
|
||||
|
||||
private fun isDontKeepActivitiesOn(context: Context): Boolean {
|
||||
@@ -54,10 +71,8 @@ class BiometricDeviceAuthentication(
|
||||
}
|
||||
|
||||
fun authenticate(context: Context, force: Boolean, showConfirmDeviceCredentialIntent: () -> Unit): Boolean {
|
||||
val isDeviceSecure = isDeviceSecure(context)
|
||||
|
||||
if (!isDeviceSecure) {
|
||||
Log.w(TAG, "Device not secure...")
|
||||
if (!canAuthenticate(context)) {
|
||||
Log.w(TAG, "Cannot authenticate, skipping. isDeviceSecure: ${isDeviceSecure(context)}, Auth status: ${biometricManager.canAuthenticate(ALLOWED_AUTHENTICATORS)}")
|
||||
return false
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package org.thoughtcrime.securesms.linkdevice
|
||||
package org.thoughtcrime.securesms
|
||||
|
||||
import android.content.DialogInterface
|
||||
import android.os.Bundle
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.defaultMinSize
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
@@ -16,55 +17,73 @@ import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.fragment.app.activityViewModels
|
||||
import androidx.fragment.app.FragmentManager
|
||||
import androidx.fragment.app.setFragmentResult
|
||||
import org.signal.core.ui.compose.BottomSheets
|
||||
import org.signal.core.ui.compose.Buttons
|
||||
import org.signal.core.ui.compose.Previews
|
||||
import org.signal.core.ui.compose.SignalPreview
|
||||
import org.thoughtcrime.securesms.R
|
||||
import org.thoughtcrime.securesms.compose.ComposeBottomSheetDialogFragment
|
||||
import org.thoughtcrime.securesms.util.BottomSheetUtil
|
||||
|
||||
/**
|
||||
* Education sheet shown before biometrics when linking a device
|
||||
* Education sheet shown before authentication explaining that users should use their device credentials
|
||||
*/
|
||||
class LinkDeviceAuthEducationSheet : ComposeBottomSheetDialogFragment() {
|
||||
class DevicePinAuthEducationSheet : ComposeBottomSheetDialogFragment() {
|
||||
|
||||
override val peekHeightPercentage: Float = 0.67f
|
||||
|
||||
private val viewModel: LinkDeviceViewModel by activityViewModels()
|
||||
companion object {
|
||||
const val REQUEST_KEY = "DevicePinAuthEducationSheet"
|
||||
|
||||
private const val ARG_TITLE = "arg.title"
|
||||
|
||||
@JvmStatic
|
||||
fun show(title: String, fragmentManager: FragmentManager) {
|
||||
DevicePinAuthEducationSheet().apply {
|
||||
arguments = Bundle().apply {
|
||||
putString(ARG_TITLE, title)
|
||||
}
|
||||
}.show(fragmentManager, BottomSheetUtil.STANDARD_BOTTOM_SHEET_FRAGMENT_TAG)
|
||||
}
|
||||
}
|
||||
|
||||
private val title: String
|
||||
get() = requireArguments().getString(ARG_TITLE)!!
|
||||
|
||||
override fun onDismiss(dialog: DialogInterface) {
|
||||
setFragmentResult(REQUEST_KEY, Bundle())
|
||||
super.onDismiss(dialog)
|
||||
}
|
||||
|
||||
@Composable
|
||||
override fun SheetContent() {
|
||||
DeviceEducationSheet(this::onDismiss)
|
||||
}
|
||||
|
||||
override fun onCancel(dialog: DialogInterface) {
|
||||
viewModel.markBioAuthEducationSheetSeen(true)
|
||||
super.onCancel(dialog)
|
||||
}
|
||||
|
||||
fun onDismiss() {
|
||||
viewModel.markBioAuthEducationSheetSeen(true)
|
||||
dismissAllowingStateLoss()
|
||||
DevicePinAuthEducationSheet(
|
||||
title = title,
|
||||
onClick = { dismissAllowingStateLoss() }
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun DeviceEducationSheet(onClick: () -> Unit) {
|
||||
fun DevicePinAuthEducationSheet(
|
||||
title: String,
|
||||
onClick: () -> Unit
|
||||
) {
|
||||
return Column(
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
) {
|
||||
BottomSheets.Handle()
|
||||
Icon(
|
||||
painter = painterResource(R.drawable.ic_phone_lock),
|
||||
painter = painterResource(R.drawable.phone_lock),
|
||||
contentDescription = null,
|
||||
tint = Color.Unspecified,
|
||||
modifier = Modifier.padding(top = 24.dp)
|
||||
)
|
||||
|
||||
Text(
|
||||
text = stringResource(R.string.LinkDeviceFragment__before_linking),
|
||||
text = title,
|
||||
style = MaterialTheme.typography.titleLarge,
|
||||
textAlign = TextAlign.Center,
|
||||
modifier = Modifier.padding(top = 20.dp, bottom = 8.dp),
|
||||
@@ -89,8 +108,11 @@ private fun DeviceEducationSheet(onClick: () -> Unit) {
|
||||
|
||||
@SignalPreview
|
||||
@Composable
|
||||
fun DeviceEducationSheetPreview() {
|
||||
fun DevicePinAuthEducationSheetPreview() {
|
||||
Previews.BottomSheetPreview {
|
||||
DeviceEducationSheet(onClick = {})
|
||||
DevicePinAuthEducationSheet(
|
||||
title = "To continue, confirm it's you",
|
||||
onClick = {}
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -31,6 +31,7 @@ import org.signal.core.ui.compose.theme.SignalTheme
|
||||
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
|
||||
@@ -66,7 +67,14 @@ class VerifyBackupKeyActivity : PassphraseRequiredActivity() {
|
||||
VerifyBackupPinScreen(
|
||||
backupKey = SignalStore.account.accountEntropyPool.displayValue,
|
||||
onForgotKeyClick = {
|
||||
if (!biometricDeviceAuthentication.authenticate(this, true) { biometricDeviceLockLauncher.launch(getString(R.string.RemoteBackupsSettingsFragment__unlock_to_view_backup_key)) }) {
|
||||
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)) }) {
|
||||
displayBackupKey()
|
||||
}
|
||||
},
|
||||
|
||||
@@ -91,6 +91,7 @@ import org.signal.core.util.logging.Log
|
||||
import org.signal.core.util.mebiBytes
|
||||
import org.signal.core.util.money.FiatMoney
|
||||
import org.thoughtcrime.securesms.BiometricDeviceAuthentication
|
||||
import org.thoughtcrime.securesms.DevicePinAuthEducationSheet
|
||||
import org.thoughtcrime.securesms.R
|
||||
import org.thoughtcrime.securesms.backup.ArchiveUploadProgress
|
||||
import org.thoughtcrime.securesms.backup.DeletionState
|
||||
@@ -214,7 +215,14 @@ class RemoteBackupsSettingsFragment : ComposeFragment() {
|
||||
}
|
||||
|
||||
override fun onViewBackupKeyClick() {
|
||||
if (!biometricDeviceAuthentication.authenticate(requireContext(), true, this@RemoteBackupsSettingsFragment::showConfirmDeviceCredentialIntent)) {
|
||||
if (biometricDeviceAuthentication.shouldShowEducationSheet(requireContext())) {
|
||||
DevicePinAuthEducationSheet.show(getString(R.string.RemoteBackupsSettingsFragment__to_view_your_key), parentFragmentManager)
|
||||
parentFragmentManager.setFragmentResultListener(DevicePinAuthEducationSheet.REQUEST_KEY, viewLifecycleOwner) { _, _ ->
|
||||
if (!biometricDeviceAuthentication.authenticate(requireContext(), true, this@RemoteBackupsSettingsFragment::showConfirmDeviceCredentialIntent)) {
|
||||
displayBackupKey()
|
||||
}
|
||||
}
|
||||
} else if (!biometricDeviceAuthentication.authenticate(requireContext(), true, this@RemoteBackupsSettingsFragment::showConfirmDeviceCredentialIntent)) {
|
||||
displayBackupKey()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -30,7 +30,6 @@ public class UiHintValues extends SignalStoreValues {
|
||||
private static final String HAS_EVER_ENABLED_REMOTE_BACKUPS = "uihints.has_ever_enabled_remote_backups";
|
||||
private static final String HAS_SEEN_CHAT_FOLDERS_EDUCATION_SHEET = "uihints.has_seen_chat_folders_education_sheet";
|
||||
private static final String HAS_SEEN_LINK_DEVICE_QR_EDUCATION_SHEET = "uihints.has_seen_link_device_qr_education_sheet";
|
||||
private static final String LAST_SEEN_LINK_DEVICE_AUTH_SHEET_TIME = "uihints.last_seen_link_device_auth_sheet_time";
|
||||
private static final String HAS_DISMISSED_SAVE_STORAGE_WARNING = "uihints.has_dismissed_save_storage_warning";
|
||||
|
||||
UiHintValues(@NonNull KeyValueStore store) {
|
||||
@@ -230,14 +229,6 @@ public class UiHintValues extends SignalStoreValues {
|
||||
return getBoolean(HAS_SEEN_LINK_DEVICE_QR_EDUCATION_SHEET, false);
|
||||
}
|
||||
|
||||
public void setLastSeenLinkDeviceAuthSheetTime(long time) {
|
||||
putLong(LAST_SEEN_LINK_DEVICE_AUTH_SHEET_TIME, time);
|
||||
}
|
||||
|
||||
public long getLastSeenLinkDeviceAuthSheetTime() {
|
||||
return getLong(LAST_SEEN_LINK_DEVICE_AUTH_SHEET_TIME, 0);
|
||||
}
|
||||
|
||||
public boolean hasDismissedSaveStorageWarning() {
|
||||
return getBoolean(HAS_DISMISSED_SAVE_STORAGE_WARNING, false);
|
||||
}
|
||||
|
||||
@@ -68,6 +68,7 @@ import org.signal.core.ui.compose.SignalPreview
|
||||
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.R
|
||||
import org.thoughtcrime.securesms.compose.ComposeFragment
|
||||
import org.thoughtcrime.securesms.linkdevice.LinkDeviceSettingsState.DialogState
|
||||
@@ -161,7 +162,7 @@ class LinkDeviceFragment : ComposeFragment() {
|
||||
Toast.makeText(context, context.getString(R.string.DeviceListActivity_network_failed), Toast.LENGTH_LONG).show()
|
||||
}
|
||||
LinkDeviceSettingsState.OneTimeEvent.LaunchQrCodeScanner -> {
|
||||
navController.navigateToQrScannerIfAuthed(state.seenBioAuthEducationSheet)
|
||||
navController.navigateToQrScannerIfAuthed()
|
||||
}
|
||||
LinkDeviceSettingsState.OneTimeEvent.ShowFinishedSheet -> {
|
||||
navController.safeNavigate(R.id.action_linkDeviceFragment_to_linkDeviceFinishedSheet)
|
||||
@@ -185,15 +186,6 @@ class LinkDeviceFragment : ComposeFragment() {
|
||||
}
|
||||
}
|
||||
|
||||
LaunchedEffect(state.seenBioAuthEducationSheet) {
|
||||
if (state.seenBioAuthEducationSheet) {
|
||||
if (!biometricAuth.authenticate(requireContext(), true) { biometricDeviceLockLauncher.launch(getString(R.string.LinkDeviceFragment__unlock_to_link)) }) {
|
||||
navController.safeNavigate(R.id.action_linkDeviceFragment_to_addLinkDeviceFragment)
|
||||
}
|
||||
viewModel.markBioAuthEducationSheetSeen(false)
|
||||
}
|
||||
}
|
||||
|
||||
Scaffolds.Settings(
|
||||
title = stringResource(id = R.string.preferences__linked_devices),
|
||||
onNavigationClick = { navController.popOrFinish() },
|
||||
@@ -206,7 +198,7 @@ class LinkDeviceFragment : ComposeFragment() {
|
||||
onLearnMoreClicked = { navController.safeNavigate(R.id.action_linkDeviceFragment_to_linkDeviceLearnMoreBottomSheet) },
|
||||
onLinkNewDeviceClicked = {
|
||||
viewModel.stopExistingPolling()
|
||||
navController.navigateToQrScannerIfAuthed(!state.needsBioAuthEducationSheet)
|
||||
navController.navigateToQrScannerIfAuthed()
|
||||
},
|
||||
onDeviceSelectedForRemoval = { device -> viewModel.setDeviceToRemove(device) },
|
||||
onDeviceRemovalConfirmed = { device -> viewModel.removeDevice(device) },
|
||||
@@ -236,14 +228,15 @@ class LinkDeviceFragment : ComposeFragment() {
|
||||
return SupportEmailUtil.generateSupportEmailBody(requireContext(), filter, prefix.toString(), null)
|
||||
}
|
||||
|
||||
private fun NavController.navigateToQrScannerIfAuthed(seenEducation: Boolean) {
|
||||
if (seenEducation && biometricAuth.canAuthenticate(requireContext())) {
|
||||
if (!biometricAuth.authenticate(requireContext(), true) { biometricDeviceLockLauncher.launch(getString(R.string.LinkDeviceFragment__unlock_to_link)) }) {
|
||||
this.safeNavigate(R.id.action_linkDeviceFragment_to_addLinkDeviceFragment)
|
||||
private fun NavController.navigateToQrScannerIfAuthed() {
|
||||
if (biometricAuth.shouldShowEducationSheet(requireContext())) {
|
||||
DevicePinAuthEducationSheet.show(getString(R.string.LinkDeviceFragment__before_linking), parentFragmentManager)
|
||||
parentFragmentManager.setFragmentResultListener(DevicePinAuthEducationSheet.REQUEST_KEY, viewLifecycleOwner) { _, _ ->
|
||||
if (!biometricAuth.authenticate(requireContext(), true) { biometricDeviceLockLauncher.launch(getString(R.string.LinkDeviceFragment__unlock_to_link)) }) {
|
||||
this.safeNavigate(R.id.action_linkDeviceFragment_to_addLinkDeviceFragment)
|
||||
}
|
||||
}
|
||||
} else if (biometricAuth.canAuthenticate(requireContext())) {
|
||||
this.safeNavigate(R.id.action_linkDeviceFragment_to_linkDeviceEducationSheet)
|
||||
} else {
|
||||
} else if (!biometricAuth.authenticate(requireContext(), true) { biometricDeviceLockLauncher.launch(getString(R.string.LinkDeviceFragment__unlock_to_link)) }) {
|
||||
this.safeNavigate(R.id.action_linkDeviceFragment_to_addLinkDeviceFragment)
|
||||
}
|
||||
}
|
||||
@@ -609,8 +602,7 @@ private fun DeviceListScreenPreview() {
|
||||
Device(1, "Sam's Macbook Pro", 1715793982000, 1716053182000),
|
||||
Device(1, "Sam's iPad", 1715793182000, 1716053122000)
|
||||
),
|
||||
seenQrEducationSheet = true,
|
||||
seenBioAuthEducationSheet = true
|
||||
seenQrEducationSheet = true
|
||||
)
|
||||
)
|
||||
}
|
||||
@@ -623,8 +615,7 @@ private fun DeviceListScreenLoadingPreview() {
|
||||
DeviceListScreen(
|
||||
state = LinkDeviceSettingsState(
|
||||
deviceListLoading = true,
|
||||
seenQrEducationSheet = true,
|
||||
seenBioAuthEducationSheet = true
|
||||
seenQrEducationSheet = true
|
||||
)
|
||||
)
|
||||
}
|
||||
@@ -637,8 +628,7 @@ private fun DeviceListScreenLinkingPreview() {
|
||||
DeviceListScreen(
|
||||
state = LinkDeviceSettingsState(
|
||||
dialogState = DialogState.Linking,
|
||||
seenQrEducationSheet = true,
|
||||
seenBioAuthEducationSheet = true
|
||||
seenQrEducationSheet = true
|
||||
)
|
||||
)
|
||||
}
|
||||
@@ -651,8 +641,7 @@ private fun DeviceListScreenUnlinkingPreview() {
|
||||
DeviceListScreen(
|
||||
state = LinkDeviceSettingsState(
|
||||
dialogState = DialogState.Unlinking,
|
||||
seenQrEducationSheet = true,
|
||||
seenBioAuthEducationSheet = true
|
||||
seenQrEducationSheet = true
|
||||
)
|
||||
)
|
||||
}
|
||||
@@ -665,8 +654,7 @@ private fun DeviceListScreenSyncingMessagesPreview() {
|
||||
DeviceListScreen(
|
||||
state = LinkDeviceSettingsState(
|
||||
dialogState = DialogState.SyncingMessages(1, 1),
|
||||
seenQrEducationSheet = true,
|
||||
seenBioAuthEducationSheet = true
|
||||
seenQrEducationSheet = true
|
||||
)
|
||||
)
|
||||
}
|
||||
@@ -679,8 +667,7 @@ private fun DeviceListScreenSyncingFailedRetryPreview() {
|
||||
DeviceListScreen(
|
||||
state = LinkDeviceSettingsState(
|
||||
dialogState = DialogState.SyncingTimedOut,
|
||||
seenQrEducationSheet = true,
|
||||
seenBioAuthEducationSheet = true
|
||||
seenQrEducationSheet = true
|
||||
)
|
||||
)
|
||||
}
|
||||
@@ -697,8 +684,7 @@ private fun DeviceListScreenSyncingFailedPreview() {
|
||||
deviceCreatedAt = 1,
|
||||
syncFailType = LinkDeviceSettingsState.SyncFailType.NOT_RETRYABLE
|
||||
),
|
||||
seenQrEducationSheet = true,
|
||||
seenBioAuthEducationSheet = true
|
||||
seenQrEducationSheet = true
|
||||
)
|
||||
)
|
||||
}
|
||||
@@ -711,8 +697,7 @@ private fun DeviceListScreenContactSupportPreview() {
|
||||
DeviceListScreen(
|
||||
state = LinkDeviceSettingsState(
|
||||
dialogState = DialogState.ContactSupport,
|
||||
seenQrEducationSheet = true,
|
||||
seenBioAuthEducationSheet = true
|
||||
seenQrEducationSheet = true
|
||||
)
|
||||
)
|
||||
}
|
||||
@@ -725,7 +710,6 @@ private fun DeviceListScreenDeviceUnlinkedPreview() {
|
||||
DeviceListScreen(
|
||||
state = LinkDeviceSettingsState(
|
||||
dialogState = DialogState.DeviceUnlinked(1736454440342),
|
||||
seenBioAuthEducationSheet = true,
|
||||
seenQrEducationSheet = true
|
||||
)
|
||||
)
|
||||
@@ -743,8 +727,7 @@ private fun DeviceListScreenNotEnoughStoragePreview() {
|
||||
deviceCreatedAt = 1,
|
||||
syncFailType = LinkDeviceSettingsState.SyncFailType.NOT_ENOUGH_SPACE
|
||||
),
|
||||
seenQrEducationSheet = true,
|
||||
seenBioAuthEducationSheet = true
|
||||
seenQrEducationSheet = true
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
@@ -3,7 +3,6 @@ package org.thoughtcrime.securesms.linkdevice
|
||||
import android.net.Uri
|
||||
import org.thoughtcrime.securesms.keyvalue.SignalStore
|
||||
import org.thoughtcrime.securesms.linkdevice.LinkDeviceRepository.LinkDeviceResult
|
||||
import kotlin.time.Duration.Companion.days
|
||||
|
||||
/**
|
||||
* Information about linked devices. Used in [LinkDeviceViewModel].
|
||||
@@ -19,8 +18,6 @@ data class LinkDeviceSettingsState(
|
||||
val linkUri: Uri? = null,
|
||||
val linkDeviceResult: LinkDeviceResult = LinkDeviceResult.None,
|
||||
val seenQrEducationSheet: Boolean = SignalStore.uiHints.hasSeenLinkDeviceQrEducationSheet() || SignalStore.account.hasLinkedDevices,
|
||||
val seenBioAuthEducationSheet: Boolean = false,
|
||||
val needsBioAuthEducationSheet: Boolean = !seenBioAuthEducationSheet && SignalStore.uiHints.lastSeenLinkDeviceAuthSheetTime < System.currentTimeMillis() - 30.days.inWholeMilliseconds,
|
||||
val bottomSheetVisible: Boolean = false,
|
||||
val deviceToEdit: Device? = null,
|
||||
val shouldCancelArchiveUpload: Boolean = false,
|
||||
|
||||
@@ -261,16 +261,6 @@ class LinkDeviceViewModel : ViewModel() {
|
||||
}
|
||||
}
|
||||
|
||||
fun markBioAuthEducationSheetSeen(seen: Boolean) {
|
||||
SignalStore.uiHints.lastSeenLinkDeviceAuthSheetTime = System.currentTimeMillis()
|
||||
_state.update {
|
||||
it.copy(
|
||||
seenBioAuthEducationSheet = seen,
|
||||
needsBioAuthEducationSheet = false
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun addDeviceWithSync(linkUri: Uri) {
|
||||
Log.d(TAG, "[addDeviceWithSync] Beginning device adding process.")
|
||||
|
||||
|
||||
@@ -8144,6 +8144,8 @@
|
||||
<string name="RemoteBackupsSettingsFragment__view_backup_key">View backup key</string>
|
||||
<!-- Prompt title for unlocking device to view backup key -->
|
||||
<string name="RemoteBackupsSettingsFragment__unlock_to_view_backup_key">Unlock to view backup key</string>
|
||||
<!-- Title of bottom sheet explaining that authentication is required to view your key -->
|
||||
<string name="RemoteBackupsSettingsFragment__to_view_your_key">To view your key, confirm it\'s you</string>
|
||||
<!-- Row label for cancelling and deleting backup -->
|
||||
<string name="RemoteBackupsSettingsFragment__turn_off_and_delete_backup">Turn off and delete backup</string>
|
||||
<!-- Snackbar text displayed when backup has been deleted and turned off -->
|
||||
|
||||
Reference in New Issue
Block a user