Bring KBS fragment source into RegistrationLockFragment and handle account locked.

master
Alan Evans 2020-02-01 10:14:02 -05:00 committed by Greyson Parrelli
parent fb82420376
commit 1ea6838db6
10 changed files with 306 additions and 597 deletions

View File

@ -31,7 +31,6 @@ import org.thoughtcrime.securesms.registration.service.CodeVerificationRequest;
import org.thoughtcrime.securesms.registration.service.RegistrationCodeRequest;
import org.thoughtcrime.securesms.registration.service.RegistrationService;
import org.thoughtcrime.securesms.registration.viewmodel.RegistrationViewModel;
import org.thoughtcrime.securesms.util.FeatureFlags;
import org.thoughtcrime.securesms.util.concurrent.AssertedSuccessListener;
import org.whispersystems.signalservice.internal.contacts.entities.TokenResponse;
@ -122,30 +121,38 @@ public final class EnterCodeFragment extends BaseRegistrationFragment {
}
@Override
public void onIncorrectRegistrationLockPin(long timeRemaining, String storageCredentials) {
model.setStorageCredentials(storageCredentials);
public void onV1RegistrationLockPinRequiredOrIncorrect(long timeRemaining) {
model.setTimeRemaining(timeRemaining);
keyboard.displayLocked().addListener(new AssertedSuccessListener<Boolean>() {
@Override
public void onSuccess(Boolean r) {
if (FeatureFlags.pinsForAll()) {
Navigation.findNavController(requireView())
.navigate(EnterCodeFragmentDirections.actionRequireKbsLockPin(timeRemaining));
} else {
Navigation.findNavController(requireView())
.navigate(EnterCodeFragmentDirections.actionRequireRegistrationLockPin(timeRemaining));
}
Navigation.findNavController(requireView())
.navigate(EnterCodeFragmentDirections.actionRequireKbsLockPin(timeRemaining));
}
});
}
@Override
public void onIncorrectKbsRegistrationLockPin(@NonNull TokenResponse triesRemaining) {
// Unexpected, because at this point, no pin has been provided by the user.
throw new AssertionError();
public void onKbsRegistrationLockPinRequired(long timeRemaining, @NonNull TokenResponse tokenResponse, @NonNull String kbsStorageCredentials) {
model.setTimeRemaining(timeRemaining);
model.setStorageCredentials(kbsStorageCredentials);
model.setKeyBackupCurrentToken(tokenResponse);
keyboard.displayLocked().addListener(new AssertedSuccessListener<Boolean>() {
@Override
public void onSuccess(Boolean r) {
Navigation.findNavController(requireView())
.navigate(EnterCodeFragmentDirections.actionRequireKbsLockPin(timeRemaining));
}
});
}
@Override
public void onTooManyAttempts() {
public void onIncorrectKbsRegistrationLockPin(@NonNull TokenResponse tokenResponse) {
throw new AssertionError("Unexpected, user has made no pin guesses");
}
@Override
public void onRateLimited() {
keyboard.displayFailure().addListener(new AssertedSuccessListener<Boolean>() {
@Override
public void onSuccess(Boolean r) {
@ -163,6 +170,14 @@ public final class EnterCodeFragment extends BaseRegistrationFragment {
});
}
@Override
public void onKbsAccountLocked(long timeRemaining) {
model.setTimeRemaining(timeRemaining);
RegistrationLockFragmentDirections.ActionAccountLocked action = RegistrationLockFragmentDirections.actionAccountLocked(timeRemaining);
Navigation.findNavController(requireView()).navigate(action);
}
@Override
public void onError() {
Toast.makeText(requireContext(), R.string.RegistrationActivity_error_connecting_to_service, Toast.LENGTH_LONG).show();

View File

@ -1,221 +0,0 @@
package org.thoughtcrime.securesms.registration.fragments;
import android.os.Bundle;
import android.text.InputType;
import android.text.TextUtils;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.view.inputmethod.EditorInfo;
import android.widget.EditText;
import android.widget.TextView;
import android.widget.Toast;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.StringRes;
import androidx.appcompat.app.AlertDialog;
import androidx.navigation.Navigation;
import com.dd.CircularProgressButton;
import org.thoughtcrime.securesms.R;
import org.thoughtcrime.securesms.keyvalue.SignalStore;
import org.thoughtcrime.securesms.lock.v2.KbsKeyboardType;
import org.thoughtcrime.securesms.registration.service.CodeVerificationRequest;
import org.thoughtcrime.securesms.registration.service.RegistrationService;
import org.thoughtcrime.securesms.registration.viewmodel.RegistrationViewModel;
import org.whispersystems.signalservice.internal.contacts.entities.TokenResponse;
import java.util.concurrent.TimeUnit;
public final class KbsLockFragment extends BaseRegistrationFragment {
private EditText pinEntry;
private CircularProgressButton pinButton;
private TextView errorLabel;
private TextView keyboardToggle;
private long timeRemaining;
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
return inflater.inflate(R.layout.kbs_lock_fragment, container, false);
}
@Override
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
setDebugLogSubmitMultiTapView(view.findViewById(R.id.kbs_lock_pin_title));
pinEntry = view.findViewById(R.id.kbs_lock_pin_input);
pinButton = view.findViewById(R.id.kbs_lock_pin_confirm);
errorLabel = view.findViewById(R.id.kbs_lock_pin_input_label);
keyboardToggle = view.findViewById(R.id.kbs_lock_keyboard_toggle);
View pinForgotButton = view.findViewById(R.id.kbs_lock_forgot_pin);
timeRemaining = KbsLockFragmentArgs.fromBundle(requireArguments()).getTimeRemaining();
pinForgotButton.setOnClickListener(v -> handleForgottenPin(timeRemaining));
pinEntry.setImeOptions(EditorInfo.IME_ACTION_DONE);
pinEntry.setOnEditorActionListener((v, actionId, event) -> {
if (actionId == EditorInfo.IME_ACTION_DONE) {
hideKeyboard(requireContext(), v);
handlePinEntry();
return true;
}
return false;
});
pinButton.setOnClickListener((v) -> {
hideKeyboard(requireContext(), pinEntry);
handlePinEntry();
});
keyboardToggle.setOnClickListener((v) -> {
KbsKeyboardType keyboardType = getPinEntryKeyboardType();
updateKeyboard(keyboardType);
keyboardToggle.setText(resolveKeyboardToggleText(keyboardType));
});
RegistrationViewModel model = getModel();
model.getTokenResponseCredentialsPair().observe(getViewLifecycleOwner(), pair -> {
if (pair.first().getTries() == 0) {
lockAccount();
}
});
model.onRegistrationLockFragmentCreate();
}
private KbsKeyboardType getPinEntryKeyboardType() {
boolean isNumeric = (pinEntry.getImeOptions() & InputType.TYPE_MASK_CLASS) == InputType.TYPE_CLASS_NUMBER;
return isNumeric ? KbsKeyboardType.NUMERIC : KbsKeyboardType.ALPHA_NUMERIC;
}
private void handlePinEntry() {
final String pin = pinEntry.getText().toString();
if (TextUtils.isEmpty(pin) || TextUtils.isEmpty(pin.replace(" ", ""))) {
Toast.makeText(requireContext(), R.string.RegistrationActivity_you_must_enter_your_registration_lock_PIN, Toast.LENGTH_LONG).show();
return;
}
RegistrationViewModel model = getModel();
RegistrationService registrationService = RegistrationService.getInstance(model.getNumber().getE164Number(), model.getRegistrationSecret());
String storageCredentials = model.getBasicStorageCredentials();
TokenResponse tokenResponse = model.getKeyBackupCurrentToken();
setSpinning(pinButton);
registrationService.verifyAccount(requireActivity(),
model.getFcmToken(),
model.getTextCodeEntered(),
pin, storageCredentials, tokenResponse,
new CodeVerificationRequest.VerifyCallback() {
@Override
public void onSuccessfulRegistration() {
cancelSpinning(pinButton);
SignalStore.kbsValues().setKeyboardType(getPinEntryKeyboardType());
Navigation.findNavController(requireView()).navigate(KbsLockFragmentDirections.actionSuccessfulRegistration());
}
@Override
public void onIncorrectRegistrationLockPin(long timeRemaining, String storageCredentials) {
model.setStorageCredentials(storageCredentials);
cancelSpinning(pinButton);
pinEntry.setText("");
errorLabel.setText(R.string.KbsLockFragment__incorrect_pin);
}
@Override
public void onIncorrectKbsRegistrationLockPin(@NonNull TokenResponse tokenResponse) {
cancelSpinning(pinButton);
model.setKeyBackupCurrentToken(tokenResponse);
int triesRemaining = tokenResponse.getTries();
if (triesRemaining == 0) {
lockAccount();
return;
}
if (triesRemaining == 3) {
long daysRemaining = getLockoutDays(timeRemaining);
new AlertDialog.Builder(requireContext())
.setTitle(R.string.KbsLockFragment__incorrect_pin)
.setMessage(getString(R.string.KbsLockFragment__you_have_d_attempts_remaining, triesRemaining, daysRemaining, daysRemaining))
.setPositiveButton(android.R.string.ok, null)
.show();
}
if (triesRemaining > 5) {
errorLabel.setText(R.string.KbsLockFragment__incorrect_pin_try_again);
} else {
errorLabel.setText(getString(R.string.KbsLockFragment__incorrect_pin_d_attempts_remaining, triesRemaining));
}
}
@Override
public void onTooManyAttempts() {
cancelSpinning(pinButton);
new AlertDialog.Builder(requireContext())
.setTitle(R.string.RegistrationActivity_too_many_attempts)
.setMessage(R.string.RegistrationActivity_you_have_made_too_many_incorrect_registration_lock_pin_attempts_please_try_again_in_a_day)
.setPositiveButton(android.R.string.ok, null)
.show();
}
@Override
public void onError() {
cancelSpinning(pinButton);
Toast.makeText(requireContext(), R.string.RegistrationActivity_error_connecting_to_service, Toast.LENGTH_LONG).show();
}
});
}
private void handleForgottenPin(long timeRemainingMs) {
new AlertDialog.Builder(requireContext())
.setTitle(R.string.KbsLockFragment__forgot_your_pin)
.setMessage(getString(R.string.KbsLockFragment__for_your_privacy_and_security_there_is_no_way_to_recover, getLockoutDays(timeRemainingMs)))
.setPositiveButton(android.R.string.ok, null)
.show();
}
private long getLockoutDays(long timeRemainingMs) {
return TimeUnit.MILLISECONDS.toDays(timeRemainingMs) + 1;
}
private void lockAccount() {
KbsLockFragmentDirections.ActionAccountLocked action = KbsLockFragmentDirections.actionAccountLocked(timeRemaining);
Navigation.findNavController(requireView()).navigate(action);
}
private void updateKeyboard(@NonNull KbsKeyboardType keyboard) {
boolean isAlphaNumeric = keyboard == KbsKeyboardType.ALPHA_NUMERIC;
pinEntry.setInputType(isAlphaNumeric ? InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_PASSWORD
: InputType.TYPE_CLASS_NUMBER | InputType.TYPE_NUMBER_VARIATION_PASSWORD);
}
private @StringRes int resolveKeyboardToggleText(@NonNull KbsKeyboardType keyboard) {
if (keyboard == KbsKeyboardType.ALPHA_NUMERIC) {
return R.string.KbsLockFragment__enter_alphanumeric_pin;
} else {
return R.string.KbsLockFragment__enter_numeric_pin;
}
}
}

View File

@ -14,7 +14,6 @@ import androidx.navigation.ActivityNavigator;
import org.thoughtcrime.securesms.MainActivity;
import org.thoughtcrime.securesms.R;
import org.thoughtcrime.securesms.keyvalue.SignalStore;
import org.thoughtcrime.securesms.lock.v2.CreateKbsPinActivity;
import org.thoughtcrime.securesms.lock.v2.PinUtil;
import org.thoughtcrime.securesms.profiles.edit.EditProfileActivity;

View File

@ -1,32 +1,33 @@
package org.thoughtcrime.securesms.registration.fragments;
import android.os.Bundle;
import android.text.Editable;
import android.text.InputType;
import android.text.TextUtils;
import android.text.TextWatcher;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.view.inputmethod.EditorInfo;
import android.widget.EditText;
import android.widget.TextView;
import android.widget.Toast;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.StringRes;
import androidx.appcompat.app.AlertDialog;
import androidx.navigation.Navigation;
import com.dd.CircularProgressButton;
import org.thoughtcrime.securesms.R;
import org.thoughtcrime.securesms.keyvalue.SignalStore;
import org.thoughtcrime.securesms.lock.v2.KbsKeyboardType;
import org.thoughtcrime.securesms.logging.Log;
import org.thoughtcrime.securesms.registration.service.CodeVerificationRequest;
import org.thoughtcrime.securesms.registration.service.RegistrationService;
import org.thoughtcrime.securesms.registration.viewmodel.RegistrationViewModel;
import org.thoughtcrime.securesms.util.concurrent.SimpleTask;
import org.whispersystems.signalservice.internal.contacts.entities.TokenResponse;
import java.io.IOException;
import java.util.concurrent.TimeUnit;
public final class RegistrationLockFragment extends BaseRegistrationFragment {
@ -35,11 +36,12 @@ public final class RegistrationLockFragment extends BaseRegistrationFragment {
private EditText pinEntry;
private CircularProgressButton pinButton;
private TextView errorLabel;
private TextView keyboardToggle;
private long timeRemaining;
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
return inflater.inflate(R.layout.fragment_registration_lock, container, false);
}
@ -47,39 +49,19 @@ public final class RegistrationLockFragment extends BaseRegistrationFragment {
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
setDebugLogSubmitMultiTapView(view.findViewById(R.id.verify_header));
setDebugLogSubmitMultiTapView(view.findViewById(R.id.kbs_lock_pin_title));
pinEntry = view.findViewById(R.id.pin);
pinButton = view.findViewById(R.id.pinButton);
pinEntry = view.findViewById(R.id.kbs_lock_pin_input);
pinButton = view.findViewById(R.id.kbs_lock_pin_confirm);
errorLabel = view.findViewById(R.id.kbs_lock_pin_input_label);
keyboardToggle = view.findViewById(R.id.kbs_lock_keyboard_toggle);
View clarificationLabel = view.findViewById(R.id.clarification_label);
View subHeader = view.findViewById(R.id.verify_subheader);
View pinForgotButton = view.findViewById(R.id.forgot_button);
String code = getModel().getTextCodeEntered();
View pinForgotButton = view.findViewById(R.id.kbs_lock_forgot_pin);
timeRemaining = RegistrationLockFragmentArgs.fromBundle(requireArguments()).getTimeRemaining();
pinForgotButton.setOnClickListener(v -> handleForgottenPin(timeRemaining));
pinEntry.addTextChangedListener(new TextWatcher() {
@Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
}
@Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
}
@Override
public void afterTextChanged(Editable s) {
boolean matchesTextCode = s != null && s.toString().equals(code);
clarificationLabel.setVisibility(matchesTextCode ? View.VISIBLE : View.INVISIBLE);
subHeader.setVisibility(matchesTextCode ? View.INVISIBLE : View.VISIBLE);
}
});
pinEntry.setImeOptions(EditorInfo.IME_ACTION_DONE);
pinEntry.setOnEditorActionListener((v, actionId, event) -> {
if (actionId == EditorInfo.IME_ACTION_DONE) {
@ -95,35 +77,21 @@ public final class RegistrationLockFragment extends BaseRegistrationFragment {
handlePinEntry();
});
RegistrationViewModel model = getModel();
model.getTokenResponseCredentialsPair()
.observe(this, pair -> {
TokenResponse token = pair.first();
String credentials = pair.second();
updateContinueText(token, credentials);
});
keyboardToggle.setOnClickListener((v) -> {
KbsKeyboardType keyboardType = getPinEntryKeyboardType();
model.onRegistrationLockFragmentCreate();
updateKeyboard(keyboardType);
keyboardToggle.setText(resolveKeyboardToggleText(keyboardType));
});
getModel().getTimeRemaining()
.observe(getViewLifecycleOwner(), t -> timeRemaining = t);
}
private void updateContinueText(@Nullable TokenResponse tokenResponse, @Nullable String storageCredentials) {
if (tokenResponse == null) {
if (storageCredentials == null) {
pinButton.setIdleText(getString(R.string.RegistrationActivity_continue));
} else {
// TODO: This is the case where we can determine they are locked out
// no token, but do have storage credentials. Might want to change text.
pinButton.setIdleText(getString(R.string.RegistrationActivity_continue));
}
} else {
int triesRemaining = tokenResponse.getTries();
if (triesRemaining == 1) {
pinButton.setIdleText(getString(R.string.RegistrationActivity_continue_last_attempt));
} else {
pinButton.setIdleText(getString(R.string.RegistrationActivity_continue_d_attempts_left, triesRemaining));
}
}
pinButton.setText(pinButton.getIdleText());
private KbsKeyboardType getPinEntryKeyboardType() {
boolean isNumeric = (pinEntry.getImeOptions() & InputType.TYPE_MASK_CLASS) == InputType.TYPE_CLASS_NUMBER;
return isNumeric ? KbsKeyboardType.NUMERIC : KbsKeyboardType.ALPHA_NUMERIC;
}
private void handlePinEntry() {
@ -134,58 +102,79 @@ public final class RegistrationLockFragment extends BaseRegistrationFragment {
return;
}
RegistrationViewModel model = getModel();
RegistrationService registrationService = RegistrationService.getInstance(model.getNumber().getE164Number(), model.getRegistrationSecret());
String storageCredentials = model.getBasicStorageCredentials();
TokenResponse tokenResponse = model.getKeyBackupCurrentToken();
RegistrationViewModel model = getModel();
RegistrationService registrationService = RegistrationService.getInstance(model.getNumber().getE164Number(), model.getRegistrationSecret());
TokenResponse tokenResponse = model.getKeyBackupCurrentToken();
String basicStorageCredentials = model.getBasicStorageCredentials();
setSpinning(pinButton);
registrationService.verifyAccount(requireActivity(),
model.getFcmToken(),
model.getTextCodeEntered(),
pin, storageCredentials, tokenResponse,
pin,
basicStorageCredentials,
tokenResponse,
new CodeVerificationRequest.VerifyCallback() {
@Override
public void onSuccessfulRegistration() {
cancelSpinning(pinButton);
SignalStore.kbsValues().setKeyboardType(getPinEntryKeyboardType());
Navigation.findNavController(requireView()).navigate(RegistrationLockFragmentDirections.actionSuccessfulRegistration());
}
@Override
public void onIncorrectRegistrationLockPin(long timeRemaining, String storageCredentials) {
model.setStorageCredentials(storageCredentials);
cancelSpinning(pinButton);
public void onV1RegistrationLockPinRequiredOrIncorrect(long timeRemaining) {
getModel().setTimeRemaining(timeRemaining);
pinEntry.setText("");
Toast.makeText(requireContext(), R.string.RegistrationActivity_incorrect_registration_lock_pin, Toast.LENGTH_LONG).show();
cancelSpinning(pinButton);
pinEntry.getText().clear();
errorLabel.setText(R.string.KbsLockFragment__incorrect_pin);
}
@Override
public void onKbsRegistrationLockPinRequired(long timeRemaining, @NonNull TokenResponse kbsTokenResponse, @NonNull String kbsStorageCredentials) {
throw new AssertionError("Not expected after a pin guess");
}
@Override
public void onIncorrectKbsRegistrationLockPin(@NonNull TokenResponse tokenResponse) {
cancelSpinning(pinButton);
pinEntry.getText().clear();
model.setKeyBackupCurrentToken(tokenResponse);
int triesRemaining = tokenResponse.getTries();
if (triesRemaining == 0) {
handleForgottenPin(timeRemaining);
Log.w(TAG, "Account locked. User out of attempts on KBS.");
lockAccount(timeRemaining);
return;
}
new AlertDialog.Builder(requireContext())
.setTitle(R.string.RegistrationActivity_pin_incorrect)
.setMessage(getString(R.string.RegistrationActivity_you_have_d_tries_remaining, triesRemaining))
.setPositiveButton(android.R.string.ok, null)
.show();
if (triesRemaining == 3) {
long daysRemaining = getLockoutDays(timeRemaining);
new AlertDialog.Builder(requireContext())
.setTitle(R.string.KbsLockFragment__incorrect_pin)
.setMessage(getString(R.string.KbsLockFragment__you_have_d_attempts_remaining, triesRemaining, daysRemaining, daysRemaining))
.setPositiveButton(android.R.string.ok, null)
.show();
}
if (triesRemaining > 5) {
errorLabel.setText(R.string.KbsLockFragment__incorrect_pin_try_again);
} else {
errorLabel.setText(getString(R.string.KbsLockFragment__incorrect_pin_d_attempts_remaining, triesRemaining));
}
}
@Override
public void onTooManyAttempts() {
public void onRateLimited() {
cancelSpinning(pinButton);
new AlertDialog.Builder(requireContext())
@ -195,6 +184,13 @@ public final class RegistrationLockFragment extends BaseRegistrationFragment {
.show();
}
@Override
public void onKbsAccountLocked(long timeRemaining) {
getModel().setTimeRemaining(timeRemaining);
lockAccount(timeRemaining);
}
@Override
public void onError() {
cancelSpinning(pinButton);
@ -206,9 +202,34 @@ public final class RegistrationLockFragment extends BaseRegistrationFragment {
private void handleForgottenPin(long timeRemainingMs) {
new AlertDialog.Builder(requireContext())
.setTitle(R.string.RegistrationActivity_oh_no)
.setMessage(getString(R.string.RegistrationActivity_registration_of_this_phone_number_will_be_possible_without_your_registration_lock_pin_after_seven_days_have_passed, (TimeUnit.MILLISECONDS.toDays(timeRemainingMs) + 1)))
.setTitle(R.string.KbsLockFragment__forgot_your_pin)
.setMessage(getString(R.string.KbsLockFragment__for_your_privacy_and_security_there_is_no_way_to_recover, getLockoutDays(timeRemainingMs)))
.setPositiveButton(android.R.string.ok, null)
.show();
}
private static long getLockoutDays(long timeRemainingMs) {
return TimeUnit.MILLISECONDS.toDays(timeRemainingMs) + 1;
}
private void lockAccount(long timeRemaining) {
RegistrationLockFragmentDirections.ActionAccountLocked action = RegistrationLockFragmentDirections.actionAccountLocked(timeRemaining);
Navigation.findNavController(requireView()).navigate(action);
}
private void updateKeyboard(@NonNull KbsKeyboardType keyboard) {
boolean isAlphaNumeric = keyboard == KbsKeyboardType.ALPHA_NUMERIC;
pinEntry.setInputType(isAlphaNumeric ? InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_PASSWORD
: InputType.TYPE_CLASS_NUMBER | InputType.TYPE_NUMBER_VARIATION_PASSWORD);
}
private @StringRes static int resolveKeyboardToggleText(@NonNull KbsKeyboardType keyboard) {
if (keyboard == KbsKeyboardType.ALPHA_NUMERIC) {
return R.string.KbsLockFragment__enter_alphanumeric_pin;
} else {
return R.string.KbsLockFragment__enter_numeric_pin;
}
}
}

View File

@ -53,16 +53,12 @@ public final class CodeVerificationRequest {
private static final String TAG = Log.tag(CodeVerificationRequest.class);
static TokenResponse getToken(@Nullable String basicStorageCredentials) throws IOException {
if (basicStorageCredentials == null) return null;
return ApplicationDependencies.getKeyBackupService().getToken(basicStorageCredentials);
}
private enum Result {
SUCCESS,
PIN_LOCKED,
KBS_WRONG_PIN,
RATE_LIMITED,
KBS_ACCOUNT_LOCKED,
ERROR
}
@ -87,17 +83,39 @@ public final class CodeVerificationRequest {
{
new AsyncTask<Void, Void, Result>() {
private volatile LockedException lockedException;
private volatile KeyBackupSystemWrongPinException keyBackupSystemWrongPinException;
private volatile LockedException lockedException;
private volatile TokenResponse kbsToken;
@Override
protected Result doInBackground(Void... voids) {
final boolean pinSupplied = pin != null;
final boolean tryKbs = kbsTokenResponse != null;
try {
verifyAccount(context, credentials, code, pin, basicStorageCredentials, kbsTokenResponse, fcmToken);
kbsToken = kbsTokenResponse;
verifyAccount(context, credentials, code, pin, kbsTokenResponse, basicStorageCredentials, fcmToken);
return Result.SUCCESS;
} catch (KeyBackupSystemWrongPinException e) {
kbsToken = e.getTokenResponse();
return Result.KBS_WRONG_PIN;
} catch (LockedException e) {
if (pinSupplied && tryKbs) {
throw new AssertionError("KBS Pin appeared to matched but reg lock still failed!");
}
Log.w(TAG, e);
lockedException = e;
if (e.getBasicStorageCredentials() != null) {
try {
kbsToken = getToken(e.getBasicStorageCredentials());
if (kbsToken == null || kbsToken.getTries() == 0) {
return Result.KBS_ACCOUNT_LOCKED;
}
} catch (IOException ex) {
Log.w(TAG, e);
return Result.ERROR;
}
}
return Result.PIN_LOCKED;
} catch (RateLimitException e) {
Log.w(TAG, e);
@ -105,9 +123,6 @@ public final class CodeVerificationRequest {
} catch (IOException e) {
Log.w(TAG, e);
return Result.ERROR;
} catch (KeyBackupSystemWrongPinException e) {
keyBackupSystemWrongPinException = e;
return Result.KBS_WRONG_PIN;
}
}
@ -119,22 +134,41 @@ public final class CodeVerificationRequest {
callback.onSuccessfulRegistration();
break;
case PIN_LOCKED:
callback.onIncorrectRegistrationLockPin(lockedException.getTimeRemaining(), lockedException.getBasicStorageCredentials());
if (kbsToken != null) {
if (lockedException.getBasicStorageCredentials() == null) {
throw new AssertionError("KBS Token set, but no storage credentials supplied.");
}
Log.w(TAG, "Reg Locked: V2 pin needed for registration");
callback.onKbsRegistrationLockPinRequired(lockedException.getTimeRemaining(), kbsToken, lockedException.getBasicStorageCredentials());
} else {
Log.w(TAG, "Reg Locked: V1 pin needed for registration");
callback.onV1RegistrationLockPinRequiredOrIncorrect(lockedException.getTimeRemaining());
}
break;
case RATE_LIMITED:
callback.onTooManyAttempts();
callback.onRateLimited();
break;
case ERROR:
callback.onError();
break;
case KBS_WRONG_PIN:
callback.onIncorrectKbsRegistrationLockPin(keyBackupSystemWrongPinException.getTokenResponse());
Log.w(TAG, "KBS Pin was wrong");
callback.onIncorrectKbsRegistrationLockPin(kbsToken);
break;
case KBS_ACCOUNT_LOCKED:
Log.w(TAG, "KBS Account is locked");
callback.onKbsAccountLocked(lockedException.getTimeRemaining());
break;
}
}
}.execute();
}
private static TokenResponse getToken(@Nullable String basicStorageCredentials) throws IOException {
if (basicStorageCredentials == null) return null;
return ApplicationDependencies.getKeyBackupService().getToken(basicStorageCredentials);
}
private static void handleSuccessfulRegistration(@NonNull Context context) {
JobManager jobManager = ApplicationDependencies.getJobManager();
jobManager.add(new DirectoryRefreshJob(false));
@ -148,11 +182,12 @@ public final class CodeVerificationRequest {
@NonNull Credentials credentials,
@NonNull String code,
@Nullable String pin,
@Nullable String basicStorageCredentials,
@Nullable TokenResponse kbsTokenResponse,
@Nullable String kbsStorageCredentials,
@Nullable String fcmToken)
throws IOException, KeyBackupSystemWrongPinException
{
boolean isV2KbsPin = kbsTokenResponse != null;
int registrationId = KeyHelper.generateRegistrationId(false);
byte[] unidentifiedAccessKey = UnidentifiedAccessUtil.getSelfUnidentifiedAccessKey(context);
boolean universalUnidentifiedAccess = TextSecurePreferences.isUniversalUnidentifiedAccess(context);
@ -161,10 +196,10 @@ public final class CodeVerificationRequest {
SessionUtil.archiveAllSessions(context);
SignalServiceAccountManager accountManager = AccountManagerFactory.createUnauthenticated(context, credentials.getE164number(), credentials.getPassword());
RegistrationLockData kbsData = restoreMasterKey(pin, basicStorageCredentials, kbsTokenResponse);
RegistrationLockData kbsData = isV2KbsPin ? restoreMasterKey(pin, kbsStorageCredentials, kbsTokenResponse) : null;
String registrationLock = kbsData != null ? kbsData.getMasterKey().deriveRegistrationLock() : null;
boolean present = fcmToken != null;
String pinForServer = basicStorageCredentials == null ? pin : null;
String pinForServer = isV2KbsPin ? null : pin;
UUID uuid = accountManager.verifyAccountWithCode(code, null, registrationId, !present,
pinForServer, registrationLock,
@ -251,14 +286,13 @@ public final class CodeVerificationRequest {
private static @Nullable RegistrationLockData restoreMasterKey(@Nullable String pin,
@Nullable String basicStorageCredentials,
@Nullable TokenResponse tokenResponse)
@NonNull TokenResponse tokenResponse)
throws IOException, KeyBackupSystemWrongPinException
{
if (pin == null) return null;
if (basicStorageCredentials == null) {
Log.i(TAG, "No storage credentials supplied, pin is not on KBS");
return null;
throw new AssertionError("Cannot restore KBS key, no storage credentials supplied");
}
KeyBackupService keyBackupService = ApplicationDependencies.getKeyBackupService();
@ -290,13 +324,32 @@ public final class CodeVerificationRequest {
void onSuccessfulRegistration();
/**
* The account is locked with a V1 (non-KBS) pin.
*
* @param timeRemaining Time until pin expires and number can be reused.
*/
void onIncorrectRegistrationLockPin(long timeRemaining, String storageCredentials);
void onV1RegistrationLockPinRequiredOrIncorrect(long timeRemaining);
/**
* The account is locked with a V2 (KBS) pin. Called before any user pin guesses.
*/
void onKbsRegistrationLockPinRequired(long timeRemaining, @NonNull TokenResponse kbsTokenResponse, @NonNull String kbsStorageCredentials);
/**
* The account is locked with a V2 (KBS) pin. Called after a user pin guess.
* <p>
* i.e. an attempt has likely been used.
*/
void onIncorrectKbsRegistrationLockPin(@NonNull TokenResponse kbsTokenResponse);
void onTooManyAttempts();
/**
* V2 (KBS) pin is set, but there is no data on KBS
*
* @param timeRemaining Time until pin expires and number can be reused.
*/
void onKbsAccountLocked(long timeRemaining);
void onRateLimited();
void onError();
}

View File

@ -45,8 +45,4 @@ public final class RegistrationService {
{
CodeVerificationRequest.verifyAccount(activity, credentials, fcmToken, code, pin, basicStorageCredentials, tokenResponse, callback);
}
public @Nullable TokenResponse getToken(@Nullable String basicStorageCredentials) throws IOException {
return CodeVerificationRequest.getToken(basicStorageCredentials);
}
}

View File

@ -6,15 +6,10 @@ import androidx.annotation.Nullable;
import androidx.lifecycle.LiveData;
import androidx.lifecycle.MutableLiveData;
import androidx.lifecycle.SavedStateHandle;
import androidx.lifecycle.Transformations;
import androidx.lifecycle.ViewModel;
import org.thoughtcrime.securesms.logging.Log;
import org.thoughtcrime.securesms.registration.service.RegistrationService;
import org.thoughtcrime.securesms.util.Util;
import org.thoughtcrime.securesms.util.concurrent.SimpleTask;
import org.thoughtcrime.securesms.util.livedata.LiveDataPair;
import org.whispersystems.libsignal.util.Pair;
import org.whispersystems.signalservice.internal.contacts.entities.TokenResponse;
import org.whispersystems.signalservice.internal.util.JsonUtil;
@ -34,8 +29,7 @@ public final class RegistrationViewModel extends ViewModel {
private final MutableLiveData<Integer> successfulCodeRequestAttempts;
private final MutableLiveData<LocalCodeRequestRateLimiter> requestLimiter;
private final MutableLiveData<String> keyBackupcurrentTokenJson;
private final LiveData<TokenResponse> keyBackupcurrentToken;
private final LiveData<Pair<TokenResponse, String>> tokenResponseCredentialsPair;
private final MutableLiveData<Long> timeRemaining;
public RegistrationViewModel(@NonNull SavedStateHandle savedStateHandle) {
secret = loadValue(savedStateHandle, "REGISTRATION_SECRET", Util.getSecret(18));
@ -49,19 +43,7 @@ public final class RegistrationViewModel extends ViewModel {
successfulCodeRequestAttempts = savedStateHandle.getLiveData("SUCCESSFUL_CODE_REQUEST_ATTEMPTS", 0);
requestLimiter = savedStateHandle.getLiveData("REQUEST_RATE_LIMITER", new LocalCodeRequestRateLimiter(60_000));
keyBackupcurrentTokenJson = savedStateHandle.getLiveData("KBS_TOKEN");
keyBackupcurrentToken = Transformations.map(keyBackupcurrentTokenJson, json ->
{
if (json == null) return null;
try {
return JsonUtil.fromJson(json, TokenResponse.class);
} catch (IOException e) {
Log.w(TAG, e);
return null;
}
});
tokenResponseCredentialsPair = new LiveDataPair<>(keyBackupcurrentToken, basicStorageCredentials);
timeRemaining = savedStateHandle.getLiveData("TIME_REMAINING", 0L);
}
private static <T> T loadValue(@NonNull SavedStateHandle savedStateHandle, @NonNull String key, @NonNull T initialValue) {
@ -179,26 +161,26 @@ public final class RegistrationViewModel extends ViewModel {
}
public @Nullable TokenResponse getKeyBackupCurrentToken() {
return keyBackupcurrentToken.getValue();
String json = keyBackupcurrentTokenJson.getValue();
if (json == null) return null;
try {
return JsonUtil.fromJson(json, TokenResponse.class);
} catch (IOException e) {
Log.w(TAG, e);
return null;
}
}
public void setKeyBackupCurrentToken(TokenResponse tokenResponse) {
keyBackupcurrentTokenJson.setValue(tokenResponse == null ? null : JsonUtil.toJson(tokenResponse));
String json = tokenResponse == null ? null : JsonUtil.toJson(tokenResponse);
keyBackupcurrentTokenJson.setValue(json);
}
public LiveData<Pair<TokenResponse, String>> getTokenResponseCredentialsPair() {
return tokenResponseCredentialsPair;
public LiveData<Long> getTimeRemaining() {
return timeRemaining;
}
public void onRegistrationLockFragmentCreate() {
SimpleTask.run(() -> {
RegistrationService registrationService = RegistrationService.getInstance(getNumber().getE164Number(), getRegistrationSecret());
try {
return registrationService.getToken(getBasicStorageCredentials());
} catch (IOException e) {
Log.w(TAG, e);
return null;
}
}, this::setKeyBackupCurrentToken);
public void setTimeRemaining(long timeRemaining) {
this.timeRemaining.setValue(timeRemaining);
}
}

View File

@ -1,113 +1,95 @@
<?xml version="1.0" encoding="utf-8"?>
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fillViewport="true"
tools:context=".registration.fragments.RegistrationLockFragment">
android:layout_height="match_parent">
<androidx.constraintlayout.widget.ConstraintLayout
<TextView
android:id="@+id/kbs_lock_pin_title"
android:layout_width="match_parent"
android:layout_height="0dp">
android:layout_height="wrap_content"
android:gravity="center_horizontal"
android:text="@string/KbsLockFragment__enter_your_pin"
android:textAppearance="@style/TextAppearance.Signal.Title1"
app:layout_constraintBottom_toTopOf="@id/kbs_lock_keyboard_toggle"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_bias="0.20" />
<TextView
android:id="@+id/clarification_label"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="32dp"
android:layout_marginTop="16dp"
android:layout_marginEnd="32dp"
android:gravity="center"
android:text="@string/registration_activity__the_registration_lock_pin_is_not_the_same_as_the_sms_verification_code_you_just_received_please_enter_the_pin_you_previously_configured_in_the_application"
android:textColor="#73B7F0"
android:visibility="invisible"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.5"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/verify_header"
tools:visibility="visible" />
<TextView
android:id="@+id/kbs_lock_pin_description"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="27dp"
android:layout_marginTop="12dp"
android:layout_marginEnd="27dp"
android:gravity="center_horizontal"
android:minHeight="66dp"
android:text="@string/KbsLockFragment__enter_the_pin_you_created"
android:textAppearance="@style/TextAppearance.MaterialComponents.Body1"
android:textColor="@color/core_grey_60"
app:layout_constraintTop_toBottomOf="@id/kbs_lock_pin_title" />
<TextView
android:id="@+id/verify_header"
style="@style/Signal.Text.Headline.Registration"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_marginStart="32dp"
android:layout_marginTop="40dp"
android:layout_marginEnd="32dp"
android:gravity="center"
android:text="@string/RegistrationActivity_registration_lock_pin"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.5"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<EditText
android:id="@+id/kbs_lock_pin_input"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:layout_marginTop="12dp"
android:gravity="center_horizontal"
android:inputType="numberPassword"
android:minWidth="210dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/kbs_lock_pin_description" />
<TextView
android:id="@+id/verify_subheader"
style="@style/Signal.Text.Body.Registration"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="32dp"
android:layout_marginTop="16dp"
android:layout_marginEnd="32dp"
android:gravity="center"
android:text="@string/RegistrationActivity_this_phone_number_has_registration_lock_enabled_please_enter_the_registration_lock_pin"
app:layout_constraintTop_toBottomOf="@+id/verify_header"
tools:layout_editor_absoluteX="0dp" />
<TextView
android:id="@+id/kbs_lock_pin_input_label"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:gravity="center_horizontal"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/kbs_lock_pin_input"
tools:text="@string/KbsLockFragment__incorrect_pin_try_again" />
<TextView
android:id="@+id/forgot_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:layout_marginTop="16dp"
android:padding="16dp"
android:text="@string/registration_activity__forgot_pin"
android:textColor="@color/blue_400"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.5"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/pinButton" />
<Button
android:id="@+id/kbs_lock_forgot_pin"
style="@style/Widget.AppCompat.Button.Borderless.Colored"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="32dp"
android:layout_marginTop="16dp"
android:layout_marginEnd="32dp"
android:text="@string/KbsLockFragment__forgot_pin"
android:textColor="@color/signal_primary"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/kbs_lock_pin_input" />
<com.dd.CircularProgressButton
android:id="@+id/pinButton"
style="@style/Button.Registration"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginTop="16dp"
android:layout_marginEnd="16dp"
app:cpb_textIdle="@string/RegistrationActivity_continue"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.5"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/textInputLayout" />
<Button
android:id="@+id/kbs_lock_keyboard_toggle"
style="@style/Widget.AppCompat.Button.Borderless.Colored"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="32dp"
android:layout_marginEnd="32dp"
android:textColor="@color/signal_primary"
app:layout_constraintBottom_toTopOf="@id/kbs_lock_pin_confirm"
tools:text="Create Alphanumeric Pin" />
<com.google.android.material.textfield.TextInputLayout
android:id="@+id/textInputLayout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="32dp"
android:layout_marginTop="16dp"
android:layout_marginEnd="32dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.5"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/clarification_label">
<com.dd.CircularProgressButton
android:id="@+id/kbs_lock_pin_confirm"
style="@style/Button.Registration"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="32dp"
android:layout_marginEnd="32dp"
android:layout_marginBottom="16dp"
app:cpb_textIdle="@string/RegistrationActivity_continue"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" />
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/pin"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center"
android:hint="@string/registration_activity__registration_lock_pin"
android:imeOptions="actionDone"
android:inputType="numberPassword" />
</com.google.android.material.textfield.TextInputLayout>
</androidx.constraintlayout.widget.ConstraintLayout>
</ScrollView>
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -1,95 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:id="@+id/kbs_lock_pin_title"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center_horizontal"
android:text="@string/KbsLockFragment__enter_your_pin"
android:textAppearance="@style/TextAppearance.Signal.Title1"
app:layout_constraintBottom_toTopOf="@id/kbs_lock_keyboard_toggle"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_bias="0.20" />
<TextView
android:id="@+id/kbs_lock_pin_description"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="27dp"
android:layout_marginTop="12dp"
android:layout_marginEnd="27dp"
android:gravity="center_horizontal"
android:minHeight="66dp"
android:text="@string/KbsLockFragment__enter_the_pin_you_created"
android:textAppearance="@style/TextAppearance.MaterialComponents.Body1"
android:textColor="@color/core_grey_60"
app:layout_constraintTop_toBottomOf="@id/kbs_lock_pin_title" />
<EditText
android:id="@+id/kbs_lock_pin_input"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:layout_marginTop="12dp"
android:gravity="center_horizontal"
android:inputType="numberPassword"
android:minWidth="210dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/kbs_lock_pin_description" />
<TextView
android:id="@+id/kbs_lock_pin_input_label"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:gravity="center_horizontal"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/kbs_lock_pin_input"
tools:text="@string/KbsLockFragment__incorrect_pin_try_again" />
<Button
android:id="@+id/kbs_lock_forgot_pin"
style="@style/Widget.AppCompat.Button.Borderless.Colored"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="32dp"
android:layout_marginTop="16dp"
android:layout_marginEnd="32dp"
android:text="@string/KbsLockFragment__forgot_pin"
android:textColor="@color/signal_primary"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/kbs_lock_pin_input" />
<Button
android:id="@+id/kbs_lock_keyboard_toggle"
style="@style/Widget.AppCompat.Button.Borderless.Colored"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="32dp"
android:layout_marginEnd="32dp"
android:textColor="@color/signal_primary"
app:layout_constraintBottom_toTopOf="@id/kbs_lock_pin_confirm"
tools:text="Create Alphanumeric Pin" />
<com.dd.CircularProgressButton
android:id="@+id/kbs_lock_pin_confirm"
style="@style/Button.Registration"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="32dp"
android:layout_marginEnd="32dp"
android:layout_marginBottom="16dp"
app:cpb_textIdle="@string/RegistrationActivity_continue"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -81,19 +81,9 @@
android:label="fragment_enter_code"
tools:layout="@layout/fragment_registration_enter_code">
<action
android:id="@+id/action_requireRegistrationLockPin"
app:destination="@id/registrationLockFragment"
app:enterAnim="@anim/nav_default_enter_anim"
app:exitAnim="@anim/nav_default_exit_anim"
app:popEnterAnim="@anim/nav_default_pop_enter_anim"
app:popExitAnim="@anim/nav_default_pop_exit_anim"
app:popUpTo="@+id/welcomeFragment"
app:popUpToInclusive="true" />
<action
android:id="@+id/action_requireKbsLockPin"
app:destination="@id/kbsLockFragment"
app:destination="@id/registrationLockFragment"
app:enterAnim="@anim/nav_default_enter_anim"
app:exitAnim="@anim/nav_default_exit_anim"
app:popEnterAnim="@anim/nav_default_pop_enter_anim"
@ -119,18 +109,9 @@
app:destination="@id/registrationCompletePlaceHolderFragment"
app:popUpTo="@+id/welcomeFragment"
app:popUpToInclusive="true" />
</fragment>
<fragment
android:id="@+id/registrationLockFragment"
android:name="org.thoughtcrime.securesms.registration.fragments.RegistrationLockFragment"
android:label="fragment_registration_lock"
tools:layout="@layout/fragment_registration_lock">
<action
android:id="@+id/action_successfulRegistration"
app:destination="@id/registrationCompletePlaceHolderFragment"
android:id="@+id/action_accountLocked"
app:destination="@id/accountLockedFragment"
app:enterAnim="@anim/nav_default_enter_anim"
app:exitAnim="@anim/nav_default_exit_anim"
app:popEnterAnim="@anim/nav_default_pop_enter_anim"
@ -138,17 +119,13 @@
app:popUpTo="@+id/welcomeFragment"
app:popUpToInclusive="true" />
<argument
android:name="timeRemaining"
app:argType="long" />
</fragment>
<fragment
android:id="@+id/kbsLockFragment"
android:name="org.thoughtcrime.securesms.registration.fragments.KbsLockFragment"
android:id="@+id/registrationLockFragment"
android:name="org.thoughtcrime.securesms.registration.fragments.RegistrationLockFragment"
android:label="fragment_kbs_lock"
tools:layout="@layout/kbs_lock_fragment">
tools:layout="@layout/fragment_registration_lock">
<action
android:id="@+id/action_successfulRegistration"