kopia lustrzana https://github.com/ryukoposting/Signal-Android
Use recovery flow for change number when possible.
rodzic
ff76c4cdef
commit
06bec76371
|
@ -89,8 +89,9 @@ abstract class DSLSettingsFragment(
|
|||
}
|
||||
|
||||
override fun onDestroyView() {
|
||||
super.onDestroyView()
|
||||
recyclerView = null
|
||||
toolbar = null
|
||||
super.onDestroyView()
|
||||
}
|
||||
|
||||
fun setTitle(@StringRes resId: Int) {
|
||||
|
|
|
@ -14,6 +14,7 @@ import org.thoughtcrime.securesms.R
|
|||
import org.thoughtcrime.securesms.components.LabeledEditText
|
||||
import org.thoughtcrime.securesms.components.settings.app.changenumber.ChangeNumberUtil.getViewModel
|
||||
import org.thoughtcrime.securesms.components.settings.app.changenumber.ChangeNumberViewModel.ContinueStatus
|
||||
import org.thoughtcrime.securesms.databinding.FragmentChangeNumberEnterPhoneNumberBinding
|
||||
import org.thoughtcrime.securesms.registration.fragments.CountryPickerFragment
|
||||
import org.thoughtcrime.securesms.registration.fragments.CountryPickerFragmentArgs
|
||||
import org.thoughtcrime.securesms.registration.util.ChangeNumberInputController
|
||||
|
@ -25,19 +26,30 @@ private const val NEW_NUMBER_COUNTRY_SELECT = "new_number_country"
|
|||
|
||||
class ChangeNumberEnterPhoneNumberFragment : LoggingFragment(R.layout.fragment_change_number_enter_phone_number) {
|
||||
|
||||
private lateinit var scrollView: ScrollView
|
||||
private var binding: FragmentChangeNumberEnterPhoneNumberBinding? = null
|
||||
|
||||
private lateinit var oldNumberCountrySpinner: Spinner
|
||||
private lateinit var oldNumberCountryCode: LabeledEditText
|
||||
private lateinit var oldNumber: LabeledEditText
|
||||
private val scrollView: ScrollView
|
||||
get() = binding!!.changeNumberEnterPhoneNumberScroll
|
||||
|
||||
private lateinit var newNumberCountrySpinner: Spinner
|
||||
private lateinit var newNumberCountryCode: LabeledEditText
|
||||
private lateinit var newNumber: LabeledEditText
|
||||
private val oldNumberCountrySpinner: Spinner
|
||||
get() = binding!!.changeNumberEnterPhoneNumberOldNumberSpinner
|
||||
private val oldNumberCountryCode: LabeledEditText
|
||||
get() = binding!!.changeNumberEnterPhoneNumberOldNumberCountryCode
|
||||
private val oldNumber: LabeledEditText
|
||||
get() = binding!!.changeNumberEnterPhoneNumberOldNumberNumber
|
||||
|
||||
private val newNumberCountrySpinner: Spinner
|
||||
get() = binding!!.changeNumberEnterPhoneNumberNewNumberSpinner
|
||||
private val newNumberCountryCode: LabeledEditText
|
||||
get() = binding!!.changeNumberEnterPhoneNumberNewNumberCountryCode
|
||||
private val newNumber: LabeledEditText
|
||||
get() = binding!!.changeNumberEnterPhoneNumberNewNumberNumber
|
||||
|
||||
private lateinit var viewModel: ChangeNumberViewModel
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
binding = FragmentChangeNumberEnterPhoneNumberBinding.bind(view)
|
||||
|
||||
viewModel = getViewModel(this)
|
||||
|
||||
val toolbar: Toolbar = view.findViewById(R.id.toolbar)
|
||||
|
@ -48,12 +60,6 @@ class ChangeNumberEnterPhoneNumberFragment : LoggingFragment(R.layout.fragment_c
|
|||
onContinue()
|
||||
}
|
||||
|
||||
scrollView = view.findViewById(R.id.change_number_enter_phone_number_scroll)
|
||||
|
||||
oldNumberCountrySpinner = view.findViewById(R.id.change_number_enter_phone_number_old_number_spinner)
|
||||
oldNumberCountryCode = view.findViewById(R.id.change_number_enter_phone_number_old_number_country_code)
|
||||
oldNumber = view.findViewById(R.id.change_number_enter_phone_number_old_number_number)
|
||||
|
||||
val oldController = ChangeNumberInputController(
|
||||
requireContext(),
|
||||
oldNumberCountryCode,
|
||||
|
@ -87,10 +93,6 @@ class ChangeNumberEnterPhoneNumberFragment : LoggingFragment(R.layout.fragment_c
|
|||
}
|
||||
)
|
||||
|
||||
newNumberCountrySpinner = view.findViewById(R.id.change_number_enter_phone_number_new_number_spinner)
|
||||
newNumberCountryCode = view.findViewById(R.id.change_number_enter_phone_number_new_number_country_code)
|
||||
newNumber = view.findViewById(R.id.change_number_enter_phone_number_new_number_number)
|
||||
|
||||
val newController = ChangeNumberInputController(
|
||||
requireContext(),
|
||||
newNumberCountryCode,
|
||||
|
@ -136,6 +138,11 @@ class ChangeNumberEnterPhoneNumberFragment : LoggingFragment(R.layout.fragment_c
|
|||
viewModel.getLiveNewNumber().observe(viewLifecycleOwner, newController::updateNumber)
|
||||
}
|
||||
|
||||
override fun onDestroyView() {
|
||||
binding = null
|
||||
super.onDestroyView()
|
||||
}
|
||||
|
||||
private fun onContinue() {
|
||||
if (TextUtils.isEmpty(oldNumberCountryCode.text)) {
|
||||
Toast.makeText(context, getString(R.string.ChangeNumberEnterPhoneNumberFragment__you_must_specify_your_old_number_country_code), Toast.LENGTH_LONG).show()
|
||||
|
|
|
@ -17,6 +17,7 @@ import org.thoughtcrime.securesms.database.SignalDatabase
|
|||
import org.thoughtcrime.securesms.database.model.databaseprotos.PendingChangeNumberMetadata
|
||||
import org.thoughtcrime.securesms.database.model.toProtoByteString
|
||||
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies
|
||||
import org.thoughtcrime.securesms.jobs.RefreshAttributesJob
|
||||
import org.thoughtcrime.securesms.keyvalue.CertificateType
|
||||
import org.thoughtcrime.securesms.keyvalue.SignalStore
|
||||
import org.thoughtcrime.securesms.pin.KbsRepository
|
||||
|
@ -86,16 +87,31 @@ class ChangeNumberRepository(
|
|||
|
||||
fun ensureDecryptionsDrained(): Completable {
|
||||
return Completable.create { emitter ->
|
||||
val drainedListener = object : Runnable {
|
||||
override fun run() {
|
||||
emitter.onComplete()
|
||||
ApplicationDependencies
|
||||
.getIncomingMessageObserver()
|
||||
.removeDecryptionDrainedListener(this)
|
||||
}
|
||||
}
|
||||
|
||||
emitter.setCancellable {
|
||||
ApplicationDependencies
|
||||
.getIncomingMessageObserver()
|
||||
.removeDecryptionDrainedListener(drainedListener)
|
||||
}
|
||||
|
||||
ApplicationDependencies
|
||||
.getIncomingMessageObserver()
|
||||
.addDecryptionDrainedListener {
|
||||
emitter.onComplete()
|
||||
}
|
||||
.addDecryptionDrainedListener(drainedListener)
|
||||
}.subscribeOn(Schedulers.single())
|
||||
.timeout(15, TimeUnit.SECONDS)
|
||||
}
|
||||
|
||||
fun changeNumber(sessionId: String, newE164: String, pniUpdateMode: Boolean = false): Single<ServiceResponse<VerifyResponse>> {
|
||||
fun changeNumber(sessionId: String? = null, recoveryPassword: String? = null, newE164: String, pniUpdateMode: Boolean = false): Single<ServiceResponse<VerifyResponse>> {
|
||||
check((sessionId != null && recoveryPassword == null) || (sessionId == null && recoveryPassword != null))
|
||||
|
||||
return Single.fromCallable {
|
||||
var completed = false
|
||||
var attempts = 0
|
||||
|
@ -104,8 +120,8 @@ class ChangeNumberRepository(
|
|||
while (!completed && attempts < 5) {
|
||||
val (request: ChangePhoneNumberRequest, metadata: PendingChangeNumberMetadata) = createChangeNumberRequest(
|
||||
sessionId = sessionId,
|
||||
recoveryPassword = recoveryPassword,
|
||||
newE164 = newE164,
|
||||
registrationLock = null,
|
||||
pniUpdateMode = pniUpdateMode
|
||||
)
|
||||
|
||||
|
@ -156,8 +172,7 @@ class ChangeNumberRepository(
|
|||
val (request: ChangePhoneNumberRequest, metadata: PendingChangeNumberMetadata) = createChangeNumberRequest(
|
||||
sessionId = sessionId,
|
||||
newE164 = newE164,
|
||||
registrationLock = registrationLock,
|
||||
pniUpdateMode = false
|
||||
registrationLock = registrationLock
|
||||
)
|
||||
|
||||
SignalStore.misc().setPendingChangeNumberMetadata(metadata)
|
||||
|
@ -254,6 +269,8 @@ class ChangeNumberRepository(
|
|||
ApplicationDependencies.closeConnections()
|
||||
ApplicationDependencies.getIncomingMessageObserver()
|
||||
|
||||
ApplicationDependencies.getJobManager().add(RefreshAttributesJob())
|
||||
|
||||
return rotateCertificates()
|
||||
}
|
||||
|
||||
|
@ -281,10 +298,11 @@ class ChangeNumberRepository(
|
|||
@Suppress("UsePropertyAccessSyntax")
|
||||
@WorkerThread
|
||||
private fun createChangeNumberRequest(
|
||||
sessionId: String,
|
||||
sessionId: String? = null,
|
||||
recoveryPassword: String? = null,
|
||||
newE164: String,
|
||||
registrationLock: String?,
|
||||
pniUpdateMode: Boolean
|
||||
registrationLock: String? = null,
|
||||
pniUpdateMode: Boolean = false
|
||||
): ChangeNumberRequestData {
|
||||
val selfIdentifier: String = SignalStore.account().requireAci().toString()
|
||||
val aciProtocolStore: SignalProtocolStore = ApplicationDependencies.getProtocolStore().aci()
|
||||
|
@ -338,7 +356,7 @@ class ChangeNumberRepository(
|
|||
|
||||
val request = ChangePhoneNumberRequest(
|
||||
sessionId,
|
||||
null,
|
||||
recoveryPassword,
|
||||
newE164,
|
||||
registrationLock,
|
||||
pniIdentity.publicKey,
|
||||
|
|
|
@ -7,11 +7,14 @@ import android.widget.Toast
|
|||
import androidx.appcompat.widget.Toolbar
|
||||
import androidx.navigation.fragment.findNavController
|
||||
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers
|
||||
import io.reactivex.rxjava3.core.Single
|
||||
import org.signal.core.util.logging.Log
|
||||
import org.thoughtcrime.securesms.LoggingFragment
|
||||
import org.thoughtcrime.securesms.R
|
||||
import org.thoughtcrime.securesms.components.settings.app.changenumber.ChangeNumberUtil.changeNumberSuccess
|
||||
import org.thoughtcrime.securesms.components.settings.app.changenumber.ChangeNumberUtil.getCaptchaArguments
|
||||
import org.thoughtcrime.securesms.components.settings.app.changenumber.ChangeNumberUtil.getViewModel
|
||||
import org.thoughtcrime.securesms.registration.RegistrationSessionProcessor
|
||||
import org.thoughtcrime.securesms.registration.VerifyAccountRepository
|
||||
import org.thoughtcrime.securesms.util.LifecycleDisposable
|
||||
import org.thoughtcrime.securesms.util.dualsim.MccMncProducer
|
||||
|
@ -54,9 +57,24 @@ class ChangeNumberVerifyFragment : LoggingFragment(R.layout.fragment_change_phon
|
|||
lifecycleDisposable += viewModel
|
||||
.ensureDecryptionsDrained()
|
||||
.onErrorComplete()
|
||||
.andThen(viewModel.requestVerificationCode(mode, mccMncProducer.mcc, mccMncProducer.mnc))
|
||||
.andThen(viewModel.changeNumberWithRecoveryPassword())
|
||||
.flatMap { changed ->
|
||||
if (changed) {
|
||||
Single.just(RequestCodeResult.RecoveryPasswordWorked)
|
||||
} else {
|
||||
viewModel.requestVerificationCode(mode, mccMncProducer.mcc, mccMncProducer.mnc)
|
||||
.map { p -> RequestCodeResult.RequestedVerificationCode(p) }
|
||||
}
|
||||
}
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe { processor ->
|
||||
.subscribe { result ->
|
||||
if (result is RequestCodeResult.RecoveryPasswordWorked) {
|
||||
changeNumberSuccess()
|
||||
return@subscribe
|
||||
}
|
||||
|
||||
val processor = (result as RequestCodeResult.RequestedVerificationCode).processor
|
||||
|
||||
if (processor.hasResult()) {
|
||||
findNavController().safeNavigate(R.id.action_changePhoneNumberVerifyFragment_to_changeNumberEnterCodeFragment)
|
||||
} else if (processor.captchaRequired()) {
|
||||
|
@ -74,4 +92,9 @@ class ChangeNumberVerifyFragment : LoggingFragment(R.layout.fragment_change_phon
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
private sealed interface RequestCodeResult {
|
||||
object RecoveryPasswordWorked : RequestCodeResult
|
||||
class RequestedVerificationCode(val processor: RegistrationSessionProcessor) : RequestCodeResult
|
||||
}
|
||||
}
|
||||
|
|
|
@ -170,7 +170,7 @@ class ChangeNumberViewModel(
|
|||
.observeOn(Schedulers.io())
|
||||
.flatMap { processor ->
|
||||
if (processor.isAlreadyVerified() || processor.hasResult() && processor.isVerified()) {
|
||||
changeNumberRepository.changeNumber(sessionId, number.e164Number)
|
||||
changeNumberRepository.changeNumber(sessionId = sessionId, newE164 = number.e164Number)
|
||||
} else if (processor.error == null) {
|
||||
Single.just<ServiceResponse<VerifyResponse>>(ServiceResponse.forApplicationError(IncorrectCodeException(), 403, null))
|
||||
} else {
|
||||
|
@ -203,6 +203,24 @@ class ChangeNumberViewModel(
|
|||
}
|
||||
}
|
||||
|
||||
fun changeNumberWithRecoveryPassword(): Single<Boolean> {
|
||||
val recoveryPassword = SignalStore.kbsValues().recoveryPassword
|
||||
|
||||
return if (SignalStore.kbsValues().hasPin() && recoveryPassword != null) {
|
||||
changeNumberRepository.changeNumber(recoveryPassword = recoveryPassword, newE164 = number.e164Number)
|
||||
.map { r -> VerifyResponseWithoutKbs(r) }
|
||||
.flatMap { p ->
|
||||
if (p.hasResult()) {
|
||||
onVerifySuccess(p).map { true }
|
||||
} else {
|
||||
Single.just(false)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
Single.just(false)
|
||||
}
|
||||
}
|
||||
|
||||
class Factory(owner: SavedStateRegistryOwner) : AbstractSavedStateViewModelFactory(owner, null) {
|
||||
|
||||
override fun <T : ViewModel> create(key: String, modelClass: Class<T>, handle: SavedStateHandle): T {
|
||||
|
|
|
@ -131,6 +131,10 @@ public class IncomingMessageObserver {
|
|||
}
|
||||
}
|
||||
|
||||
public synchronized void removeDecryptionDrainedListener(@NonNull Runnable listener) {
|
||||
decryptionDrainedListeners.remove(listener);
|
||||
}
|
||||
|
||||
public boolean isDecryptionDrained() {
|
||||
return decryptionDrained;
|
||||
}
|
||||
|
|
|
@ -16,20 +16,22 @@ import androidx.core.content.ContextCompat;
|
|||
|
||||
import org.thoughtcrime.securesms.R;
|
||||
|
||||
import java.lang.ref.WeakReference;
|
||||
|
||||
public class LongClickMovementMethod extends LinkMovementMethod {
|
||||
@SuppressLint("StaticFieldLeak")
|
||||
private static LongClickMovementMethod sInstance;
|
||||
|
||||
private final GestureDetector gestureDetector;
|
||||
private View widget;
|
||||
private LongClickCopySpan currentSpan;
|
||||
private final GestureDetector gestureDetector;
|
||||
private WeakReference<View> widget;
|
||||
private LongClickCopySpan currentSpan;
|
||||
|
||||
private LongClickMovementMethod(final Context context) {
|
||||
gestureDetector = new GestureDetector(context, new GestureDetector.SimpleOnGestureListener() {
|
||||
@Override
|
||||
public void onLongPress(MotionEvent e) {
|
||||
if (currentSpan != null && widget != null) {
|
||||
currentSpan.onLongClick(widget);
|
||||
if (currentSpan != null && widget != null && widget.get() != null) {
|
||||
currentSpan.onLongClick(widget.get());
|
||||
widget = null;
|
||||
currentSpan = null;
|
||||
}
|
||||
|
@ -37,8 +39,8 @@ public class LongClickMovementMethod extends LinkMovementMethod {
|
|||
|
||||
@Override
|
||||
public boolean onSingleTapUp(MotionEvent e) {
|
||||
if (currentSpan != null && widget != null) {
|
||||
currentSpan.onClick(widget);
|
||||
if (currentSpan != null && widget != null && widget.get() != null) {
|
||||
currentSpan.onClick(widget.get());
|
||||
widget = null;
|
||||
currentSpan = null;
|
||||
}
|
||||
|
@ -80,7 +82,7 @@ public class LongClickMovementMethod extends LinkMovementMethod {
|
|||
}
|
||||
|
||||
this.currentSpan = aSingleSpan;
|
||||
this.widget = widget;
|
||||
this.widget = new WeakReference<>(widget);
|
||||
return gestureDetector.onTouchEvent(event);
|
||||
} else if (action == MotionEvent.ACTION_UP && Selection.getSelectionEnd(buffer) > 0){
|
||||
Selection.setSelection(buffer, 0);
|
||||
|
|
|
@ -1,8 +1,7 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
tools:viewBindingIgnore="true"
|
||||
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">
|
||||
|
||||
|
|
Ładowanie…
Reference in New Issue