Convert registration error handling from callbacks to observers.

This commit is contained in:
Nicholas Tinsley
2024-07-25 21:21:36 +02:00
parent 4fb335de28
commit f87ff58701
6 changed files with 228 additions and 154 deletions
@@ -11,6 +11,9 @@ import com.google.i18n.phonenumbers.Phonenumber
import org.signal.core.util.logging.Log import org.signal.core.util.logging.Log
import org.thoughtcrime.securesms.keyvalue.SignalStore import org.thoughtcrime.securesms.keyvalue.SignalStore
import org.thoughtcrime.securesms.registration.data.network.Challenge import org.thoughtcrime.securesms.registration.data.network.Challenge
import org.thoughtcrime.securesms.registration.data.network.RegisterAccountResult
import org.thoughtcrime.securesms.registration.data.network.RegistrationSessionResult
import org.thoughtcrime.securesms.registration.data.network.VerificationCodeRequestResult
import org.whispersystems.signalservice.api.svr.Svr3Credentials import org.whispersystems.signalservice.api.svr.Svr3Credentials
import org.whispersystems.signalservice.internal.push.AuthCredentials import org.whispersystems.signalservice.internal.push.AuthCredentials
@@ -45,7 +48,10 @@ data class RegistrationState(
val verified: Boolean = false, val verified: Boolean = false,
val smsListenerTimeout: Long = 0L, val smsListenerTimeout: Long = 0L,
val registrationCheckpoint: RegistrationCheckpoint = RegistrationCheckpoint.INITIALIZATION, val registrationCheckpoint: RegistrationCheckpoint = RegistrationCheckpoint.INITIALIZATION,
val networkError: Throwable? = null val networkError: Throwable? = null,
val sessionCreationError: RegistrationSessionResult? = null,
val sessionStateError: VerificationCodeRequestResult? = null,
val registerAccountError: RegisterAccountResult? = null
) { ) {
val challengesRemaining: List<Challenge> = challengesRequested.filterNot { it in challengesPresented } val challengesRemaining: List<Challenge> = challengesRequested.filterNot { it in challengesPresented }
@@ -39,10 +39,10 @@ import org.thoughtcrime.securesms.registration.data.RegistrationRepository
import org.thoughtcrime.securesms.registration.data.network.BackupAuthCheckResult import org.thoughtcrime.securesms.registration.data.network.BackupAuthCheckResult
import org.thoughtcrime.securesms.registration.data.network.Challenge import org.thoughtcrime.securesms.registration.data.network.Challenge
import org.thoughtcrime.securesms.registration.data.network.RegisterAccountResult import org.thoughtcrime.securesms.registration.data.network.RegisterAccountResult
import org.thoughtcrime.securesms.registration.data.network.RegistrationResult
import org.thoughtcrime.securesms.registration.data.network.RegistrationSessionCheckResult import org.thoughtcrime.securesms.registration.data.network.RegistrationSessionCheckResult
import org.thoughtcrime.securesms.registration.data.network.RegistrationSessionCreationResult import org.thoughtcrime.securesms.registration.data.network.RegistrationSessionCreationResult
import org.thoughtcrime.securesms.registration.data.network.RegistrationSessionResult import org.thoughtcrime.securesms.registration.data.network.RegistrationSessionResult
import org.thoughtcrime.securesms.registration.data.network.VerificationCodeRequestResult
import org.thoughtcrime.securesms.registration.data.network.VerificationCodeRequestResult.AlreadyVerified import org.thoughtcrime.securesms.registration.data.network.VerificationCodeRequestResult.AlreadyVerified
import org.thoughtcrime.securesms.registration.data.network.VerificationCodeRequestResult.AttemptsExhausted import org.thoughtcrime.securesms.registration.data.network.VerificationCodeRequestResult.AttemptsExhausted
import org.thoughtcrime.securesms.registration.data.network.VerificationCodeRequestResult.ChallengeRequired import org.thoughtcrime.securesms.registration.data.network.VerificationCodeRequestResult.ChallengeRequired
@@ -96,8 +96,6 @@ class RegistrationViewModel : ViewModel() {
val incorrectCodeAttempts = store.map { it.incorrectCodeAttempts }.asLiveData() val incorrectCodeAttempts = store.map { it.incorrectCodeAttempts }.asLiveData()
val inProgress = store.map { it.inProgress }.asLiveData()
val svrTriesRemaining: Int val svrTriesRemaining: Int
get() = store.value.svrTriesRemaining get() = store.value.svrTriesRemaining
@@ -158,6 +156,24 @@ class RegistrationViewModel : ViewModel() {
} }
} }
fun sessionCreationErrorShown() {
store.update {
it.copy(sessionCreationError = null)
}
}
fun sessionStateErrorShown() {
store.update {
it.copy(sessionStateError = null)
}
}
fun registerAccountErrorShown() {
store.update {
it.copy(registerAccountError = null)
}
}
fun incrementIncorrectCodeAttempts() { fun incrementIncorrectCodeAttempts() {
store.update { store.update {
it.copy(incorrectCodeAttempts = it.incorrectCodeAttempts + 1) it.copy(incorrectCodeAttempts = it.incorrectCodeAttempts + 1)
@@ -202,7 +218,7 @@ class RegistrationViewModel : ViewModel() {
} }
} }
fun onUserConfirmedPhoneNumber(context: Context, errorHandler: (RegistrationResult) -> Unit) { fun onUserConfirmedPhoneNumber(context: Context) {
setRegistrationCheckpoint(RegistrationCheckpoint.PHONE_NUMBER_CONFIRMED) setRegistrationCheckpoint(RegistrationCheckpoint.PHONE_NUMBER_CONFIRMED)
val state = store.value val state = store.value
@@ -253,11 +269,11 @@ class RegistrationViewModel : ViewModel() {
} }
} }
val validSession = getOrCreateValidSession(context, errorHandler) ?: return@launch bail { Log.i(TAG, "Could not create valid session for confirming the entered E164.") } val validSession = getOrCreateValidSession(context) ?: return@launch bail { Log.i(TAG, "Could not create valid session for confirming the entered E164.") }
if (validSession.body.verified) { if (validSession.body.verified) {
Log.i(TAG, "Session is already verified, registering account.") Log.i(TAG, "Session is already verified, registering account.")
registerVerifiedSession(context, validSession.body.id, errorHandler) registerVerifiedSession(context, validSession.body.id)
return@launch return@launch
} }
@@ -269,25 +285,25 @@ class RegistrationViewModel : ViewModel() {
} else { } else {
val challenges = validSession.body.requestedInformation val challenges = validSession.body.requestedInformation
Log.i(TAG, "Not allowed to request code! Remaining challenges: ${challenges.joinToString()}") Log.i(TAG, "Not allowed to request code! Remaining challenges: ${challenges.joinToString()}")
handleSessionStateResult(context, ChallengeRequired(Challenge.parse(validSession.body.requestedInformation)), errorHandler) handleSessionStateResult(context, ChallengeRequired(Challenge.parse(validSession.body.requestedInformation)))
} }
return@launch return@launch
} }
requestSmsCodeInternal(context, validSession.body.id, e164, errorHandler) requestSmsCodeInternal(context, validSession.body.id, e164)
} }
} }
fun requestSmsCode(context: Context, errorHandler: (RegistrationResult) -> Unit) { fun requestSmsCode(context: Context) {
val e164 = getCurrentE164() ?: return bail { Log.i(TAG, "Phone number was null after confirmation.") } val e164 = getCurrentE164() ?: return bail { Log.i(TAG, "Phone number was null after confirmation.") }
viewModelScope.launch { viewModelScope.launch {
val validSession = getOrCreateValidSession(context, errorHandler) ?: return@launch bail { Log.i(TAG, "Could not create valid session for requesting an SMS code.") } val validSession = getOrCreateValidSession(context) ?: return@launch bail { Log.i(TAG, "Could not create valid session for requesting an SMS code.") }
requestSmsCodeInternal(context, validSession.body.id, e164, errorHandler) requestSmsCodeInternal(context, validSession.body.id, e164)
} }
} }
fun requestVerificationCall(context: Context, errorHandler: (RegistrationResult) -> Unit) { fun requestVerificationCall(context: Context) {
val e164 = getCurrentE164() val e164 = getCurrentE164()
if (e164 == null) { if (e164 == null) {
@@ -297,7 +313,7 @@ class RegistrationViewModel : ViewModel() {
} }
viewModelScope.launch { viewModelScope.launch {
val validSession = getOrCreateValidSession(context, errorHandler) ?: return@launch bail { Log.i(TAG, "Could not create valid session for requesting a verification call.") } val validSession = getOrCreateValidSession(context) ?: return@launch bail { Log.i(TAG, "Could not create valid session for requesting a verification call.") }
Log.d(TAG, "Requesting voice call code…") Log.d(TAG, "Requesting voice call code…")
val codeRequestResponse = RegistrationRepository.requestSmsCode( val codeRequestResponse = RegistrationRepository.requestSmsCode(
context = context, context = context,
@@ -308,14 +324,14 @@ class RegistrationViewModel : ViewModel() {
) )
Log.d(TAG, "Voice code request network call completed.") Log.d(TAG, "Voice code request network call completed.")
handleSessionStateResult(context, codeRequestResponse, errorHandler) handleSessionStateResult(context, codeRequestResponse)
if (codeRequestResponse is Success) { if (codeRequestResponse is Success) {
Log.d(TAG, "Voice code request was successful.") Log.d(TAG, "Voice code request was successful.")
} }
} }
} }
private suspend fun requestSmsCodeInternal(context: Context, sessionId: String, e164: String, errorHandler: (RegistrationResult) -> Unit) { private suspend fun requestSmsCodeInternal(context: Context, sessionId: String, e164: String) {
var smsListenerReady = false var smsListenerReady = false
Log.d(TAG, "Initializing SMS listener.") Log.d(TAG, "Initializing SMS listener.")
if (store.value.smsListenerTimeout < System.currentTimeMillis()) { if (store.value.smsListenerTimeout < System.currentTimeMillis()) {
@@ -341,7 +357,7 @@ class RegistrationViewModel : ViewModel() {
) )
Log.d(TAG, "SMS code request network call completed.") Log.d(TAG, "SMS code request network call completed.")
handleSessionStateResult(context, codeRequestResponse, errorHandler) handleSessionStateResult(context, codeRequestResponse)
if (codeRequestResponse is Success) { if (codeRequestResponse is Success) {
Log.d(TAG, "SMS code request was successful.") Log.d(TAG, "SMS code request was successful.")
@@ -353,7 +369,7 @@ class RegistrationViewModel : ViewModel() {
} }
} }
private suspend fun getOrCreateValidSession(context: Context, errorHandler: (RegistrationResult) -> Unit): RegistrationSessionMetadataResponse? { private suspend fun getOrCreateValidSession(context: Context): RegistrationSessionMetadataResponse? {
Log.v(TAG, "getOrCreateValidSession()") Log.v(TAG, "getOrCreateValidSession()")
val e164 = getCurrentE164() ?: throw IllegalStateException("E164 required to create session!") val e164 = getCurrentE164() ?: throw IllegalStateException("E164 required to create session!")
val mccMncProducer = MccMncProducer(context) val mccMncProducer = MccMncProducer(context)
@@ -379,11 +395,17 @@ class RegistrationViewModel : ViewModel() {
) )
} }
}, },
errorHandler = errorHandler errorHandler = { error ->
store.update {
it.copy(
sessionCreationError = error
)
}
}
) )
} }
fun submitCaptchaToken(context: Context, errorHandler: (RegistrationResult) -> Unit) { fun submitCaptchaToken(context: Context) {
val e164 = getCurrentE164() ?: throw IllegalStateException("Can't submit captcha token if no phone number is set!") val e164 = getCurrentE164() ?: throw IllegalStateException("Can't submit captcha token if no phone number is set!")
val captchaToken = store.value.captchaToken ?: throw IllegalStateException("Can't submit captcha token if no captcha token is set!") val captchaToken = store.value.captchaToken ?: throw IllegalStateException("Can't submit captcha token if no captcha token is set!")
@@ -392,16 +414,16 @@ class RegistrationViewModel : ViewModel() {
} }
viewModelScope.launch { viewModelScope.launch {
val session = getOrCreateValidSession(context, errorHandler) ?: return@launch bail { Log.i(TAG, "Could not create valid session for submitting a captcha token.") } val session = getOrCreateValidSession(context) ?: return@launch bail { Log.i(TAG, "Could not create valid session for submitting a captcha token.") }
Log.d(TAG, "Submitting captcha token…") Log.d(TAG, "Submitting captcha token…")
val captchaSubmissionResult = RegistrationRepository.submitCaptchaToken(context, e164, password, session.body.id, captchaToken) val captchaSubmissionResult = RegistrationRepository.submitCaptchaToken(context, e164, password, session.body.id, captchaToken)
Log.d(TAG, "Captcha token submitted.") Log.d(TAG, "Captcha token submitted.")
handleSessionStateResult(context, captchaSubmissionResult, errorHandler) handleSessionStateResult(context, captchaSubmissionResult)
} }
} }
fun requestAndSubmitPushToken(context: Context, errorHandler: (RegistrationResult) -> Unit) { fun requestAndSubmitPushToken(context: Context) {
Log.v(TAG, "validatePushToken()") Log.v(TAG, "validatePushToken()")
addPresentedChallenge(Challenge.PUSH) addPresentedChallenge(Challenge.PUSH)
@@ -410,7 +432,7 @@ class RegistrationViewModel : ViewModel() {
viewModelScope.launch { viewModelScope.launch {
Log.d(TAG, "Getting session in order to perform push token verification…") Log.d(TAG, "Getting session in order to perform push token verification…")
val session = getOrCreateValidSession(context, errorHandler) ?: return@launch bail { Log.i(TAG, "Could not create valid session for submitting a push challenge token.") } val session = getOrCreateValidSession(context) ?: return@launch bail { Log.i(TAG, "Could not create valid session for submitting a push challenge token.") }
if (!Challenge.parse(session.body.requestedInformation).contains(Challenge.PUSH)) { if (!Challenge.parse(session.body.requestedInformation).contains(Challenge.PUSH)) {
Log.d(TAG, "Push submission no longer necessary, bailing.") Log.d(TAG, "Push submission no longer necessary, bailing.")
@@ -425,14 +447,14 @@ class RegistrationViewModel : ViewModel() {
Log.d(TAG, "Requesting push challenge token…") Log.d(TAG, "Requesting push challenge token…")
val pushSubmissionResult = RegistrationRepository.requestAndVerifyPushToken(context, session.body.id, e164, password) val pushSubmissionResult = RegistrationRepository.requestAndVerifyPushToken(context, session.body.id, e164, password)
Log.d(TAG, "Push challenge token submitted.") Log.d(TAG, "Push challenge token submitted.")
handleSessionStateResult(context, pushSubmissionResult, errorHandler) handleSessionStateResult(context, pushSubmissionResult)
} }
} }
/** /**
* @return whether the request was successful and execution should continue * @return whether the request was successful and execution should continue
*/ */
private suspend fun handleSessionStateResult(context: Context, sessionResult: RegistrationResult, errorHandler: (RegistrationResult) -> Unit): Boolean { private suspend fun handleSessionStateResult(context: Context, sessionResult: VerificationCodeRequestResult): Boolean {
Log.v(TAG, "handleSessionStateResult()") Log.v(TAG, "handleSessionStateResult()")
when (sessionResult) { when (sessionResult) {
is UnknownError -> { is UnknownError -> {
@@ -492,14 +514,18 @@ class RegistrationViewModel : ViewModel() {
is AlreadyVerified -> Log.i(TAG, "Received AlreadyVerified", sessionResult.getCause()) is AlreadyVerified -> Log.i(TAG, "Received AlreadyVerified", sessionResult.getCause())
} }
setInProgress(false) setInProgress(false)
errorHandler(sessionResult) store.update {
it.copy(
sessionStateError = sessionResult
)
}
return false return false
} }
/** /**
* @return whether the request was successful and execution should continue * @return whether the request was successful and execution should continue
*/ */
private suspend fun handleRegistrationResult(context: Context, registrationData: RegistrationData, registrationResult: RegisterAccountResult, reglockEnabled: Boolean, errorHandler: (RegisterAccountResult) -> Unit): Boolean { private suspend fun handleRegistrationResult(context: Context, registrationData: RegistrationData, registrationResult: RegisterAccountResult, reglockEnabled: Boolean): Boolean {
Log.v(TAG, "handleRegistrationResult()") Log.v(TAG, "handleRegistrationResult()")
when (registrationResult) { when (registrationResult) {
is RegisterAccountResult.Success -> { is RegisterAccountResult.Success -> {
@@ -521,6 +547,7 @@ class RegistrationViewModel : ViewModel() {
is RegisterAccountResult.RegistrationLocked -> { is RegisterAccountResult.RegistrationLocked -> {
Log.i(TAG, "Account is registration locked!", registrationResult.getCause()) Log.i(TAG, "Account is registration locked!", registrationResult.getCause())
} }
is RegisterAccountResult.SvrWrongPin -> { is RegisterAccountResult.SvrWrongPin -> {
Log.i(TAG, "Received wrong SVR PIN response! ${registrationResult.triesRemaining} tries remaining.") Log.i(TAG, "Received wrong SVR PIN response! ${registrationResult.triesRemaining} tries remaining.")
updateSvrTriesRemaining(registrationResult.triesRemaining) updateSvrTriesRemaining(registrationResult.triesRemaining)
@@ -535,7 +562,11 @@ class RegistrationViewModel : ViewModel() {
is RegisterAccountResult.UnknownError -> Log.i(TAG, "Received error when trying to register!", registrationResult.getCause()) is RegisterAccountResult.UnknownError -> Log.i(TAG, "Received error when trying to register!", registrationResult.getCause())
} }
setInProgress(false) setInProgress(false)
errorHandler(registrationResult) store.update {
it.copy(
registerAccountError = registrationResult
)
}
return false return false
} }
@@ -564,7 +595,7 @@ class RegistrationViewModel : ViewModel() {
} }
} }
fun verifyReRegisterWithPin(context: Context, pin: String, wrongPinHandler: () -> Unit, registrationErrorHandler: (RegisterAccountResult) -> Unit) { fun verifyReRegisterWithPin(context: Context, pin: String, wrongPinHandler: () -> Unit) {
setInProgress(true) setInProgress(true)
// Local recovery password // Local recovery password
@@ -572,7 +603,7 @@ class RegistrationViewModel : ViewModel() {
if (RegistrationRepository.doesPinMatchLocalHash(pin)) { if (RegistrationRepository.doesPinMatchLocalHash(pin)) {
Log.d(TAG, "Found recovery password, attempting to re-register.") Log.d(TAG, "Found recovery password, attempting to re-register.")
viewModelScope.launch(context = coroutineExceptionHandler) { viewModelScope.launch(context = coroutineExceptionHandler) {
verifyReRegisterInternal(context, pin, SignalStore.svr.getOrCreateMasterKey(), registrationErrorHandler) verifyReRegisterInternal(context, pin, SignalStore.svr.getOrCreateMasterKey())
setInProgress(false) setInProgress(false)
} }
} else { } else {
@@ -594,7 +625,7 @@ class RegistrationViewModel : ViewModel() {
val masterKey = RegistrationRepository.fetchMasterKeyFromSvrRemote(pin, svr2Credentials, svr3Credentials) val masterKey = RegistrationRepository.fetchMasterKeyFromSvrRemote(pin, svr2Credentials, svr3Credentials)
setRecoveryPassword(masterKey.deriveRegistrationRecoveryPassword()) setRecoveryPassword(masterKey.deriveRegistrationRecoveryPassword())
updateSvrTriesRemaining(10) updateSvrTriesRemaining(10)
verifyReRegisterInternal(context, pin, masterKey, registrationErrorHandler) verifyReRegisterInternal(context, pin, masterKey)
} catch (rejectedPin: SvrWrongPinException) { } catch (rejectedPin: SvrWrongPinException) {
Log.w(TAG, "Submitted PIN was rejected by SVR.", rejectedPin) Log.w(TAG, "Submitted PIN was rejected by SVR.", rejectedPin)
updateSvrTriesRemaining(rejectedPin.triesRemaining) updateSvrTriesRemaining(rejectedPin.triesRemaining)
@@ -615,7 +646,7 @@ class RegistrationViewModel : ViewModel() {
} }
} }
private suspend fun verifyReRegisterInternal(context: Context, pin: String, masterKey: MasterKey, registrationErrorHandler: (RegisterAccountResult) -> Unit) { private suspend fun verifyReRegisterInternal(context: Context, pin: String, masterKey: MasterKey) {
Log.v(TAG, "verifyReRegisterInternal()") Log.v(TAG, "verifyReRegisterInternal()")
updateFcmToken(context) updateFcmToken(context)
@@ -625,7 +656,7 @@ class RegistrationViewModel : ViewModel() {
val result = resultAndRegLockStatus.first val result = resultAndRegLockStatus.first
val reglockEnabled = resultAndRegLockStatus.second val reglockEnabled = resultAndRegLockStatus.second
handleRegistrationResult(context, registrationData, result, reglockEnabled, registrationErrorHandler) handleRegistrationResult(context, registrationData, result, reglockEnabled)
} }
/** /**
@@ -652,7 +683,7 @@ class RegistrationViewModel : ViewModel() {
return Pair(RegistrationRepository.registerAccount(context = context, sessionId = sessionId, registrationData = registrationData, pin = pin) { masterKey }, true) return Pair(RegistrationRepository.registerAccount(context = context, sessionId = sessionId, registrationData = registrationData, pin = pin) { masterKey }, true)
} }
fun verifyCodeWithoutRegistrationLock(context: Context, code: String, submissionErrorHandler: (RegistrationResult) -> Unit, registrationErrorHandler: (RegisterAccountResult) -> Unit) { fun verifyCodeWithoutRegistrationLock(context: Context, code: String) {
Log.v(TAG, "verifyCodeWithoutRegistrationLock()") Log.v(TAG, "verifyCodeWithoutRegistrationLock()")
store.update { store.update {
it.copy( it.copy(
@@ -665,15 +696,13 @@ class RegistrationViewModel : ViewModel() {
viewModelScope.launch(context = coroutineExceptionHandler) { viewModelScope.launch(context = coroutineExceptionHandler) {
verifyCodeInternal( verifyCodeInternal(
context = context, context = context,
pin = null,
registrationLocked = false, registrationLocked = false,
submissionErrorHandler = submissionErrorHandler, pin = null
registrationErrorHandler = registrationErrorHandler
) )
} }
} }
fun verifyCodeAndRegisterAccountWithRegistrationLock(context: Context, pin: String, submissionErrorHandler: (RegistrationResult) -> Unit, registrationErrorHandler: (RegisterAccountResult) -> Unit) { fun verifyCodeAndRegisterAccountWithRegistrationLock(context: Context, pin: String) {
Log.v(TAG, "verifyCodeAndRegisterAccountWithRegistrationLock()") Log.v(TAG, "verifyCodeAndRegisterAccountWithRegistrationLock()")
store.update { store.update {
it.copy( it.copy(
@@ -684,15 +713,13 @@ class RegistrationViewModel : ViewModel() {
viewModelScope.launch { viewModelScope.launch {
verifyCodeInternal( verifyCodeInternal(
context = context, context = context,
pin = pin,
registrationLocked = true, registrationLocked = true,
submissionErrorHandler = submissionErrorHandler, pin = pin
registrationErrorHandler = registrationErrorHandler
) )
} }
} }
private suspend fun verifyCodeInternal(context: Context, registrationLocked: Boolean, pin: String?, submissionErrorHandler: (RegistrationResult) -> Unit, registrationErrorHandler: (RegisterAccountResult) -> Unit) { private suspend fun verifyCodeInternal(context: Context, registrationLocked: Boolean, pin: String?) {
Log.d(TAG, "Getting valid session in order to submit verification code.") Log.d(TAG, "Getting valid session in order to submit verification code.")
if (registrationLocked && pin.isNullOrBlank()) { if (registrationLocked && pin.isNullOrBlank()) {
@@ -701,7 +728,7 @@ class RegistrationViewModel : ViewModel() {
var reglock = registrationLocked var reglock = registrationLocked
val sessionId = getOrCreateValidSession(context, submissionErrorHandler)?.body?.id ?: return val sessionId = getOrCreateValidSession(context)?.body?.id ?: return
val registrationData = getRegistrationData() val registrationData = getRegistrationData()
Log.d(TAG, "Submitting verification code…") Log.d(TAG, "Submitting verification code…")
@@ -714,7 +741,7 @@ class RegistrationViewModel : ViewModel() {
Log.d(TAG, "Verification code submission network call completed. Submission successful? $submissionSuccessful Account already verified? $alreadyVerified") Log.d(TAG, "Verification code submission network call completed. Submission successful? $submissionSuccessful Account already verified? $alreadyVerified")
if (!submissionSuccessful && !alreadyVerified) { if (!submissionSuccessful && !alreadyVerified) {
handleSessionStateResult(context, verificationResponse, submissionErrorHandler) handleSessionStateResult(context, verificationResponse)
return return
} }
@@ -758,16 +785,16 @@ class RegistrationViewModel : ViewModel() {
} }
if (result != null) { if (result != null) {
handleRegistrationResult(context, registrationData, result, reglock, registrationErrorHandler) handleRegistrationResult(context, registrationData, result, reglock)
} else { } else {
Log.w(TAG, "No registration response received!") Log.w(TAG, "No registration response received!")
} }
} }
private suspend fun registerVerifiedSession(context: Context, sessionId: String, registrationErrorHandler: (RegisterAccountResult) -> Unit) { private suspend fun registerVerifiedSession(context: Context, sessionId: String) {
val registrationData = getRegistrationData() val registrationData = getRegistrationData()
val registrationResponse: RegisterAccountResult = RegistrationRepository.registerAccount(context, sessionId, registrationData) val registrationResponse: RegisterAccountResult = RegistrationRepository.registerAccount(context, sessionId, registrationData)
handleRegistrationResult(context, registrationData, registrationResponse, false, registrationErrorHandler) handleRegistrationResult(context, registrationData, registrationResponse, false)
} }
private suspend fun onSuccessfulRegistration(context: Context, registrationData: RegistrationData, remoteResult: RegistrationRepository.AccountRegistrationResult, reglockEnabled: Boolean) { private suspend fun onSuccessfulRegistration(context: Context, registrationData: RegistrationData, remoteResult: RegistrationRepository.AccountRegistrationResult, reglockEnabled: Boolean) {
@@ -813,7 +840,7 @@ class RegistrationViewModel : ViewModel() {
RegistrationUtil.maybeMarkRegistrationComplete() RegistrationUtil.maybeMarkRegistrationComplete()
} }
fun clearNetworkError() { fun networkErrorShown() {
store.update { store.update {
it.copy(networkError = null) it.copy(networkError = null)
} }
@@ -11,12 +11,10 @@ import android.view.View
import android.widget.Toast import android.widget.Toast
import androidx.activity.OnBackPressedCallback import androidx.activity.OnBackPressedCallback
import androidx.fragment.app.activityViewModels import androidx.fragment.app.activityViewModels
import androidx.lifecycle.lifecycleScope
import androidx.navigation.fragment.NavHostFragment import androidx.navigation.fragment.NavHostFragment
import androidx.navigation.fragment.findNavController import androidx.navigation.fragment.findNavController
import com.google.android.material.dialog.MaterialAlertDialogBuilder import com.google.android.material.dialog.MaterialAlertDialogBuilder
import com.google.i18n.phonenumbers.PhoneNumberUtil import com.google.i18n.phonenumbers.PhoneNumberUtil
import kotlinx.coroutines.launch
import org.greenrobot.eventbus.Subscribe import org.greenrobot.eventbus.Subscribe
import org.greenrobot.eventbus.ThreadMode import org.greenrobot.eventbus.ThreadMode
import org.signal.core.util.logging.Log import org.signal.core.util.logging.Log
@@ -76,7 +74,7 @@ class EnterCodeFragment : LoggingFragment(R.layout.fragment_registration_enter_c
} }
binding.code.setOnCompleteListener { binding.code.setOnCompleteListener {
sharedViewModel.verifyCodeWithoutRegistrationLock(requireContext(), it, ::handleSessionErrorResponse, ::handleRegistrationErrorResponse) sharedViewModel.verifyCodeWithoutRegistrationLock(requireContext(), it)
} }
binding.havingTroubleButton.setOnClickListener { binding.havingTroubleButton.setOnClickListener {
@@ -86,14 +84,14 @@ class EnterCodeFragment : LoggingFragment(R.layout.fragment_registration_enter_c
binding.callMeCountDown.apply { binding.callMeCountDown.apply {
setTextResources(R.string.RegistrationActivity_call, R.string.RegistrationActivity_call_me_instead_available_in) setTextResources(R.string.RegistrationActivity_call, R.string.RegistrationActivity_call_me_instead_available_in)
setOnClickListener { setOnClickListener {
sharedViewModel.requestVerificationCall(requireContext(), ::handleSessionErrorResponse) sharedViewModel.requestVerificationCall(requireContext())
} }
} }
binding.resendSmsCountDown.apply { binding.resendSmsCountDown.apply {
setTextResources(R.string.RegistrationActivity_resend_code, R.string.RegistrationActivity_resend_sms_available_in) setTextResources(R.string.RegistrationActivity_resend_code, R.string.RegistrationActivity_resend_sms_available_in)
setOnClickListener { setOnClickListener {
sharedViewModel.requestSmsCode(requireContext(), ::handleSessionErrorResponse) sharedViewModel.requestSmsCode(requireContext())
} }
} }
@@ -114,6 +112,16 @@ class EnterCodeFragment : LoggingFragment(R.layout.fragment_registration_enter_c
} }
sharedViewModel.uiState.observe(viewLifecycleOwner) { sharedViewModel.uiState.observe(viewLifecycleOwner) {
it.sessionStateError?.let { error ->
handleSessionErrorResponse(error)
sharedViewModel.sessionStateErrorShown()
}
it.registerAccountError?.let { error ->
handleRegistrationErrorResponse(error)
sharedViewModel.registerAccountErrorShown()
}
binding.resendSmsCountDown.startCountDownTo(it.nextSmsTimestamp) binding.resendSmsCountDown.startCountDownTo(it.nextSmsTimestamp)
binding.callMeCountDown.startCountDownTo(it.nextCallTimestamp) binding.callMeCountDown.startCountDownTo(it.nextCallTimestamp)
if (it.inProgress) { if (it.inProgress) {
@@ -132,29 +140,25 @@ class EnterCodeFragment : LoggingFragment(R.layout.fragment_registration_enter_c
} }
} }
private fun handleSessionErrorResponse(result: RegistrationResult) { private fun handleSessionErrorResponse(result: VerificationCodeRequestResult) {
viewLifecycleOwner.lifecycleScope.launch { when (result) {
when (result) { is VerificationCodeRequestResult.Success -> throw IllegalStateException("Session error handler called on successful response!")
is VerificationCodeRequestResult.Success -> binding.keyboard.displaySuccess() is VerificationCodeRequestResult.RateLimited -> presentRateLimitedDialog()
is VerificationCodeRequestResult.RateLimited -> presentRateLimitedDialog() is VerificationCodeRequestResult.AttemptsExhausted -> presentAccountLocked()
is VerificationCodeRequestResult.AttemptsExhausted -> presentAccountLocked() is VerificationCodeRequestResult.RegistrationLocked -> presentRegistrationLocked(result.timeRemaining)
is VerificationCodeRequestResult.RegistrationLocked -> presentRegistrationLocked(result.timeRemaining) else -> presentGenericError(result)
else -> presentGenericError(result)
}
} }
} }
private fun handleRegistrationErrorResponse(result: RegisterAccountResult) { private fun handleRegistrationErrorResponse(result: RegisterAccountResult) {
viewLifecycleOwner.lifecycleScope.launch { when (result) {
when (result) { is RegisterAccountResult.Success -> throw IllegalStateException("Register account error handler called on successful response!")
is RegisterAccountResult.Success -> binding.keyboard.displaySuccess() is RegisterAccountResult.RegistrationLocked -> presentRegistrationLocked(result.timeRemaining)
is RegisterAccountResult.RegistrationLocked -> presentRegistrationLocked(result.timeRemaining) is RegisterAccountResult.AuthorizationFailed -> presentIncorrectCodeDialog()
is RegisterAccountResult.AuthorizationFailed -> presentIncorrectCodeDialog() is RegisterAccountResult.AttemptsExhausted -> presentAccountLocked()
is RegisterAccountResult.AttemptsExhausted -> presentAccountLocked() is RegisterAccountResult.RateLimited -> presentRateLimitedDialog()
is RegisterAccountResult.RateLimited -> presentRateLimitedDialog()
else -> presentGenericError(result) else -> presentGenericError(result)
}
} }
} }
@@ -202,19 +206,17 @@ class EnterCodeFragment : LoggingFragment(R.layout.fragment_registration_enter_c
private fun presentIncorrectCodeDialog() { private fun presentIncorrectCodeDialog() {
sharedViewModel.incrementIncorrectCodeAttempts() sharedViewModel.incrementIncorrectCodeAttempts()
viewLifecycleOwner.lifecycleScope.launch { Toast.makeText(requireContext(), R.string.RegistrationActivity_incorrect_code, Toast.LENGTH_LONG).show()
Toast.makeText(requireContext(), R.string.RegistrationActivity_incorrect_code, Toast.LENGTH_LONG).show()
binding.keyboard.displayFailure().addListener(object : AssertedSuccessListener<Boolean?>() { binding.keyboard.displayFailure().addListener(object : AssertedSuccessListener<Boolean?>() {
override fun onSuccess(result: Boolean?) { override fun onSuccess(result: Boolean?) {
binding.callMeCountDown.visibility = View.VISIBLE binding.callMeCountDown.visibility = View.VISIBLE
binding.resendSmsCountDown.visibility = View.VISIBLE binding.resendSmsCountDown.visibility = View.VISIBLE
binding.wrongNumber.visibility = View.VISIBLE binding.wrongNumber.visibility = View.VISIBLE
binding.code.clear() binding.code.clear()
binding.keyboard.displayKeyboard() binding.keyboard.displayKeyboard()
} }
}) })
}
} }
private fun presentGenericError(requestResult: RegistrationResult) { private fun presentGenericError(requestResult: RegistrationResult) {
@@ -25,7 +25,6 @@ import androidx.core.view.MenuProvider
import androidx.core.widget.addTextChangedListener import androidx.core.widget.addTextChangedListener
import androidx.fragment.app.activityViewModels import androidx.fragment.app.activityViewModels
import androidx.fragment.app.viewModels import androidx.fragment.app.viewModels
import androidx.lifecycle.lifecycleScope
import androidx.navigation.fragment.NavHostFragment import androidx.navigation.fragment.NavHostFragment
import androidx.navigation.fragment.findNavController import androidx.navigation.fragment.findNavController
import com.google.android.gms.common.ConnectionResult import com.google.android.gms.common.ConnectionResult
@@ -36,7 +35,6 @@ import com.google.android.material.textfield.TextInputEditText
import com.google.i18n.phonenumbers.NumberParseException import com.google.i18n.phonenumbers.NumberParseException
import com.google.i18n.phonenumbers.PhoneNumberUtil import com.google.i18n.phonenumbers.PhoneNumberUtil
import com.google.i18n.phonenumbers.Phonenumber.PhoneNumber import com.google.i18n.phonenumbers.Phonenumber.PhoneNumber
import kotlinx.coroutines.launch
import org.signal.core.util.isNotNullOrBlank import org.signal.core.util.isNotNullOrBlank
import org.signal.core.util.logging.Log import org.signal.core.util.logging.Log
import org.thoughtcrime.securesms.LoggingFragment import org.thoughtcrime.securesms.LoggingFragment
@@ -46,8 +44,9 @@ import org.thoughtcrime.securesms.databinding.FragmentRegistrationEnterPhoneNumb
import org.thoughtcrime.securesms.phonenumbers.PhoneNumberFormatter import org.thoughtcrime.securesms.phonenumbers.PhoneNumberFormatter
import org.thoughtcrime.securesms.registration.data.RegistrationRepository import org.thoughtcrime.securesms.registration.data.RegistrationRepository
import org.thoughtcrime.securesms.registration.data.network.Challenge import org.thoughtcrime.securesms.registration.data.network.Challenge
import org.thoughtcrime.securesms.registration.data.network.RegistrationResult import org.thoughtcrime.securesms.registration.data.network.RegistrationSessionCheckResult
import org.thoughtcrime.securesms.registration.data.network.RegistrationSessionCreationResult import org.thoughtcrime.securesms.registration.data.network.RegistrationSessionCreationResult
import org.thoughtcrime.securesms.registration.data.network.RegistrationSessionResult
import org.thoughtcrime.securesms.registration.data.network.VerificationCodeRequestResult import org.thoughtcrime.securesms.registration.data.network.VerificationCodeRequestResult
import org.thoughtcrime.securesms.registration.fragments.RegistrationViewDelegate.setDebugLogSubmitMultiTapView import org.thoughtcrime.securesms.registration.fragments.RegistrationViewDelegate.setDebugLogSubmitMultiTapView
import org.thoughtcrime.securesms.registration.ui.RegistrationCheckpoint import org.thoughtcrime.securesms.registration.ui.RegistrationCheckpoint
@@ -116,10 +115,21 @@ class EnterPhoneNumberFragment : LoggingFragment(R.layout.fragment_registration_
sharedState.networkError?.let { sharedState.networkError?.let {
presentNetworkError(it) presentNetworkError(it)
sharedViewModel.networkErrorShown()
}
sharedState.sessionCreationError?.let {
handleSessionCreationError(it)
sharedViewModel.sessionCreationErrorShown()
}
sharedState.sessionStateError?.let {
handleSessionStateError(it)
sharedViewModel.sessionStateErrorShown()
} }
if (sharedState.challengesRequested.contains(Challenge.CAPTCHA) && sharedState.captchaToken.isNotNullOrBlank()) { if (sharedState.challengesRequested.contains(Challenge.CAPTCHA) && sharedState.captchaToken.isNotNullOrBlank()) {
sharedViewModel.submitCaptchaToken(requireContext(), ::handleErrorResponse) sharedViewModel.submitCaptchaToken(requireContext())
} else if (sharedState.challengesRemaining.isNotEmpty()) { } else if (sharedState.challengesRemaining.isNotEmpty()) {
handleChallenges(sharedState.challengesRemaining) handleChallenges(sharedState.challengesRemaining)
} else if (sharedState.registrationCheckpoint >= RegistrationCheckpoint.PHONE_NUMBER_CONFIRMED && sharedState.canSkipSms) { } else if (sharedState.registrationCheckpoint >= RegistrationCheckpoint.PHONE_NUMBER_CONFIRMED && sharedState.canSkipSms) {
@@ -184,7 +194,7 @@ class EnterPhoneNumberFragment : LoggingFragment(R.layout.fragment_registration_
} }
private fun performPushChallenge() { private fun performPushChallenge() {
sharedViewModel.requestAndSubmitPushToken(requireContext(), ::handleErrorResponse) sharedViewModel.requestAndSubmitPushToken(requireContext())
} }
private fun initializeInputFields() { private fun initializeInputFields() {
@@ -294,67 +304,82 @@ class EnterPhoneNumberFragment : LoggingFragment(R.layout.fragment_registration_
Log.i(TAG, "Unknown error during verification code request", networkError) Log.i(TAG, "Unknown error during verification code request", networkError)
MaterialAlertDialogBuilder(requireContext()).apply { MaterialAlertDialogBuilder(requireContext()).apply {
setMessage(R.string.RegistrationActivity_unable_to_connect_to_service) setMessage(R.string.RegistrationActivity_unable_to_connect_to_service)
setPositiveButton(android.R.string.ok) { _, _ -> sharedViewModel.clearNetworkError() } setPositiveButton(android.R.string.ok, null)
setOnCancelListener { sharedViewModel.clearNetworkError() }
setOnDismissListener { sharedViewModel.clearNetworkError() }
show() show()
} }
} }
private fun handleErrorResponse(result: RegistrationResult) { private fun handleSessionCreationError(result: RegistrationSessionResult) {
viewLifecycleOwner.lifecycleScope.launch { if (!result.isSuccess()) {
if (!result.isSuccess()) { Log.i(TAG, "Handling error response.", result.getCause())
Log.i(TAG, "Handling error response.", result.getCause()) }
when (result) {
is RegistrationSessionCheckResult.Success,
is RegistrationSessionCreationResult.Success -> throw IllegalStateException("Session error handler called on successful response!")
is RegistrationSessionCreationResult.AttemptsExhausted -> presentRemoteErrorDialog(getString(R.string.RegistrationActivity_rate_limited_to_service))
is RegistrationSessionCreationResult.MalformedRequest -> presentRemoteErrorDialog(getString(R.string.RegistrationActivity_unable_to_connect_to_service), skipToNextScreen)
is RegistrationSessionCreationResult.RateLimited -> {
Log.i(TAG, "Session creation rate limited! Next attempt: ${result.timeRemaining.milliseconds}")
presentRemoteErrorDialog(getString(R.string.RegistrationActivity_rate_limited_to_try_again, result.timeRemaining.milliseconds.toString()))
} }
when (result) {
is RegistrationSessionCreationResult.Success,
is VerificationCodeRequestResult.Success -> Unit
is RegistrationSessionCreationResult.AttemptsExhausted, is RegistrationSessionCreationResult.ServerUnableToParse -> presentRemoteErrorDialog(getString(R.string.RegistrationActivity_unable_to_connect_to_service))
is VerificationCodeRequestResult.AttemptsExhausted -> presentRemoteErrorDialog(getString(R.string.RegistrationActivity_rate_limited_to_service)) is RegistrationSessionCheckResult.SessionNotFound -> presentRemoteErrorDialog(getString(R.string.RegistrationActivity_unable_to_connect_to_service))
is RegistrationSessionCheckResult.UnknownError,
is RegistrationSessionCreationResult.UnknownError -> presentRemoteErrorDialog(getString(R.string.RegistrationActivity_unable_to_connect_to_service))
}
}
is VerificationCodeRequestResult.ChallengeRequired -> { private fun handleSessionStateError(result: VerificationCodeRequestResult) {
handleChallenges(result.challenges) if (!result.isSuccess()) {
} Log.i(TAG, "Handling error response.", result.getCause())
}
when (result) {
is VerificationCodeRequestResult.Success -> throw IllegalStateException("Session error handler called on successful response!")
is VerificationCodeRequestResult.ExternalServiceFailure -> presentRemoteErrorDialog(getString(R.string.RegistrationActivity_unable_to_connect_to_service), skipToNextScreen) is VerificationCodeRequestResult.AttemptsExhausted -> presentRemoteErrorDialog(getString(R.string.RegistrationActivity_rate_limited_to_service))
is VerificationCodeRequestResult.ImpossibleNumber -> {
MaterialAlertDialogBuilder(requireContext()).apply {
setMessage(getString(R.string.RegistrationActivity_the_number_you_specified_s_is_invalid, fragmentViewModel.phoneNumber?.toE164()))
setPositiveButton(android.R.string.ok, null)
show()
}
}
is VerificationCodeRequestResult.InvalidTransportModeFailure -> { is VerificationCodeRequestResult.ChallengeRequired -> {
MaterialAlertDialogBuilder(requireContext()).apply { handleChallenges(result.challenges)
setMessage(R.string.RegistrationActivity_we_couldnt_send_you_a_verification_code)
setPositiveButton(R.string.RegistrationActivity_voice_call) { _, _ ->
sharedViewModel.requestVerificationCall(requireContext(), ::handleErrorResponse)
}
setNegativeButton(R.string.RegistrationActivity_cancel, null)
show()
}
}
is RegistrationSessionCreationResult.MalformedRequest,
is VerificationCodeRequestResult.MalformedRequest -> presentRemoteErrorDialog(getString(R.string.RegistrationActivity_unable_to_connect_to_service), skipToNextScreen)
is VerificationCodeRequestResult.MustRetry -> presentRemoteErrorDialog(getString(R.string.RegistrationActivity_unable_to_connect_to_service), skipToNextScreen)
is VerificationCodeRequestResult.NonNormalizedNumber -> handleNonNormalizedNumberError(result.originalNumber, result.normalizedNumber, fragmentViewModel.mode)
is RegistrationSessionCreationResult.RateLimited -> {
Log.i(TAG, "Session creation rate limited! Next attempt: ${result.timeRemaining.milliseconds}")
presentRemoteErrorDialog(getString(R.string.RegistrationActivity_rate_limited_to_try_again, result.timeRemaining.milliseconds.toString()))
}
is VerificationCodeRequestResult.RateLimited -> {
Log.i(TAG, "Code request rate limited! Next attempt: ${result.timeRemaining.milliseconds}")
presentRemoteErrorDialog(getString(R.string.RegistrationActivity_rate_limited_to_try_again, result.timeRemaining.milliseconds.toString()))
}
is VerificationCodeRequestResult.TokenNotAccepted -> presentRemoteErrorDialog(getString(R.string.RegistrationActivity_we_need_to_verify_that_youre_human)) { _, _ -> moveToCaptcha() }
else -> presentRemoteErrorDialog(getString(R.string.RegistrationActivity_unable_to_connect_to_service))
} }
is VerificationCodeRequestResult.ExternalServiceFailure -> presentRemoteErrorDialog(getString(R.string.RegistrationActivity_unable_to_connect_to_service), skipToNextScreen)
is VerificationCodeRequestResult.ImpossibleNumber -> {
MaterialAlertDialogBuilder(requireContext()).apply {
setMessage(getString(R.string.RegistrationActivity_the_number_you_specified_s_is_invalid, fragmentViewModel.phoneNumber?.toE164()))
setPositiveButton(android.R.string.ok, null)
show()
}
}
is VerificationCodeRequestResult.InvalidTransportModeFailure -> {
MaterialAlertDialogBuilder(requireContext()).apply {
setMessage(R.string.RegistrationActivity_we_couldnt_send_you_a_verification_code)
setPositiveButton(R.string.RegistrationActivity_voice_call) { _, _ ->
sharedViewModel.requestVerificationCall(requireContext())
}
setNegativeButton(R.string.RegistrationActivity_cancel, null)
show()
}
}
is VerificationCodeRequestResult.MalformedRequest -> presentRemoteErrorDialog(getString(R.string.RegistrationActivity_unable_to_connect_to_service), skipToNextScreen)
is VerificationCodeRequestResult.MustRetry -> presentRemoteErrorDialog(getString(R.string.RegistrationActivity_unable_to_connect_to_service), skipToNextScreen)
is VerificationCodeRequestResult.NonNormalizedNumber -> handleNonNormalizedNumberError(result.originalNumber, result.normalizedNumber, fragmentViewModel.mode)
is VerificationCodeRequestResult.RateLimited -> {
Log.i(TAG, "Code request rate limited! Next attempt: ${result.timeRemaining.milliseconds}")
presentRemoteErrorDialog(getString(R.string.RegistrationActivity_rate_limited_to_try_again, result.timeRemaining.milliseconds.toString()))
}
is VerificationCodeRequestResult.TokenNotAccepted -> presentRemoteErrorDialog(getString(R.string.RegistrationActivity_we_need_to_verify_that_youre_human)) { _, _ -> moveToCaptcha() }
is VerificationCodeRequestResult.RegistrationLocked -> presentRemoteErrorDialog(getString(R.string.RegistrationActivity_unable_to_connect_to_service))
is VerificationCodeRequestResult.AlreadyVerified -> presentRemoteErrorDialog(getString(R.string.RegistrationActivity_unable_to_connect_to_service))
is VerificationCodeRequestResult.NoSuchSession -> presentRemoteErrorDialog(getString(R.string.RegistrationActivity_unable_to_connect_to_service))
is VerificationCodeRequestResult.UnknownError -> presentRemoteErrorDialog(getString(R.string.RegistrationActivity_unable_to_connect_to_service))
} }
} }
@@ -390,8 +415,8 @@ class EnterPhoneNumberFragment : LoggingFragment(R.layout.fragment_registration_
phoneNumberInputLayout.setText(phoneNumber.nationalNumber.toString()) phoneNumberInputLayout.setText(phoneNumber.nationalNumber.toString())
when (mode) { when (mode) {
RegistrationRepository.Mode.SMS_WITH_LISTENER, RegistrationRepository.Mode.SMS_WITH_LISTENER,
RegistrationRepository.Mode.SMS_WITHOUT_LISTENER -> sharedViewModel.requestSmsCode(requireContext(), ::handleErrorResponse) RegistrationRepository.Mode.SMS_WITHOUT_LISTENER -> sharedViewModel.requestSmsCode(requireContext())
RegistrationRepository.Mode.PHONE_CALL -> sharedViewModel.requestVerificationCall(requireContext(), ::handleErrorResponse) RegistrationRepository.Mode.PHONE_CALL -> sharedViewModel.requestVerificationCall(requireContext())
} }
dialogInterface.dismiss() dialogInterface.dismiss()
} }
@@ -510,7 +535,7 @@ class EnterPhoneNumberFragment : LoggingFragment(R.layout.fragment_registration_
if (missingFcmConsentRequired) { if (missingFcmConsentRequired) {
handlePromptForNoPlayServices() handlePromptForNoPlayServices()
} else { } else {
sharedViewModel.onUserConfirmedPhoneNumber(requireContext(), ::handleErrorResponse) sharedViewModel.onUserConfirmedPhoneNumber(requireContext())
} }
} }
setNegativeButton(R.string.RegistrationActivity_edit_number) { _, _ -> handleConfirmNumberDialogCanceled() } setNegativeButton(R.string.RegistrationActivity_edit_number) { _, _ -> handleConfirmNumberDialogCanceled() }
@@ -525,7 +550,7 @@ class EnterPhoneNumberFragment : LoggingFragment(R.layout.fragment_registration_
setMessage(R.string.RegistrationActivity_this_device_is_missing_google_play_services) setMessage(R.string.RegistrationActivity_this_device_is_missing_google_play_services)
setPositiveButton(R.string.RegistrationActivity_i_understand) { _, _ -> setPositiveButton(R.string.RegistrationActivity_i_understand) { _, _ ->
Log.d(TAG, "User confirmed number.") Log.d(TAG, "User confirmed number.")
sharedViewModel.onUserConfirmedPhoneNumber(requireContext(), ::handleErrorResponse) sharedViewModel.onUserConfirmedPhoneNumber(requireContext())
} }
setNegativeButton(android.R.string.cancel, null) setNegativeButton(android.R.string.cancel, null)
setOnCancelListener { fragmentViewModel.clearError() } setOnCancelListener { fragmentViewModel.clearError() }
@@ -24,7 +24,6 @@ import org.thoughtcrime.securesms.keyvalue.SignalStore
import org.thoughtcrime.securesms.lock.v2.PinKeyboardType import org.thoughtcrime.securesms.lock.v2.PinKeyboardType
import org.thoughtcrime.securesms.lock.v2.SvrConstants import org.thoughtcrime.securesms.lock.v2.SvrConstants
import org.thoughtcrime.securesms.registration.data.network.RegisterAccountResult import org.thoughtcrime.securesms.registration.data.network.RegisterAccountResult
import org.thoughtcrime.securesms.registration.data.network.RegistrationResult
import org.thoughtcrime.securesms.registration.data.network.VerificationCodeRequestResult import org.thoughtcrime.securesms.registration.data.network.VerificationCodeRequestResult
import org.thoughtcrime.securesms.registration.fragments.RegistrationViewDelegate.setDebugLogSubmitMultiTapView import org.thoughtcrime.securesms.registration.fragments.RegistrationViewDelegate.setDebugLogSubmitMultiTapView
import org.thoughtcrime.securesms.registration.ui.RegistrationViewModel import org.thoughtcrime.securesms.registration.ui.RegistrationViewModel
@@ -101,12 +100,22 @@ class RegistrationLockFragment : LoggingFragment(R.layout.fragment_registration_
binding.kbsLockPinInputLabel.text = requireContext().resources.getQuantityString(R.plurals.RegistrationLockFragment__d_attempts_remaining, triesRemaining, triesRemaining) binding.kbsLockPinInputLabel.text = requireContext().resources.getQuantityString(R.plurals.RegistrationLockFragment__d_attempts_remaining, triesRemaining, triesRemaining)
} }
viewModel.inProgress.observe(viewLifecycleOwner) { viewModel.uiState.observe(viewLifecycleOwner) {
if (it) { if (it.inProgress) {
binding.kbsLockPinConfirm.setSpinning() binding.kbsLockPinConfirm.setSpinning()
} else { } else {
binding.kbsLockPinConfirm.cancelSpinning() binding.kbsLockPinConfirm.cancelSpinning()
} }
it.sessionStateError?.let { error ->
handleSessionErrorResponse(error)
viewModel.sessionStateErrorShown()
}
it.registerAccountError?.let { error ->
handleRegistrationErrorResponse(error)
viewModel.registerAccountErrorShown()
}
} }
} }
@@ -132,12 +141,12 @@ class RegistrationLockFragment : LoggingFragment(R.layout.fragment_registration_
binding.kbsLockPinConfirm.setSpinning() binding.kbsLockPinConfirm.setSpinning()
viewModel.verifyCodeAndRegisterAccountWithRegistrationLock(requireContext(), pin, ::handleSessionErrorResponse, ::handleRegistrationErrorResponse) viewModel.verifyCodeAndRegisterAccountWithRegistrationLock(requireContext(), pin)
} }
private fun handleSessionErrorResponse(requestResult: RegistrationResult) { private fun handleSessionErrorResponse(requestResult: VerificationCodeRequestResult) {
when (requestResult) { when (requestResult) {
is VerificationCodeRequestResult.Success -> Unit is VerificationCodeRequestResult.Success -> throw IllegalStateException("Session error handler called on successful response!")
is VerificationCodeRequestResult.RateLimited -> onRateLimited() is VerificationCodeRequestResult.RateLimited -> onRateLimited()
is VerificationCodeRequestResult.AttemptsExhausted -> { is VerificationCodeRequestResult.AttemptsExhausted -> {
findNavController().safeNavigate(RegistrationLockFragmentDirections.actionAccountLocked()) findNavController().safeNavigate(RegistrationLockFragmentDirections.actionAccountLocked())
@@ -159,7 +168,7 @@ class RegistrationLockFragment : LoggingFragment(R.layout.fragment_registration_
private fun handleRegistrationErrorResponse(result: RegisterAccountResult) { private fun handleRegistrationErrorResponse(result: RegisterAccountResult) {
when (result) { when (result) {
is RegisterAccountResult.Success -> Unit is RegisterAccountResult.Success -> throw IllegalStateException("Register account error handler called on successful response!")
is RegisterAccountResult.RateLimited -> onRateLimited() is RegisterAccountResult.RateLimited -> onRateLimited()
is RegisterAccountResult.AttemptsExhausted -> { is RegisterAccountResult.AttemptsExhausted -> {
findNavController().safeNavigate(RegistrationLockFragmentDirections.actionAccountLocked()) findNavController().safeNavigate(RegistrationLockFragmentDirections.actionAccountLocked())
@@ -82,6 +82,7 @@ class ReRegisterWithPinFragment : LoggingFragment(R.layout.fragment_registration
private fun updateViewState(state: RegistrationState) { private fun updateViewState(state: RegistrationState) {
if (state.networkError != null) { if (state.networkError != null) {
genericErrorDialog() genericErrorDialog()
registrationViewModel.networkErrorShown()
} else if (!state.canSkipSms) { } else if (!state.canSkipSms) {
findNavController().safeNavigate(ReRegisterWithPinFragmentDirections.actionReRegisterWithPinFragmentToEnterPhoneNumberFragment()) findNavController().safeNavigate(ReRegisterWithPinFragmentDirections.actionReRegisterWithPinFragmentToEnterPhoneNumberFragment())
} else if (state.isRegistrationLockEnabled && state.svrTriesRemaining == 0) { } else if (state.isRegistrationLockEnabled && state.svrTriesRemaining == 0) {
@@ -91,6 +92,11 @@ class ReRegisterWithPinFragment : LoggingFragment(R.layout.fragment_registration
presentProgress(state.inProgress) presentProgress(state.inProgress)
presentTriesRemaining(state.svrTriesRemaining) presentTriesRemaining(state.svrTriesRemaining)
} }
state.registerAccountError?.let { error ->
registrationErrorHandler(error)
registrationViewModel.registerAccountErrorShown()
}
} }
private fun presentProgress(inProgress: Boolean) { private fun presentProgress(inProgress: Boolean) {
@@ -126,8 +132,7 @@ class ReRegisterWithPinFragment : LoggingFragment(R.layout.fragment_registration
pin = pin, pin = pin,
wrongPinHandler = { wrongPinHandler = {
reRegisterViewModel.markIncorrectGuess() reRegisterViewModel.markIncorrectGuess()
}, }
registrationErrorHandler = ::registrationErrorHandler
) )
} }
@@ -251,7 +256,7 @@ class ReRegisterWithPinFragment : LoggingFragment(R.layout.fragment_registration
private fun registrationErrorHandler(result: RegisterAccountResult) { private fun registrationErrorHandler(result: RegisterAccountResult) {
when (result) { when (result) {
is RegisterAccountResult.Success -> Log.d(TAG, "Register account was successful.") is RegisterAccountResult.Success -> throw IllegalStateException("Register account error handler called on successful response!")
is RegisterAccountResult.AuthorizationFailed, is RegisterAccountResult.AuthorizationFailed,
is RegisterAccountResult.MalformedRequest, is RegisterAccountResult.MalformedRequest,
is RegisterAccountResult.UnknownError, is RegisterAccountResult.UnknownError,