Skip to content

Commit

Permalink
Use recovery flow for change number when possible.
Browse files Browse the repository at this point in the history
  • Loading branch information
cody-signal committed Feb 24, 2023
1 parent ff76c4c commit 06bec76
Show file tree
Hide file tree
Showing 8 changed files with 114 additions and 42 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -89,8 +89,9 @@ abstract class DSLSettingsFragment(
}

override fun onDestroyView() {
super.onDestroyView()
recyclerView = null
toolbar = null
super.onDestroyView()
}

fun setTitle(@StringRes resId: Int) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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)
Expand All @@ -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,
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -86,16 +87,31 @@ class ChangeNumberRepository(

fun ensureDecryptionsDrained(): Completable {
return Completable.create { emitter ->
ApplicationDependencies
.getIncomingMessageObserver()
.addDecryptionDrainedListener {
val drainedListener = object : Runnable {
override fun run() {
emitter.onComplete()
ApplicationDependencies
.getIncomingMessageObserver()
.removeDecryptionDrainedListener(this)
}
}

emitter.setCancellable {
ApplicationDependencies
.getIncomingMessageObserver()
.removeDecryptionDrainedListener(drainedListener)
}

ApplicationDependencies
.getIncomingMessageObserver()
.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
Expand All @@ -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
)

Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -254,6 +269,8 @@ class ChangeNumberRepository(
ApplicationDependencies.closeConnections()
ApplicationDependencies.getIncomingMessageObserver()

ApplicationDependencies.getJobManager().add(RefreshAttributesJob())

return rotateCertificates()
}

Expand Down Expand Up @@ -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()
Expand Down Expand Up @@ -338,7 +356,7 @@ class ChangeNumberRepository(

val request = ChangePhoneNumberRequest(
sessionId,
null,
recoveryPassword,
newE164,
registrationLock,
pniIdentity.publicKey,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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()) {
Expand All @@ -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
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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 {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,10 @@ public synchronized void addDecryptionDrainedListener(@NonNull Runnable listener
}
}

public synchronized void removeDecryptionDrainedListener(@NonNull Runnable listener) {
decryptionDrainedListeners.remove(listener);
}

public boolean isDecryptionDrained() {
return decryptionDrained;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,29 +16,31 @@

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;
}
}

@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;
}
Expand Down Expand Up @@ -80,7 +82,7 @@ public boolean onTouchEvent(TextView widget, Spannable buffer, MotionEvent event
}

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);
Expand Down
Original file line number Diff line number Diff line change
@@ -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">

Expand Down

0 comments on commit 06bec76

Please sign in to comment.