Add skip SMS flow.

This commit is contained in:
Cody Henthorne
2023-02-22 11:03:10 -05:00
committed by Greyson Parrelli
parent a47e3900c1
commit 4f458a022f
19 changed files with 657 additions and 131 deletions

View File

@@ -1,6 +1,7 @@
package org.thoughtcrime.securesms.registration;
import android.app.Application;
import android.app.backup.BackupManager;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
@@ -41,6 +42,7 @@ import org.whispersystems.signalservice.api.push.PNI;
import org.whispersystems.signalservice.api.push.ServiceIdType;
import org.whispersystems.signalservice.api.push.SignalServiceAddress;
import org.whispersystems.signalservice.internal.ServiceResponse;
import org.whispersystems.signalservice.internal.push.BackupAuthCheckProcessor;
import org.whispersystems.signalservice.internal.push.VerifyAccountResponse;
import java.io.IOException;
@@ -95,12 +97,13 @@ public final class RegistrationRepository {
}
public Single<ServiceResponse<VerifyResponse>> registerAccount(@NonNull RegistrationData registrationData,
@NonNull VerifyResponse response)
@NonNull VerifyResponse response,
boolean setRegistrationLockEnabled)
{
return Single.<ServiceResponse<VerifyResponse>>fromCallable(() -> {
try {
String pin = response.getPin();
registerAccountInternal(registrationData, response.getVerifyAccountResponse(), pin, response.getKbsData());
registerAccountInternal(registrationData, response.getVerifyAccountResponse(), pin, response.getKbsData(), setRegistrationLockEnabled);
if (pin != null && !pin.isEmpty()) {
PinState.onPinChangedOrCreated(context, pin, SignalStore.pinValues().getKeyboardType());
@@ -124,7 +127,8 @@ public final class RegistrationRepository {
private void registerAccountInternal(@NonNull RegistrationData registrationData,
@NonNull VerifyAccountResponse response,
@Nullable String pin,
@Nullable KbsPinData kbsData)
@Nullable KbsPinData kbsData,
boolean setRegistrationLockEnabled)
throws IOException
{
ACI aci = ACI.parseOrThrow(response.getUuid());
@@ -172,7 +176,10 @@ public final class RegistrationRepository {
TextSecurePreferences.setPromptedPushRegistration(context, true);
NotificationManagerCompat.from(context).cancel(NotificationIds.UNREGISTERED_NOTIFICATION_ID);
PinState.onRegistration(context, kbsData, pin, hasPin);
PinState.onRegistration(context, kbsData, pin, hasPin, setRegistrationLockEnabled);
ApplicationDependencies.closeConnections();
ApplicationDependencies.getIncomingMessageObserver();
}
private void generateAndRegisterPreKeys(@NonNull ServiceIdType serviceIdType,
@@ -210,8 +217,15 @@ public final class RegistrationRepository {
return null;
}
@Nullable
public String getRecoveryPassword() {
return SignalStore.kbsValues().getRegistrationRecoveryPassword();
public Single<BackupAuthCheckProcessor> getKbsAuthCredential(@NonNull RegistrationData registrationData) {
SignalServiceAccountManager accountManager = AccountManagerFactory.createUnauthenticated(context, registrationData.getE164(), SignalServiceAddress.DEFAULT_DEVICE_ID, registrationData.getPassword());
return accountManager.checkBackupAuthCredentials(registrationData.getE164(), SignalStore.kbsValues().getKbsAuthTokenList())
.map(BackupAuthCheckProcessor::new)
.doOnSuccess(processor -> {
if (SignalStore.kbsValues().removeAuthTokens(processor.getInvalid())) {
new BackupManager(context).dataChanged();
}
});
}
}

View File

@@ -147,7 +147,7 @@ class VerifyAccountRepository(private val context: Application) {
}.subscribeOn(Schedulers.io())
}
fun registerAccount(sessionId: String, registrationData: RegistrationData, pin: String? = null, kbsPinDataProducer: KbsPinDataProducer? = null): Single<ServiceResponse<VerifyResponse>> {
fun registerAccount(sessionId: String?, registrationData: RegistrationData, pin: String? = null, kbsPinDataProducer: KbsPinDataProducer? = null): Single<ServiceResponse<VerifyResponse>> {
val universalUnidentifiedAccess: Boolean = TextSecurePreferences.isUniversalUnidentifiedAccess(context)
val unidentifiedAccessKey: ByteArray = UnidentifiedAccess.deriveAccessKeyFrom(registrationData.profileKey)

View File

@@ -44,7 +44,7 @@ public abstract class BaseRegistrationLockFragment extends LoggingFragment {
/**
* Applies to both V1 and V2 pins, because some V2 pins may have been migrated from V1.
*/
private static final int MINIMUM_PIN_LENGTH = 4;
public static final int MINIMUM_PIN_LENGTH = 4;
private EditText pinEntry;
private View forgotPin;

View File

@@ -180,7 +180,7 @@ public final class EnterPhoneNumberFragment extends LoggingFragment implements R
PlayServicesUtil.PlayServicesStatus fcmStatus = PlayServicesUtil.getPlayServicesStatus(context);
if (fcmStatus == PlayServicesUtil.PlayServicesStatus.SUCCESS) {
confirmNumberPrompt(context, e164number, () -> handleRequestVerification(context, true));
confirmNumberPrompt(context, e164number, () -> onE164EnteredSuccessfully(context, true));
} else if (fcmStatus == PlayServicesUtil.PlayServicesStatus.MISSING) {
confirmNumberPrompt(context, e164number, () -> handlePromptForNoPlayServices(context));
} else if (fcmStatus == PlayServicesUtil.PlayServicesStatus.NEEDS_UPDATE) {
@@ -192,10 +192,26 @@ public final class EnterPhoneNumberFragment extends LoggingFragment implements R
}
}
private void handleRequestVerification(@NonNull Context context, boolean fcmSupported) {
private void onE164EnteredSuccessfully(@NonNull Context context, boolean fcmSupported) {
register.setSpinning();
disableAllEntries();
Disposable disposable = viewModel.canEnterSkipSmsFlow()
.observeOn(AndroidSchedulers.mainThread())
.onErrorReturnItem(false)
.subscribe(canEnter -> {
if (canEnter) {
Log.i(TAG, "Enter skip flow");
SafeNavigation.safeNavigate(NavHostFragment.findNavController(this), EnterPhoneNumberFragmentDirections.actionReRegisterWithPinFragment());
} else {
Log.i(TAG, "Unable to collect necessary data to enter skip flow, returning to normal");
handleRequestVerification(context, fcmSupported);
}
});
disposables.add(disposable);
}
private void handleRequestVerification(@NonNull Context context, boolean fcmSupported) {
if (fcmSupported) {
SmsRetrieverClient client = SmsRetriever.getClient(context);
Task<Void> task = client.startSmsRetriever();
@@ -378,7 +394,7 @@ public final class EnterPhoneNumberFragment extends LoggingFragment implements R
new MaterialAlertDialogBuilder(context)
.setTitle(R.string.RegistrationActivity_missing_google_play_services)
.setMessage(R.string.RegistrationActivity_this_device_is_missing_google_play_services)
.setPositiveButton(R.string.RegistrationActivity_i_understand, (dialog1, which) -> handleRequestVerification(context, false))
.setPositiveButton(R.string.RegistrationActivity_i_understand, (dialog1, which) -> onE164EnteredSuccessfully(context, false))
.setNegativeButton(android.R.string.cancel, null)
.show();
}

View File

@@ -0,0 +1,135 @@
package org.thoughtcrime.securesms.registration.fragments
import android.os.Bundle
import android.text.InputType
import android.view.View
import android.view.inputmethod.EditorInfo
import android.widget.Toast
import androidx.annotation.StringRes
import androidx.fragment.app.activityViewModels
import androidx.navigation.fragment.findNavController
import org.signal.core.util.logging.Log
import org.thoughtcrime.securesms.LoggingFragment
import org.thoughtcrime.securesms.R
import org.thoughtcrime.securesms.databinding.FragmentRegistrationLockBinding
import org.thoughtcrime.securesms.lock.v2.PinKeyboardType
import org.thoughtcrime.securesms.registration.viewmodel.RegistrationViewModel
import org.thoughtcrime.securesms.util.LifecycleDisposable
import org.thoughtcrime.securesms.util.ServiceUtil
import org.thoughtcrime.securesms.util.ViewUtil
import org.thoughtcrime.securesms.util.navigation.safeNavigate
/**
* Using a recovery password or restored KBS token attempt to register in the skip flow.
*/
class ReRegisterWithPinFragment : LoggingFragment(R.layout.fragment_registration_lock) {
companion object {
private val TAG = Log.tag(ReRegisterWithPinFragment::class.java)
}
private var _binding: FragmentRegistrationLockBinding? = null
private val binding: FragmentRegistrationLockBinding
get() = _binding!!
private val viewModel: RegistrationViewModel by activityViewModels()
private val disposables = LifecycleDisposable()
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
_binding = FragmentRegistrationLockBinding.bind(view)
disposables.bindTo(viewLifecycleOwner.lifecycle)
RegistrationViewDelegate.setDebugLogSubmitMultiTapView(binding.kbsLockPinTitle)
binding.kbsLockForgotPin.visibility = View.GONE
binding.kbsLockPinInput.imeOptions = EditorInfo.IME_ACTION_DONE
binding.kbsLockPinInput.setOnEditorActionListener { v, actionId, _ ->
if (actionId == EditorInfo.IME_ACTION_DONE) {
ViewUtil.hideKeyboard(requireContext(), v!!)
handlePinEntry()
return@setOnEditorActionListener true
}
false
}
enableAndFocusPinEntry()
binding.kbsLockPinConfirm.setOnClickListener {
ViewUtil.hideKeyboard(requireContext(), binding.kbsLockPinInput)
handlePinEntry()
}
binding.kbsLockKeyboardToggle.setOnClickListener { v: View? ->
val keyboardType: PinKeyboardType = getPinEntryKeyboardType()
updateKeyboard(keyboardType.other)
binding.kbsLockKeyboardToggle.setText(resolveKeyboardToggleText(keyboardType))
}
val keyboardType: PinKeyboardType = getPinEntryKeyboardType().other
binding.kbsLockKeyboardToggle.setText(resolveKeyboardToggleText(keyboardType))
}
private fun handlePinEntry() {
binding.kbsLockPinInput.isEnabled = false
val pin: String? = binding.kbsLockPinInput.text?.toString()
val trimmedLength = pin?.replace(" ", "")?.length ?: 0
if (trimmedLength == 0) {
Toast.makeText(requireContext(), R.string.RegistrationActivity_you_must_enter_your_registration_lock_PIN, Toast.LENGTH_LONG).show()
enableAndFocusPinEntry()
return
}
if (trimmedLength < BaseRegistrationLockFragment.MINIMUM_PIN_LENGTH) {
Toast.makeText(requireContext(), getString(R.string.RegistrationActivity_your_pin_has_at_least_d_digits_or_characters, BaseRegistrationLockFragment.MINIMUM_PIN_LENGTH), Toast.LENGTH_LONG).show()
enableAndFocusPinEntry()
return
}
binding.kbsLockPinConfirm.setSpinning()
disposables += viewModel.verifyReRegisterWithPin(pin!!)
.subscribe { p ->
if (p.hasResult()) {
Log.i(TAG, "Successfully re-registered via skip flow")
findNavController().safeNavigate(R.id.action_reRegisterWithPinFragment_to_registrationCompletePlaceHolderFragment)
} else {
Log.w(TAG, "Unable to continue skip flow, resuming normal flow", p.error)
// todo handle the various error conditions
Toast.makeText(requireContext(), "retry or nav TODO ERROR See log", Toast.LENGTH_SHORT).show()
binding.kbsLockPinInput.isEnabled = true
}
}
}
private fun enableAndFocusPinEntry() {
binding.kbsLockPinInput.isEnabled = true
binding.kbsLockPinInput.isFocusable = true
if (binding.kbsLockPinInput.requestFocus()) {
ServiceUtil.getInputMethodManager(binding.kbsLockPinInput.context).showSoftInput(binding.kbsLockPinInput, 0)
}
}
private fun getPinEntryKeyboardType(): PinKeyboardType {
val isNumeric = binding.kbsLockPinInput.inputType and InputType.TYPE_MASK_CLASS == InputType.TYPE_CLASS_NUMBER
return if (isNumeric) PinKeyboardType.NUMERIC else PinKeyboardType.ALPHA_NUMERIC
}
private fun updateKeyboard(keyboard: PinKeyboardType) {
val isAlphaNumeric = keyboard == PinKeyboardType.ALPHA_NUMERIC
binding.kbsLockPinInput.inputType = if (isAlphaNumeric) InputType.TYPE_CLASS_TEXT or InputType.TYPE_TEXT_VARIATION_PASSWORD else InputType.TYPE_CLASS_NUMBER or InputType.TYPE_NUMBER_VARIATION_PASSWORD
binding.kbsLockPinInput.text.clear()
}
@StringRes
private fun resolveKeyboardToggleText(keyboard: PinKeyboardType): Int {
return if (keyboard == PinKeyboardType.ALPHA_NUMERIC) {
R.string.RegistrationLockFragment__enter_alphanumeric_pin
} else {
R.string.RegistrationLockFragment__enter_numeric_pin
}
}
}

View File

@@ -42,6 +42,7 @@ public abstract class BaseRegistrationViewModel extends ViewModel {
private static final String STATE_TIME_REMAINING = "TIME_REMAINING";
private static final String STATE_CAN_CALL_AT_TIME = "CAN_CALL_AT_TIME";
private static final String STATE_CAN_SMS_AT_TIME = "CAN_SMS_AT_TIME";
private static final String STATE_RECOVERY_PASSWORD = "RECOVERY_PASSWORD";
protected final SavedStateHandle savedState;
protected final VerifyAccountRepository verifyAccountRepository;
@@ -62,9 +63,10 @@ public abstract class BaseRegistrationViewModel extends ViewModel {
setInitialDefaultValue(STATE_VERIFICATION_CODE, "");
setInitialDefaultValue(STATE_SUCCESSFUL_CODE_REQUEST_ATTEMPTS, 0);
setInitialDefaultValue(STATE_REQUEST_RATE_LIMITER, new LocalCodeRequestRateLimiter(60_000));
setInitialDefaultValue(STATE_RECOVERY_PASSWORD, SignalStore.kbsValues().getRecoveryPassword());
}
protected <T> void setInitialDefaultValue(@NonNull String key, @NonNull T initialValue) {
protected <T> void setInitialDefaultValue(@NonNull String key, @Nullable T initialValue) {
if (!savedState.contains(key) || savedState.get(key) == null) {
savedState.set(key, initialValue);
}
@@ -164,6 +166,14 @@ public abstract class BaseRegistrationViewModel extends ViewModel {
savedState.set(STATE_KBS_TOKEN, tokenData);
}
public void setRecoveryPassword(@Nullable String recoveryPassword) {
savedState.set(STATE_RECOVERY_PASSWORD, recoveryPassword);
}
public @Nullable String getRecoveryPassword() {
return savedState.get(STATE_RECOVERY_PASSWORD);
}
public LiveData<Long> getLockedTimeRemaining() {
return savedState.getLiveData(STATE_TIME_REMAINING, 0L);
}

View File

@@ -3,14 +3,21 @@ package org.thoughtcrime.securesms.registration.viewmodel;
import androidx.annotation.MainThread;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.WorkerThread;
import androidx.lifecycle.AbstractSavedStateViewModelFactory;
import androidx.lifecycle.SavedStateHandle;
import androidx.lifecycle.ViewModel;
import androidx.savedstate.SavedStateRegistryOwner;
import org.signal.core.util.Stopwatch;
import org.signal.core.util.logging.Log;
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
import org.thoughtcrime.securesms.jobs.StorageAccountRestoreJob;
import org.thoughtcrime.securesms.jobs.StorageSyncJob;
import org.thoughtcrime.securesms.keyvalue.SignalStore;
import org.thoughtcrime.securesms.lock.PinHashing;
import org.thoughtcrime.securesms.pin.KbsRepository;
import org.thoughtcrime.securesms.pin.KeyBackupSystemWrongPinException;
import org.thoughtcrime.securesms.pin.TokenData;
import org.thoughtcrime.securesms.registration.RegistrationData;
import org.thoughtcrime.securesms.registration.RegistrationRepository;
@@ -19,13 +26,18 @@ import org.thoughtcrime.securesms.registration.VerifyAccountRepository;
import org.thoughtcrime.securesms.registration.VerifyResponse;
import org.thoughtcrime.securesms.registration.VerifyResponseProcessor;
import org.thoughtcrime.securesms.registration.VerifyResponseWithRegistrationLockProcessor;
import org.thoughtcrime.securesms.registration.VerifyResponseWithSuccessfulKbs;
import org.thoughtcrime.securesms.registration.VerifyResponseWithoutKbs;
import org.thoughtcrime.securesms.util.FeatureFlags;
import org.thoughtcrime.securesms.util.Util;
import org.whispersystems.signalservice.api.KbsPinData;
import org.whispersystems.signalservice.api.KeyBackupSystemNoDataException;
import org.whispersystems.signalservice.internal.ServiceResponse;
import org.whispersystems.signalservice.internal.push.RegistrationSessionMetadataResponse;
import java.io.IOException;
import java.util.Objects;
import java.util.concurrent.TimeUnit;
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers;
import io.reactivex.rxjava3.core.Single;
@@ -33,6 +45,8 @@ import io.reactivex.rxjava3.schedulers.Schedulers;
public final class RegistrationViewModel extends BaseRegistrationViewModel {
private static final String TAG = Log.tag(RegistrationViewModel.class);
private static final String STATE_FCM_TOKEN = "FCM_TOKEN";
private static final String STATE_RESTORE_FLOW_SHOWN = "RESTORE_FLOW_SHOWN";
private static final String STATE_IS_REREGISTER = "IS_REREGISTER";
@@ -113,7 +127,7 @@ public final class RegistrationViewModel extends BaseRegistrationViewModel {
setCanCallAtTime(processor.getNextCodeViaCallAttempt());
})
.observeOn(Schedulers.io())
.flatMap( processor -> {
.flatMap(processor -> {
if (processor.isAlreadyVerified() || (processor.hasResult() && processor.isVerified())) {
return verifyAccountRepository.registerAccount(sessionId, getRegistrationData(), null, null);
} else {
@@ -155,7 +169,7 @@ public final class RegistrationViewModel extends BaseRegistrationViewModel {
setCanCallAtTime(processor.getNextCodeViaCallAttempt());
}
})
.flatMap( processor -> {
.flatMap(processor -> {
if (processor.isAlreadyVerified() || (processor.hasResult() && processor.isVerified())) {
return verifyAccountRepository.registerAccount(sessionId, getRegistrationData(), pin, () -> Objects.requireNonNull(KbsRepository.restoreMasterKey(pin, kbsTokenData.getEnclave(), kbsTokenData.getBasicAuth(), kbsTokenData.getTokenResponse())));
} else {
@@ -166,13 +180,13 @@ public final class RegistrationViewModel extends BaseRegistrationViewModel {
@Override
protected Single<VerifyResponseProcessor> onVerifySuccess(@NonNull VerifyResponseProcessor processor) {
return registrationRepository.registerAccount(getRegistrationData(), processor.getResult())
return registrationRepository.registerAccount(getRegistrationData(), processor.getResult(), false)
.map(VerifyResponseWithoutKbs::new);
}
@Override
protected Single<VerifyResponseWithRegistrationLockProcessor> onVerifySuccessWithRegistrationLock(@NonNull VerifyResponseWithRegistrationLockProcessor processor, String pin) {
return registrationRepository.registerAccount(getRegistrationData(), processor.getResult())
return registrationRepository.registerAccount(getRegistrationData(), processor.getResult(), true)
.map(processor::updatedIfRegistrationFailed);
}
@@ -184,7 +198,169 @@ public final class RegistrationViewModel extends BaseRegistrationViewModel {
registrationRepository.getProfileKey(getNumber().getE164Number()),
getFcmToken(),
registrationRepository.getPniRegistrationId(),
registrationRepository.getRecoveryPassword());
getRecoveryPassword());
}
public @NonNull Single<VerifyResponseProcessor> verifyReRegisterWithPin(@NonNull String pin) {
return Single.fromCallable(() -> verifyReRegisterWithPinInternal(pin))
.subscribeOn(Schedulers.io())
.observeOn(Schedulers.io())
.flatMap(data -> {
if (data.canProceed) {
return verifyReRegisterWithRecoveryPassword(pin, data.pinData);
} else {
throw new IllegalStateException("Unable to get token or master key");
}
})
.onErrorReturn(t -> new VerifyResponseWithoutKbs(ServiceResponse.forUnknownError(t)))
.doOnSuccess(p -> {
if (p.hasResult()) {
restoreFromStorageService();
}
})
.observeOn(AndroidSchedulers.mainThread());
}
@WorkerThread
private @NonNull ReRegistrationData verifyReRegisterWithPinInternal(@NonNull String pin)
throws KeyBackupSystemWrongPinException, IOException, KeyBackupSystemNoDataException
{
String localPinHash = SignalStore.kbsValues().getLocalPinHash();
if (hasRecoveryPassword() && localPinHash != null && PinHashing.verifyLocalPinHash(localPinHash, pin)) {
Log.i(TAG, "Local pin matches input, attempting registration");
return ReRegistrationData.canProceed(new KbsPinData(SignalStore.kbsValues().getOrCreateMasterKey(), SignalStore.kbsValues().getRegistrationLockTokenResponse()));
} else {
TokenData data = getKeyBackupCurrentToken();
if (data == null) {
Log.w(TAG, "No token data, abort skip flow");
return ReRegistrationData.cannotProceed();
}
KbsPinData kbsPinData = KbsRepository.restoreMasterKey(pin, data.getEnclave(), data.getBasicAuth(), data.getTokenResponse());
if (kbsPinData == null || kbsPinData.getMasterKey() == null) {
Log.w(TAG, "No kbs data, abort skip flow");
return ReRegistrationData.cannotProceed();
}
setRecoveryPassword(kbsPinData.getMasterKey().deriveRegistrationRecoveryPassword());
return ReRegistrationData.canProceed(kbsPinData);
}
}
private Single<VerifyResponseProcessor> verifyReRegisterWithRecoveryPassword(@NonNull String pin, @NonNull KbsPinData pinData) {
RegistrationData registrationData = getRegistrationData();
if (registrationData.getRecoveryPassword() == null) {
throw new IllegalStateException("No valid recovery password");
}
return verifyAccountRepository.registerAccount(null, registrationData, null, null)
.onErrorReturn(ServiceResponse::forUnknownError)
.map(VerifyResponseWithoutKbs::new)
.flatMap(processor -> {
if (processor.registrationLock()) {
return verifyAccountRepository.registerAccount(null, registrationData, pin, () -> pinData)
.onErrorReturn(ServiceResponse::forUnknownError)
.map(r -> new VerifyResponseWithRegistrationLockProcessor(r, getKeyBackupCurrentToken()));
} else {
return Single.just(processor);
}
})
.<VerifyResponseProcessor>flatMap(processor -> {
if (processor.hasResult()) {
VerifyResponse verifyResponse = processor.getResult();
boolean setRegistrationLockEnabled = verifyResponse.getKbsData() != null;
if (!setRegistrationLockEnabled) {
verifyResponse = new VerifyResponse(processor.getResult().getVerifyAccountResponse(), pinData, pin);
}
return registrationRepository.registerAccount(registrationData, verifyResponse, setRegistrationLockEnabled)
.map(r -> {
return setRegistrationLockEnabled ? new VerifyResponseWithRegistrationLockProcessor(r, getKeyBackupCurrentToken())
: new VerifyResponseWithoutKbs(r);
});
} else {
return Single.just(processor);
}
})
.observeOn(AndroidSchedulers.mainThread());
}
public @NonNull Single<Boolean> canEnterSkipSmsFlow() {
return Single.just(hasRecoveryPassword())
.flatMap(hasRecoveryPassword -> {
if (hasRecoveryPassword) {
Log.d(TAG, "Have valid recovery password but still checking kbs credentials as a backup");
return checkForValidKbsAuthCredentials().map(unused -> true);
} else {
return checkForValidKbsAuthCredentials();
}
});
}
private Single<Boolean> checkForValidKbsAuthCredentials() {
return registrationRepository.getKbsAuthCredential(getRegistrationData())
.flatMap(p -> {
if (p.getValid() != null) {
return kbsRepository.getToken(p.getValid())
.flatMap(r -> {
if (r.getResult().isPresent()) {
setKeyBackupTokenData(r.getResult().get());
return Single.just(true);
} else {
return Single.just(false);
}
});
} else {
return Single.just(false);
}
})
.onErrorReturnItem(false)
.observeOn(AndroidSchedulers.mainThread());
}
private void restoreFromStorageService() {
SignalStore.onboarding().clearAll();
Stopwatch stopwatch = new Stopwatch("ReRegisterRestore");
ApplicationDependencies.getJobManager().runSynchronously(new StorageAccountRestoreJob(), StorageAccountRestoreJob.LIFESPAN);
stopwatch.split("AccountRestore");
ApplicationDependencies.getJobManager().runSynchronously(new StorageSyncJob(), TimeUnit.SECONDS.toMillis(10));
stopwatch.split("ContactRestore");
try {
FeatureFlags.refreshSync();
} catch (IOException e) {
Log.w(TAG, "Failed to refresh flags.", e);
}
stopwatch.split("FeatureFlags");
stopwatch.stop(TAG);
}
private boolean hasRecoveryPassword() {
return getRecoveryPassword() != null && Objects.equals(getRegistrationData().getE164(), SignalStore.account().getE164());
}
private static class ReRegistrationData {
public boolean canProceed;
public KbsPinData pinData;
private ReRegistrationData(boolean canProceed, @Nullable KbsPinData pinData) {
this.canProceed = canProceed;
this.pinData = pinData;
}
public static ReRegistrationData cannotProceed() {
return new ReRegistrationData(false, null);
}
public static ReRegistrationData canProceed(@NonNull KbsPinData pinData) {
return new ReRegistrationData(true, pinData);
}
}
public static final class Factory extends AbstractSavedStateViewModelFactory {