Prompt to save PIN to device password manager.

Fixes an issue where the system auto-fill could overwrite the Signal backup key saved to the device password manager with the Signal PIN. The PIN confirmation screen now explicitly uses `CredentialManager` to save the `Signal PIN` under a separate username from the `Signal Backups` key, allowing both credentials to be stored and auto-filled correctly.

- Add `com.google.android.libraries.identity.googleid` dependency so `CredentialManager` works on Android < 14.
- Prompt to save Signal PIN to credential manager after PIN is created/edited.
This commit is contained in:
jeffrey-signal
2025-07-08 11:53:01 -04:00
committed by Alex Hart
parent ef874c4091
commit 6d58e89c18
11 changed files with 181 additions and 125 deletions

View File

@@ -8,6 +8,9 @@ import androidx.core.content.ContextCompat
import androidx.core.view.ViewCompat
import androidx.lifecycle.ViewModelProvider
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import org.thoughtcrime.securesms.R
import org.thoughtcrime.securesms.dependencies.AppDependencies
import org.thoughtcrime.securesms.lock.v2.ConfirmSvrPinViewModel.SaveAnimation
@@ -15,6 +18,7 @@ import org.thoughtcrime.securesms.megaphone.Megaphones
import org.thoughtcrime.securesms.registration.util.RegistrationUtil
import org.thoughtcrime.securesms.storage.StorageSyncHelper
import org.thoughtcrime.securesms.util.SpanUtil
import org.thoughtcrime.securesms.util.storage.AndroidCredentialRepository
internal class ConfirmSvrPinFragment : BaseSvrPinFragment<ConfirmSvrPinViewModel>() {
@@ -25,6 +29,8 @@ internal class ConfirmSvrPinFragment : BaseSvrPinFragment<ConfirmSvrPinViewModel
} else {
initializeViewStatesForPinCreate()
}
ViewCompat.setImportantForAutofill(input, View.IMPORTANT_FOR_AUTOFILL_YES)
ViewCompat.setAutofillHints(input, HintConstants.AUTOFILL_HINT_NEW_PASSWORD)
}
@@ -64,6 +70,7 @@ internal class ConfirmSvrPinFragment : BaseSvrPinFragment<ConfirmSvrPinViewModel
label.setText(R.string.ConfirmKbsPinFragment__creating_pin)
input.isEnabled = false
}
ConfirmSvrPinViewModel.LabelState.RE_ENTER_PIN -> label.setText(R.string.ConfirmKbsPinFragment__re_enter_your_pin)
ConfirmSvrPinViewModel.LabelState.PIN_DOES_NOT_MATCH -> {
label.text = SpanUtil.color(
@@ -85,7 +92,9 @@ internal class ConfirmSvrPinFragment : BaseSvrPinFragment<ConfirmSvrPinViewModel
closeNavGraphBranch()
RegistrationUtil.maybeMarkRegistrationComplete()
StorageSyncHelper.scheduleSyncForDataChange()
showSavePinToPasswordManagerPrompt()
}
SaveAnimation.FAILURE -> {
confirm.cancelSpinning()
RegistrationUtil.maybeMarkRegistrationComplete()
@@ -118,4 +127,14 @@ internal class ConfirmSvrPinFragment : BaseSvrPinFragment<ConfirmSvrPinViewModel
private fun markMegaphoneSeenIfNecessary() {
AppDependencies.megaphoneRepository.markSeen(Megaphones.Event.PINS_FOR_ALL)
}
private fun showSavePinToPasswordManagerPrompt() {
CoroutineScope(Dispatchers.Main).launch {
AndroidCredentialRepository.saveCredential(
activityContext = requireActivity(),
username = getString(R.string.ConfirmKbsPinFragment__pin_password_manager_id),
password = input.text.toString()
)
}
}
}

View File

@@ -4,9 +4,7 @@ import android.view.animation.Animation
import android.view.animation.TranslateAnimation
import android.widget.EditText
import androidx.annotation.PluralsRes
import androidx.autofill.HintConstants
import androidx.core.content.ContextCompat
import androidx.core.view.ViewCompat
import androidx.lifecycle.ViewModelProvider
import androidx.navigation.Navigation.findNavController
import org.thoughtcrime.securesms.R
@@ -19,16 +17,15 @@ class CreateSvrPinFragment : BaseSvrPinFragment<CreateSvrPinViewModel?>() {
override fun initializeViewStates() {
val args = CreateSvrPinFragmentArgs.fromBundle(requireArguments())
if (args.isPinChange) {
initializeViewStatesForPinChange(args.isForgotPin)
initializeViewStatesForPinChange()
} else {
initializeViewStatesForPinCreate()
}
label.text = getPinLengthRestrictionText(R.plurals.CreateKbsPinFragment__pin_must_be_at_least_digits)
confirm.isEnabled = false
ViewCompat.setAutofillHints(input, HintConstants.AUTOFILL_HINT_NEW_PASSWORD)
}
private fun initializeViewStatesForPinChange(isForgotPin: Boolean) {
private fun initializeViewStatesForPinChange() {
title.setText(R.string.CreateKbsPinFragment__create_a_new_pin)
description.setText(R.string.CreateKbsPinFragment__you_can_choose_a_new_pin_as_long_as_this_device_is_registered)
description.setLearnMoreVisible(true)