diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/registration/CallMeCountDownView.java b/app/src/main/java/org/thoughtcrime/securesms/components/registration/CallMeCountDownView.java index 51e5b8e724..9cbfaf1c09 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/components/registration/CallMeCountDownView.java +++ b/app/src/main/java/org/thoughtcrime/securesms/components/registration/CallMeCountDownView.java @@ -8,9 +8,11 @@ import androidx.annotation.Nullable; import org.thoughtcrime.securesms.R; +import java.util.concurrent.TimeUnit; + public class CallMeCountDownView extends androidx.appcompat.widget.AppCompatButton { - private int countDown; + private long countDownToTime; @Nullable private Listener listener; @@ -26,9 +28,14 @@ public class CallMeCountDownView extends androidx.appcompat.widget.AppCompatButt super(context, attrs, defStyleAttr); } - public void startCountDown(int countDown) { - this.countDown = countDown; - updateCountDown(); + /** + * Starts a count down to the specified {@param time}. + */ + public void startCountDownTo(long time) { + if (time > 0) { + this.countDownToTime = time; + updateCountDown(); + } } public void setCallEnabled() { @@ -38,23 +45,24 @@ public class CallMeCountDownView extends androidx.appcompat.widget.AppCompatButt } private void updateCountDown() { - if (countDown > 0) { + final long remainingMillis = countDownToTime - System.currentTimeMillis(); + + if (remainingMillis > 0) { setEnabled(false); setAlpha(0.5f); - countDown--; - - int minutesRemaining = countDown / 60; - int secondsRemaining = countDown % 60; + int totalRemainingSeconds = (int) TimeUnit.MILLISECONDS.toSeconds(remainingMillis); + int minutesRemaining = totalRemainingSeconds / 60; + int secondsRemaining = totalRemainingSeconds % 60; setText(getResources().getString(R.string.RegistrationActivity_call_me_instead_available_in, minutesRemaining, secondsRemaining)); if (listener != null) { - listener.onRemaining(this, countDown); + listener.onRemaining(this, totalRemainingSeconds); } - postDelayed(this::updateCountDown, 1000); - } else if (countDown == 0) { + postDelayed(this::updateCountDown, 250); + } else { setCallEnabled(); } } @@ -64,6 +72,6 @@ public class CallMeCountDownView extends androidx.appcompat.widget.AppCompatButt } public interface Listener { - void onRemaining(@NonNull CallMeCountDownView view, int remaining); + void onRemaining(@NonNull CallMeCountDownView view, int secondsRemaining); } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/registration/fragments/AccountLockedFragment.java b/app/src/main/java/org/thoughtcrime/securesms/registration/fragments/AccountLockedFragment.java index 14543f52d0..3628b13aa5 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/registration/fragments/AccountLockedFragment.java +++ b/app/src/main/java/org/thoughtcrime/securesms/registration/fragments/AccountLockedFragment.java @@ -29,7 +29,7 @@ public class AccountLockedFragment extends BaseRegistrationFragment { TextView description = view.findViewById(R.id.account_locked_description); - getModel().getTimeRemaining().observe(getViewLifecycleOwner(), + getModel().getLockedTimeRemaining().observe(getViewLifecycleOwner(), t -> description.setText(getString(R.string.AccountLockedFragment__your_account_has_been_locked_to_protect_your_privacy, durationToDays(t))) ); diff --git a/app/src/main/java/org/thoughtcrime/securesms/registration/fragments/EnterCodeFragment.java b/app/src/main/java/org/thoughtcrime/securesms/registration/fragments/EnterCodeFragment.java index 7ba26650b1..5c6d933205 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/registration/fragments/EnterCodeFragment.java +++ b/app/src/main/java/org/thoughtcrime/securesms/registration/fragments/EnterCodeFragment.java @@ -85,12 +85,15 @@ public final class EnterCodeFragment extends BaseRegistrationFragment { noCodeReceivedHelp.setOnClickListener(v -> sendEmailToSupport()); - getModel().getSuccessfulCodeRequestAttempts().observe(this, (attempts) -> { + 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) { @@ -119,7 +122,7 @@ public final class EnterCodeFragment extends BaseRegistrationFragment { @Override public void onV1RegistrationLockPinRequiredOrIncorrect(long timeRemaining) { - model.setTimeRemaining(timeRemaining); + model.setLockedTimeRemaining(timeRemaining); keyboard.displayLocked().addListener(new AssertedSuccessListener() { @Override public void onSuccess(Boolean r) { @@ -131,7 +134,7 @@ public final class EnterCodeFragment extends BaseRegistrationFragment { @Override public void onKbsRegistrationLockPinRequired(long timeRemaining, @NonNull TokenResponse tokenResponse, @NonNull String kbsStorageCredentials) { - model.setTimeRemaining(timeRemaining); + model.setLockedTimeRemaining(timeRemaining); model.setStorageCredentials(kbsStorageCredentials); model.setKeyBackupCurrentToken(tokenResponse); keyboard.displayLocked().addListener(new AssertedSuccessListener() { @@ -170,7 +173,7 @@ public final class EnterCodeFragment extends BaseRegistrationFragment { @Override public void onKbsAccountLocked(@Nullable Long timeRemaining) { if (timeRemaining != null) { - model.setTimeRemaining(timeRemaining); + model.setLockedTimeRemaining(timeRemaining); } Navigation.findNavController(requireView()).navigate(RegistrationLockFragmentDirections.actionAccountLocked()); } @@ -249,12 +252,12 @@ public final class EnterCodeFragment extends BaseRegistrationFragment { } private void handlePhoneCallRequest() { - callMeCountDown.startCountDown(RegistrationConstants.SUBSEQUENT_CALL_AVAILABLE_AFTER); - 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()); @@ -301,9 +304,10 @@ public final class EnterCodeFragment extends BaseRegistrationFragment { public void onResume() { super.onResume(); - getModel().getLiveNumber().observe(this, (s) -> header.setText(requireContext().getString(R.string.RegistrationActivity_enter_the_code_we_sent_to_s, s.getFullFormattedNumber()))); + RegistrationViewModel model = getModel(); + model.getLiveNumber().observe(this, (s) -> header.setText(requireContext().getString(R.string.RegistrationActivity_enter_the_code_we_sent_to_s, s.getFullFormattedNumber()))); - callMeCountDown.startCountDown(RegistrationConstants.FIRST_CALL_AVAILABLE_AFTER); + model.getCanCallAtTime().observe(this, callAtTime -> callMeCountDown.startCountDownTo(callAtTime)); } private void sendEmailToSupport() { diff --git a/app/src/main/java/org/thoughtcrime/securesms/registration/fragments/RegistrationConstants.java b/app/src/main/java/org/thoughtcrime/securesms/registration/fragments/RegistrationConstants.java index 1b7db0a5c3..5d9497fa78 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/registration/fragments/RegistrationConstants.java +++ b/app/src/main/java/org/thoughtcrime/securesms/registration/fragments/RegistrationConstants.java @@ -5,11 +5,8 @@ final class RegistrationConstants { private RegistrationConstants() { } - static final int FIRST_CALL_AVAILABLE_AFTER = 64; - static final int SUBSEQUENT_CALL_AVAILABLE_AFTER = 300; - static final String TERMS_AND_CONDITIONS_URL = "https://signal.org/legal"; - - static final String SIGNAL_CAPTCHA_URL = "https://signalcaptchas.org/registration/generate.html"; - static final String SIGNAL_CAPTCHA_SCHEME = "signalcaptcha://"; + static final String TERMS_AND_CONDITIONS_URL = "https://signal.org/legal"; + static final String SIGNAL_CAPTCHA_URL = "https://signalcaptchas.org/registration/generate.html"; + static final String SIGNAL_CAPTCHA_SCHEME = "signalcaptcha://"; } diff --git a/app/src/main/java/org/thoughtcrime/securesms/registration/fragments/RegistrationLockFragment.java b/app/src/main/java/org/thoughtcrime/securesms/registration/fragments/RegistrationLockFragment.java index c0bbc876fe..35e4b65594 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/registration/fragments/RegistrationLockFragment.java +++ b/app/src/main/java/org/thoughtcrime/securesms/registration/fragments/RegistrationLockFragment.java @@ -28,7 +28,6 @@ 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.FeatureFlags; import org.thoughtcrime.securesms.util.ServiceUtil; import org.thoughtcrime.securesms.util.concurrent.SimpleTask; import org.whispersystems.signalservice.internal.contacts.entities.TokenResponse; @@ -104,7 +103,7 @@ public final class RegistrationLockFragment extends BaseRegistrationFragment { PinKeyboardType keyboardType = getPinEntryKeyboardType().getOther(); keyboardToggle.setText(resolveKeyboardToggleText(keyboardType)); - getModel().getTimeRemaining() + getModel().getLockedTimeRemaining() .observe(getViewLifecycleOwner(), t -> timeRemaining = t); TokenResponse keyBackupCurrentToken = getModel().getKeyBackupCurrentToken(); @@ -180,7 +179,7 @@ public final class RegistrationLockFragment extends BaseRegistrationFragment { @Override public void onV1RegistrationLockPinRequiredOrIncorrect(long timeRemaining) { - getModel().setTimeRemaining(timeRemaining); + getModel().setLockedTimeRemaining(timeRemaining); cancelSpinning(pinButton); pinEntry.getText().clear(); @@ -243,7 +242,7 @@ public final class RegistrationLockFragment extends BaseRegistrationFragment { @Override public void onKbsAccountLocked(@Nullable Long timeRemaining) { if (timeRemaining != null) { - model.setTimeRemaining(timeRemaining); + model.setLockedTimeRemaining(timeRemaining); } onAccountLocked(); diff --git a/app/src/main/java/org/thoughtcrime/securesms/registration/viewmodel/RegistrationViewModel.java b/app/src/main/java/org/thoughtcrime/securesms/registration/viewmodel/RegistrationViewModel.java index 95557e82db..c3894dea31 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/registration/viewmodel/RegistrationViewModel.java +++ b/app/src/main/java/org/thoughtcrime/securesms/registration/viewmodel/RegistrationViewModel.java @@ -14,11 +14,15 @@ import org.whispersystems.signalservice.internal.contacts.entities.TokenResponse import org.whispersystems.signalservice.internal.util.JsonUtil; import java.io.IOException; +import java.util.concurrent.TimeUnit; public final class RegistrationViewModel extends ViewModel { private static final String TAG = Log.tag(RegistrationViewModel.class); + private static final long FIRST_CALL_AVAILABLE_AFTER_MS = TimeUnit.SECONDS.toMillis(64); + private static final long SUBSEQUENT_CALL_AVAILABLE_AFTER_MS = TimeUnit.SECONDS.toMillis(300); + private final String secret; private final MutableLiveData number; private final MutableLiveData textCodeEntered; @@ -28,8 +32,9 @@ public final class RegistrationViewModel extends ViewModel { private final MutableLiveData restoreFlowShown; private final MutableLiveData successfulCodeRequestAttempts; private final MutableLiveData requestLimiter; - private final MutableLiveData keyBackupcurrentTokenJson; - private final MutableLiveData timeRemaining; + private final MutableLiveData keyBackupCurrentTokenJson; + private final MutableLiveData lockedTimeRemaining; + private final MutableLiveData canCallAtTime; public RegistrationViewModel(@NonNull SavedStateHandle savedStateHandle) { secret = loadValue(savedStateHandle, "REGISTRATION_SECRET", Util.getSecret(18)); @@ -42,8 +47,9 @@ public final class RegistrationViewModel extends ViewModel { restoreFlowShown = savedStateHandle.getLiveData("RESTORE_FLOW_SHOWN", false); successfulCodeRequestAttempts = savedStateHandle.getLiveData("SUCCESSFUL_CODE_REQUEST_ATTEMPTS", 0); requestLimiter = savedStateHandle.getLiveData("REQUEST_RATE_LIMITER", new LocalCodeRequestRateLimiter(60_000)); - keyBackupcurrentTokenJson = savedStateHandle.getLiveData("KBS_TOKEN"); - timeRemaining = savedStateHandle.getLiveData("TIME_REMAINING", 0L); + keyBackupCurrentTokenJson = savedStateHandle.getLiveData("KBS_TOKEN"); + lockedTimeRemaining = savedStateHandle.getLiveData("TIME_REMAINING", 0L); + canCallAtTime = savedStateHandle.getLiveData("CAN_CALL_AT_TIME", 0L); } private static T loadValue(@NonNull SavedStateHandle savedStateHandle, @NonNull String key, @NonNull T initialValue) { @@ -161,7 +167,7 @@ public final class RegistrationViewModel extends ViewModel { } public @Nullable TokenResponse getKeyBackupCurrentToken() { - String json = keyBackupcurrentTokenJson.getValue(); + String json = keyBackupCurrentTokenJson.getValue(); if (json == null) return null; try { return JsonUtil.fromJson(json, TokenResponse.class); @@ -173,14 +179,26 @@ public final class RegistrationViewModel extends ViewModel { public void setKeyBackupCurrentToken(TokenResponse tokenResponse) { String json = tokenResponse == null ? null : JsonUtil.toJson(tokenResponse); - keyBackupcurrentTokenJson.setValue(json); + keyBackupCurrentTokenJson.setValue(json); } - public LiveData getTimeRemaining() { - return timeRemaining; + public LiveData getLockedTimeRemaining() { + return lockedTimeRemaining; } - public void setTimeRemaining(long timeRemaining) { - this.timeRemaining.setValue(timeRemaining); + public LiveData getCanCallAtTime() { + return canCallAtTime; + } + + public void setLockedTimeRemaining(long lockedTimeRemaining) { + this.lockedTimeRemaining.setValue(lockedTimeRemaining); + } + + public void onStartEnterCode() { + canCallAtTime.setValue(System.currentTimeMillis() + FIRST_CALL_AVAILABLE_AFTER_MS); + } + + public void onCallRequested() { + canCallAtTime.setValue(System.currentTimeMillis() + SUBSEQUENT_CALL_AVAILABLE_AFTER_MS); } }