323 lines
12 KiB
Java
323 lines
12 KiB
Java
package org.thoughtcrime.securesms.registration.fragments;
|
|
|
|
import android.os.Bundle;
|
|
import android.view.LayoutInflater;
|
|
import android.view.View;
|
|
import android.view.ViewGroup;
|
|
import android.widget.ScrollView;
|
|
import android.widget.TextView;
|
|
import android.widget.Toast;
|
|
|
|
import androidx.annotation.NonNull;
|
|
import androidx.annotation.Nullable;
|
|
import androidx.appcompat.app.AlertDialog;
|
|
import androidx.navigation.NavController;
|
|
import androidx.navigation.Navigation;
|
|
|
|
import org.greenrobot.eventbus.EventBus;
|
|
import org.greenrobot.eventbus.Subscribe;
|
|
import org.greenrobot.eventbus.ThreadMode;
|
|
import org.thoughtcrime.securesms.R;
|
|
import org.thoughtcrime.securesms.components.registration.CallMeCountDownView;
|
|
import org.thoughtcrime.securesms.components.registration.VerificationCodeView;
|
|
import org.thoughtcrime.securesms.components.registration.VerificationPinKeyboard;
|
|
import org.thoughtcrime.securesms.logging.Log;
|
|
import org.thoughtcrime.securesms.pin.PinRestoreRepository;
|
|
import org.thoughtcrime.securesms.registration.ReceivedSmsEvent;
|
|
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.CommunicationActions;
|
|
import org.thoughtcrime.securesms.util.SupportEmailUtil;
|
|
import org.thoughtcrime.securesms.util.concurrent.AssertedSuccessListener;
|
|
|
|
import java.util.ArrayList;
|
|
import java.util.Collections;
|
|
import java.util.List;
|
|
|
|
public final class EnterCodeFragment extends BaseRegistrationFragment {
|
|
|
|
private static final String TAG = Log.tag(EnterCodeFragment.class);
|
|
|
|
private ScrollView scrollView;
|
|
private TextView header;
|
|
private VerificationCodeView verificationCodeView;
|
|
private VerificationPinKeyboard keyboard;
|
|
private CallMeCountDownView callMeCountDown;
|
|
private View wrongNumber;
|
|
private View noCodeReceivedHelp;
|
|
private boolean autoCompleting;
|
|
|
|
@Override
|
|
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
|
|
return inflater.inflate(R.layout.fragment_registration_enter_code, container, false);
|
|
}
|
|
|
|
@Override
|
|
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
|
|
super.onViewCreated(view, savedInstanceState);
|
|
|
|
setDebugLogSubmitMultiTapView(view.findViewById(R.id.verify_header));
|
|
|
|
scrollView = view.findViewById(R.id.scroll_view);
|
|
header = view.findViewById(R.id.verify_header);
|
|
verificationCodeView = view.findViewById(R.id.code);
|
|
keyboard = view.findViewById(R.id.keyboard);
|
|
callMeCountDown = view.findViewById(R.id.call_me_count_down);
|
|
wrongNumber = view.findViewById(R.id.wrong_number);
|
|
noCodeReceivedHelp = view.findViewById(R.id.no_code);
|
|
|
|
connectKeyboard(verificationCodeView, keyboard);
|
|
|
|
setOnCodeFullyEnteredListener(verificationCodeView);
|
|
|
|
wrongNumber.setOnClickListener(v -> Navigation.findNavController(view).navigate(EnterCodeFragmentDirections.actionWrongNumber()));
|
|
|
|
callMeCountDown.setOnClickListener(v -> handlePhoneCallRequest());
|
|
|
|
callMeCountDown.setListener((v, remaining) -> {
|
|
if (remaining <= 30) {
|
|
scrollView.smoothScrollTo(0, v.getBottom());
|
|
callMeCountDown.setListener(null);
|
|
}
|
|
});
|
|
|
|
noCodeReceivedHelp.setOnClickListener(v -> sendEmailToSupport());
|
|
|
|
RegistrationViewModel model = getModel();
|
|
model.getSuccessfulCodeRequestAttempts().observe(this, (attempts) -> {
|
|
if (attempts >= 3) {
|
|
noCodeReceivedHelp.setVisibility(View.VISIBLE);
|
|
scrollView.postDelayed(() -> scrollView.smoothScrollTo(0, noCodeReceivedHelp.getBottom()), 15000);
|
|
}
|
|
});
|
|
|
|
model.onStartEnterCode();
|
|
}
|
|
|
|
private void setOnCodeFullyEnteredListener(VerificationCodeView verificationCodeView) {
|
|
verificationCodeView.setOnCompleteListener(code -> {
|
|
RegistrationViewModel model = getModel();
|
|
|
|
model.onVerificationCodeEntered(code);
|
|
callMeCountDown.setVisibility(View.INVISIBLE);
|
|
wrongNumber.setVisibility(View.INVISIBLE);
|
|
keyboard.displayProgress();
|
|
|
|
RegistrationService registrationService = RegistrationService.getInstance(model.getNumber().getE164Number(), model.getRegistrationSecret());
|
|
|
|
registrationService.verifyAccount(requireActivity(), model.getFcmToken(), code, null, null,
|
|
new CodeVerificationRequest.VerifyCallback() {
|
|
|
|
@Override
|
|
public void onSuccessfulRegistration() {
|
|
keyboard.displaySuccess().addListener(new AssertedSuccessListener<Boolean>() {
|
|
@Override
|
|
public void onSuccess(Boolean result) {
|
|
handleSuccessfulRegistration();
|
|
}
|
|
});
|
|
}
|
|
|
|
@Override
|
|
public void onV1RegistrationLockPinRequiredOrIncorrect(long timeRemaining) {
|
|
model.setLockedTimeRemaining(timeRemaining);
|
|
keyboard.displayLocked().addListener(new AssertedSuccessListener<Boolean>() {
|
|
@Override
|
|
public void onSuccess(Boolean r) {
|
|
Navigation.findNavController(requireView())
|
|
.navigate(EnterCodeFragmentDirections.actionRequireKbsLockPin(timeRemaining, true));
|
|
}
|
|
});
|
|
}
|
|
|
|
@Override
|
|
public void onKbsRegistrationLockPinRequired(long timeRemaining, @NonNull PinRestoreRepository.TokenData tokenData, @NonNull String kbsStorageCredentials) {
|
|
model.setLockedTimeRemaining(timeRemaining);
|
|
model.setKeyBackupTokenData(tokenData);
|
|
keyboard.displayLocked().addListener(new AssertedSuccessListener<Boolean>() {
|
|
@Override
|
|
public void onSuccess(Boolean r) {
|
|
Navigation.findNavController(requireView())
|
|
.navigate(EnterCodeFragmentDirections.actionRequireKbsLockPin(timeRemaining, false));
|
|
}
|
|
});
|
|
}
|
|
|
|
@Override
|
|
public void onIncorrectKbsRegistrationLockPin(@NonNull PinRestoreRepository.TokenData tokenData) {
|
|
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) {
|
|
new AlertDialog.Builder(requireContext())
|
|
.setTitle(R.string.RegistrationActivity_too_many_attempts)
|
|
.setMessage(R.string.RegistrationActivity_you_have_made_too_many_attempts_please_try_again_later)
|
|
.setPositiveButton(android.R.string.ok, (dialog, which) -> {
|
|
callMeCountDown.setVisibility(View.VISIBLE);
|
|
wrongNumber.setVisibility(View.VISIBLE);
|
|
verificationCodeView.clear();
|
|
keyboard.displayKeyboard();
|
|
})
|
|
.show();
|
|
}
|
|
});
|
|
}
|
|
|
|
@Override
|
|
public void onKbsAccountLocked(@Nullable Long timeRemaining) {
|
|
if (timeRemaining != null) {
|
|
model.setLockedTimeRemaining(timeRemaining);
|
|
}
|
|
Navigation.findNavController(requireView()).navigate(RegistrationLockFragmentDirections.actionAccountLocked());
|
|
}
|
|
|
|
@Override
|
|
public void onError() {
|
|
Toast.makeText(requireContext(), R.string.RegistrationActivity_error_connecting_to_service, Toast.LENGTH_LONG).show();
|
|
keyboard.displayFailure().addListener(new AssertedSuccessListener<Boolean>() {
|
|
@Override
|
|
public void onSuccess(Boolean result) {
|
|
callMeCountDown.setVisibility(View.VISIBLE);
|
|
wrongNumber.setVisibility(View.VISIBLE);
|
|
verificationCodeView.clear();
|
|
keyboard.displayKeyboard();
|
|
}
|
|
});
|
|
}
|
|
});
|
|
});
|
|
}
|
|
|
|
private void handleSuccessfulRegistration() {
|
|
Navigation.findNavController(requireView()).navigate(EnterCodeFragmentDirections.actionSuccessfulRegistration());
|
|
}
|
|
|
|
@Override
|
|
public void onStart() {
|
|
super.onStart();
|
|
EventBus.getDefault().register(this);
|
|
}
|
|
|
|
@Override
|
|
public void onStop() {
|
|
super.onStop();
|
|
EventBus.getDefault().unregister(this);
|
|
}
|
|
|
|
@Subscribe(threadMode = ThreadMode.MAIN)
|
|
public void onVerificationCodeReceived(@NonNull ReceivedSmsEvent event) {
|
|
verificationCodeView.clear();
|
|
|
|
List<Integer> parsedCode = convertVerificationCodeToDigits(event.getCode());
|
|
|
|
autoCompleting = true;
|
|
|
|
final int size = parsedCode.size();
|
|
|
|
for (int i = 0; i < size; i++) {
|
|
final int index = i;
|
|
verificationCodeView.postDelayed(() -> {
|
|
verificationCodeView.append(parsedCode.get(index));
|
|
if (index == size - 1) {
|
|
autoCompleting = false;
|
|
}
|
|
}, i * 200);
|
|
}
|
|
}
|
|
|
|
private static List<Integer> convertVerificationCodeToDigits(@Nullable String code) {
|
|
if (code == null || code.length() != 6) {
|
|
return Collections.emptyList();
|
|
}
|
|
|
|
List<Integer> result = new ArrayList<>(code.length());
|
|
|
|
try {
|
|
for (int i = 0; i < code.length(); i++) {
|
|
result.add(Integer.parseInt(Character.toString(code.charAt(i))));
|
|
}
|
|
} catch (NumberFormatException e) {
|
|
Log.w(TAG, "Failed to convert code into digits.", e);
|
|
return Collections.emptyList();
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
private void handlePhoneCallRequest() {
|
|
RegistrationViewModel model = getModel();
|
|
String captcha = model.getCaptchaToken();
|
|
model.clearCaptchaResponse();
|
|
|
|
model.onCallRequested();
|
|
|
|
NavController navController = Navigation.findNavController(callMeCountDown);
|
|
|
|
RegistrationService registrationService = RegistrationService.getInstance(model.getNumber().getE164Number(), model.getRegistrationSecret());
|
|
|
|
registrationService.requestVerificationCode(requireActivity(), RegistrationCodeRequest.Mode.PHONE_CALL, captcha,
|
|
new RegistrationCodeRequest.SmsVerificationCodeCallback() {
|
|
|
|
@Override
|
|
public void onNeedCaptcha() {
|
|
navController.navigate(EnterCodeFragmentDirections.actionRequestCaptcha());
|
|
}
|
|
|
|
@Override
|
|
public void requestSent(@Nullable String fcmToken) {
|
|
model.setFcmToken(fcmToken);
|
|
model.markASuccessfulAttempt();
|
|
}
|
|
|
|
@Override
|
|
public void onRateLimited() {
|
|
Toast.makeText(requireContext(), R.string.RegistrationActivity_rate_limited_to_service, Toast.LENGTH_LONG).show();
|
|
}
|
|
|
|
@Override
|
|
public void onError() {
|
|
Toast.makeText(requireContext(), R.string.RegistrationActivity_unable_to_connect_to_service, Toast.LENGTH_LONG).show();
|
|
}
|
|
});
|
|
}
|
|
|
|
private void connectKeyboard(VerificationCodeView verificationCodeView, VerificationPinKeyboard keyboard) {
|
|
keyboard.setOnKeyPressListener(key -> {
|
|
if (!autoCompleting) {
|
|
if (key >= 0) {
|
|
verificationCodeView.append(key);
|
|
} else {
|
|
verificationCodeView.delete();
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
@Override
|
|
public void onResume() {
|
|
super.onResume();
|
|
|
|
RegistrationViewModel model = getModel();
|
|
model.getLiveNumber().observe(this, (s) -> header.setText(requireContext().getString(R.string.RegistrationActivity_enter_the_code_we_sent_to_s, s.getFullFormattedNumber())));
|
|
|
|
model.getCanCallAtTime().observe(this, callAtTime -> callMeCountDown.startCountDownTo(callAtTime));
|
|
}
|
|
|
|
private void sendEmailToSupport() {
|
|
String body = SupportEmailUtil.generateSupportEmailBody(requireContext(),
|
|
getString(R.string.RegistrationActivity_code_support_subject),
|
|
null,
|
|
null);
|
|
CommunicationActions.openEmail(requireContext(),
|
|
SupportEmailUtil.getSupportEmailAddress(requireContext()),
|
|
getString(R.string.RegistrationActivity_code_support_subject),
|
|
body);
|
|
}
|
|
}
|