diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 9f9a414930..1383e63659 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -391,6 +391,10 @@
+
+
destination, @Nullable Intent nextIntent) {
final Intent intent = new Intent(this, destination);
if (nextIntent != null) intent.putExtra("next_intent", nextIntent);
diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/changenumber/ChangeNumberEnterCodeFragment.kt b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/changenumber/ChangeNumberEnterCodeFragment.kt
index 713f648366..c34b2ffe18 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/changenumber/ChangeNumberEnterCodeFragment.kt
+++ b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/changenumber/ChangeNumberEnterCodeFragment.kt
@@ -2,12 +2,14 @@ package org.thoughtcrime.securesms.components.settings.app.changenumber
import android.os.Bundle
import android.view.View
+import androidx.activity.OnBackPressedCallback
import androidx.appcompat.widget.Toolbar
import androidx.navigation.fragment.findNavController
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.keyvalue.SignalStore
import org.thoughtcrime.securesms.registration.fragments.BaseEnterCodeFragment
class ChangeNumberEnterCodeFragment : BaseEnterCodeFragment(R.layout.fragment_change_number_enter_code) {
@@ -17,9 +19,26 @@ class ChangeNumberEnterCodeFragment : BaseEnterCodeFragment(R.id.verify_header).setOnClickListener(null)
+
+ requireActivity().onBackPressedDispatcher.addCallback(
+ viewLifecycleOwner,
+ object : OnBackPressedCallback(true) {
+ override fun handleOnBackPressed() {
+ navigateUp()
+ }
+ }
+ )
+ }
+
+ private fun navigateUp() {
+ if (SignalStore.misc().isChangeNumberLocked) {
+ startActivity(ChangeNumberLockActivity.createIntent(requireContext()))
+ } else {
+ findNavController().navigateUp()
+ }
}
override fun getViewModel(): ChangeNumberViewModel {
diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/changenumber/ChangeNumberLockActivity.kt b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/changenumber/ChangeNumberLockActivity.kt
new file mode 100644
index 0000000000..ecc17d0dd4
--- /dev/null
+++ b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/changenumber/ChangeNumberLockActivity.kt
@@ -0,0 +1,108 @@
+package org.thoughtcrime.securesms.components.settings.app.changenumber
+
+import android.content.Context
+import android.content.Intent
+import android.os.Bundle
+import com.google.android.material.dialog.MaterialAlertDialogBuilder
+import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers
+import io.reactivex.rxjava3.core.Single
+import io.reactivex.rxjava3.kotlin.subscribeBy
+import org.signal.core.util.logging.Log
+import org.thoughtcrime.securesms.MainActivity
+import org.thoughtcrime.securesms.PassphraseRequiredActivity
+import org.thoughtcrime.securesms.R
+import org.thoughtcrime.securesms.keyvalue.SignalStore
+import org.thoughtcrime.securesms.logsubmit.SubmitDebugLogActivity
+import org.thoughtcrime.securesms.phonenumbers.PhoneNumberFormatter
+import org.thoughtcrime.securesms.util.DynamicNoActionBarTheme
+import org.thoughtcrime.securesms.util.DynamicTheme
+import org.thoughtcrime.securesms.util.LifecycleDisposable
+import org.thoughtcrime.securesms.util.TextSecurePreferences
+import java.util.Objects
+
+private val TAG: String = Log.tag(ChangeNumberLockActivity::class.java)
+
+/**
+ * A captive activity that can determine if an interrupted/erred change number request
+ * caused a disparity between the server and our locally stored number.
+ */
+class ChangeNumberLockActivity : PassphraseRequiredActivity() {
+
+ private val dynamicTheme: DynamicTheme = DynamicNoActionBarTheme()
+ private val disposables: LifecycleDisposable = LifecycleDisposable()
+ private lateinit var changeNumberRepository: ChangeNumberRepository
+
+ override fun onCreate(savedInstanceState: Bundle?, ready: Boolean) {
+ dynamicTheme.onCreate(this)
+ disposables.bindTo(lifecycle)
+
+ setContentView(R.layout.activity_change_number_lock)
+
+ changeNumberRepository = ChangeNumberRepository(applicationContext)
+ checkWhoAmI()
+ }
+
+ override fun onResume() {
+ super.onResume()
+ dynamicTheme.onResume(this)
+ }
+
+ override fun onBackPressed() = Unit
+
+ private fun checkWhoAmI() {
+ disposables.add(
+ changeNumberRepository.whoAmI()
+ .flatMap { whoAmI ->
+ if (Objects.equals(whoAmI.number, TextSecurePreferences.getLocalNumber(this))) {
+ Log.i(TAG, "Local and remote numbers match, nothing needs to be done.")
+ Single.just(false)
+ } else {
+ Log.i(TAG, "Local (${TextSecurePreferences.getLocalNumber(this)}) and remote (${whoAmI.number}) numbers do not match, updating local.")
+ changeNumberRepository.changeLocalNumber(whoAmI.number)
+ .map { true }
+ }
+ }
+ .observeOn(AndroidSchedulers.mainThread())
+ .subscribeBy(onSuccess = { onChangeStatusConfirmed() }, onError = this::onFailedToGetChangeNumberStatus)
+ )
+ }
+
+ private fun onChangeStatusConfirmed() {
+ SignalStore.misc().unlockChangeNumber()
+
+ MaterialAlertDialogBuilder(this)
+ .setTitle(R.string.ChangeNumberLockActivity__change_status_confirmed)
+ .setMessage(getString(R.string.ChangeNumberLockActivity__your_number_has_been_confirmed_as_s, PhoneNumberFormatter.prettyPrint(TextSecurePreferences.getLocalNumber(this))))
+ .setPositiveButton(android.R.string.ok) { _, _ ->
+ startActivity(MainActivity.clearTop(this))
+ finish()
+ }
+ .setCancelable(false)
+ .show()
+ }
+
+ private fun onFailedToGetChangeNumberStatus(error: Throwable) {
+ Log.w(TAG, "Unable to determine status of change number", error)
+
+ MaterialAlertDialogBuilder(this)
+ .setTitle(R.string.ChangeNumberLockActivity__change_status_unconfirmed)
+ .setMessage(getString(R.string.ChangeNumberLockActivity__we_could_not_determine_the_status_of_your_change_number_request, error.javaClass.simpleName))
+ .setPositiveButton(R.string.ChangeNumberLockActivity__retry) { _, _ -> checkWhoAmI() }
+ .setNegativeButton(R.string.ChangeNumberLockActivity__leave) { _, _ -> finish() }
+ .setNeutralButton(R.string.ChangeNumberLockActivity__submit_debug_log) { _, _ ->
+ startActivity(Intent(this, SubmitDebugLogActivity::class.java))
+ finish()
+ }
+ .setCancelable(false)
+ .show()
+ }
+
+ companion object {
+ @JvmStatic
+ fun createIntent(context: Context): Intent {
+ return Intent(context, ChangeNumberLockActivity::class.java).apply {
+ flags = Intent.FLAG_ACTIVITY_SINGLE_TOP
+ }
+ }
+ }
+}
diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/changenumber/ChangeNumberRegistrationLockFragment.kt b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/changenumber/ChangeNumberRegistrationLockFragment.kt
index 16a078923f..a98e902269 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/changenumber/ChangeNumberRegistrationLockFragment.kt
+++ b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/changenumber/ChangeNumberRegistrationLockFragment.kt
@@ -2,6 +2,7 @@ package org.thoughtcrime.securesms.components.settings.app.changenumber
import android.os.Bundle
import android.view.View
+import androidx.activity.OnBackPressedCallback
import androidx.appcompat.widget.Toolbar
import androidx.navigation.fragment.findNavController
import org.thoughtcrime.securesms.R
@@ -20,7 +21,16 @@ class ChangeNumberRegistrationLockFragment : BaseRegistrationLockFragment(R.layo
super.onViewCreated(view, savedInstanceState)
val toolbar: Toolbar = view.findViewById(R.id.toolbar)
- toolbar.setNavigationOnClickListener { findNavController().navigateUp() }
+ toolbar.setNavigationOnClickListener { navigateUp() }
+
+ requireActivity().onBackPressedDispatcher.addCallback(
+ viewLifecycleOwner,
+ object : OnBackPressedCallback(true) {
+ override fun handleOnBackPressed() {
+ navigateUp()
+ }
+ }
+ )
}
override fun getViewModel(): BaseRegistrationViewModel {
@@ -60,4 +70,12 @@ class ChangeNumberRegistrationLockFragment : BaseRegistrationLockFragment(R.layo
body
)
}
+
+ private fun navigateUp() {
+ if (SignalStore.misc().isChangeNumberLocked) {
+ startActivity(ChangeNumberLockActivity.createIntent(requireContext()))
+ } else {
+ findNavController().navigateUp()
+ }
+ }
}
diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/changenumber/ChangeNumberRepository.kt b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/changenumber/ChangeNumberRepository.kt
index 0dd487a5ce..9fd581b2f5 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/changenumber/ChangeNumberRepository.kt
+++ b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/changenumber/ChangeNumberRepository.kt
@@ -18,6 +18,7 @@ import org.whispersystems.signalservice.api.KbsPinData
import org.whispersystems.signalservice.api.KeyBackupSystemNoDataException
import org.whispersystems.signalservice.internal.ServiceResponse
import org.whispersystems.signalservice.internal.push.VerifyAccountResponse
+import org.whispersystems.signalservice.internal.push.WhoAmIResponse
private val TAG: String = Log.tag(ChangeNumberRepository::class.java)
@@ -51,12 +52,18 @@ class ChangeNumberRepository(private val context: Context) {
}.subscribeOn(Schedulers.io())
}
+ @Suppress("UsePropertyAccessSyntax")
+ fun whoAmI(): Single {
+ return Single.fromCallable { ApplicationDependencies.getSignalServiceAccountManager().getWhoAmI() }
+ .subscribeOn(Schedulers.io())
+ }
+
@WorkerThread
fun changeLocalNumber(e164: String): Single {
- TextSecurePreferences.setLocalNumber(context, e164)
-
DatabaseFactory.getRecipientDatabase(context).updateSelfPhone(e164)
+ TextSecurePreferences.setLocalNumber(context, e164)
+
ApplicationDependencies.closeConnections()
ApplicationDependencies.getIncomingMessageObserver()
diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/changenumber/ChangeNumberViewModel.kt b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/changenumber/ChangeNumberViewModel.kt
index 205fe98e18..48b9d91a12 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/changenumber/ChangeNumberViewModel.kt
+++ b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/changenumber/ChangeNumberViewModel.kt
@@ -12,18 +12,21 @@ import com.google.i18n.phonenumbers.PhoneNumberUtil
import io.reactivex.rxjava3.core.Single
import org.signal.core.util.logging.Log
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies
+import org.thoughtcrime.securesms.keyvalue.SignalStore
import org.thoughtcrime.securesms.pin.KbsRepository
import org.thoughtcrime.securesms.pin.TokenData
import org.thoughtcrime.securesms.registration.VerifyAccountRepository
import org.thoughtcrime.securesms.registration.VerifyAccountResponseProcessor
import org.thoughtcrime.securesms.registration.VerifyAccountResponseWithoutKbs
import org.thoughtcrime.securesms.registration.VerifyCodeWithRegistrationLockResponseProcessor
+import org.thoughtcrime.securesms.registration.VerifyProcessor
import org.thoughtcrime.securesms.registration.viewmodel.BaseRegistrationViewModel
import org.thoughtcrime.securesms.registration.viewmodel.NumberViewState
import org.thoughtcrime.securesms.util.DefaultValueLiveData
import org.thoughtcrime.securesms.util.TextSecurePreferences
import org.whispersystems.signalservice.internal.ServiceResponse
import org.whispersystems.signalservice.internal.push.VerifyAccountResponse
+import java.util.Objects
private val TAG: String = Log.tag(ChangeNumberViewModel::class.java)
@@ -104,6 +107,35 @@ class ChangeNumberViewModel(
}
}
+ override fun verifyCodeWithoutRegistrationLock(code: String): Single {
+ return super.verifyCodeWithoutRegistrationLock(code)
+ .doOnSubscribe { SignalStore.misc().lockChangeNumber() }
+ .flatMap(this::attemptToUnlockChangeNumber)
+ }
+
+ override fun verifyCodeAndRegisterAccountWithRegistrationLock(pin: String): Single {
+ return super.verifyCodeAndRegisterAccountWithRegistrationLock(pin)
+ .doOnSubscribe { SignalStore.misc().lockChangeNumber() }
+ .flatMap(this::attemptToUnlockChangeNumber)
+ }
+
+ private fun attemptToUnlockChangeNumber(processor: T): Single {
+ return if (processor.hasResult() || processor.isServerSentError()) {
+ SignalStore.misc().unlockChangeNumber()
+ Single.just(processor)
+ } else {
+ changeNumberRepository.whoAmI()
+ .map { whoAmI ->
+ if (Objects.equals(whoAmI.number, localNumber)) {
+ Log.i(TAG, "Local and remote numbers match, we can unlock.")
+ SignalStore.misc().unlockChangeNumber()
+ }
+ processor
+ }
+ .onErrorReturn { processor }
+ }
+ }
+
override fun verifyAccountWithoutRegistrationLock(): Single> {
return changeNumberRepository.changeNumber(textCodeEntered, number.e164Number)
}
diff --git a/app/src/main/java/org/thoughtcrime/securesms/keyvalue/MiscellaneousValues.java b/app/src/main/java/org/thoughtcrime/securesms/keyvalue/MiscellaneousValues.java
index a9cc647138..c659b51b97 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/keyvalue/MiscellaneousValues.java
+++ b/app/src/main/java/org/thoughtcrime/securesms/keyvalue/MiscellaneousValues.java
@@ -15,6 +15,7 @@ public final class MiscellaneousValues extends SignalStoreValues {
private static final String CLIENT_DEPRECATED = "misc.client_deprecated";
private static final String OLD_DEVICE_TRANSFER_LOCKED = "misc.old_device.transfer.locked";
private static final String HAS_EVER_HAD_AN_AVATAR = "misc.has.ever.had.an.avatar";
+ private static final String CHANGE_NUMBER_LOCK = "misc.change_number.lock";
MiscellaneousValues(@NonNull KeyValueStore store) {
super(store);
@@ -97,4 +98,16 @@ public final class MiscellaneousValues extends SignalStoreValues {
public void markHasEverHadAnAvatar() {
putBoolean(HAS_EVER_HAD_AN_AVATAR, true);
}
+
+ public boolean isChangeNumberLocked() {
+ return getBoolean(CHANGE_NUMBER_LOCK, false);
+ }
+
+ public void lockChangeNumber() {
+ putBoolean(CHANGE_NUMBER_LOCK, true);
+ }
+
+ public void unlockChangeNumber() {
+ putBoolean(CHANGE_NUMBER_LOCK, false);
+ }
}
diff --git a/app/src/main/java/org/thoughtcrime/securesms/registration/VerifyAccountResponseProcessor.kt b/app/src/main/java/org/thoughtcrime/securesms/registration/VerifyAccountResponseProcessor.kt
index 3072e32adb..0cb9e669c0 100644
--- a/app/src/main/java/org/thoughtcrime/securesms/registration/VerifyAccountResponseProcessor.kt
+++ b/app/src/main/java/org/thoughtcrime/securesms/registration/VerifyAccountResponseProcessor.kt
@@ -1,6 +1,7 @@
package org.thoughtcrime.securesms.registration
import org.thoughtcrime.securesms.pin.TokenData
+import org.whispersystems.signalservice.api.push.exceptions.NonSuccessfulResponseCodeException
import org.whispersystems.signalservice.internal.ServiceResponse
import org.whispersystems.signalservice.internal.ServiceResponseProcessor
import org.whispersystems.signalservice.internal.push.LockedException
@@ -9,7 +10,9 @@ import org.whispersystems.signalservice.internal.push.VerifyAccountResponse
/**
* Process responses from attempting to verify an account for use in account registration.
*/
-sealed class VerifyAccountResponseProcessor(response: ServiceResponse) : ServiceResponseProcessor(response) {
+sealed class VerifyAccountResponseProcessor(
+ response: ServiceResponse
+) : ServiceResponseProcessor(response), VerifyProcessor {
open val tokenData: TokenData? = null
@@ -33,6 +36,10 @@ sealed class VerifyAccountResponseProcessor(response: ServiceResponse,
val token: TokenData
-) : ServiceResponseProcessor(response) {
+) : ServiceResponseProcessor(response), VerifyProcessor {
public override fun rateLimit(): Boolean {
return super.rateLimit()
@@ -45,4 +46,10 @@ class VerifyCodeWithRegistrationLockResponseProcessor(
return VerifyCodeWithRegistrationLockResponseProcessor(ServiceResponse.coerceError(response), token)
}
+
+ override fun isServerSentError(): Boolean {
+ return error is NonSuccessfulResponseCodeException ||
+ error is KeyBackupSystemWrongPinException ||
+ error is KeyBackupSystemNoDataException
+ }
}
diff --git a/app/src/main/java/org/thoughtcrime/securesms/registration/VerifyProcessor.kt b/app/src/main/java/org/thoughtcrime/securesms/registration/VerifyProcessor.kt
new file mode 100644
index 0000000000..ce09ec01cb
--- /dev/null
+++ b/app/src/main/java/org/thoughtcrime/securesms/registration/VerifyProcessor.kt
@@ -0,0 +1,6 @@
+package org.thoughtcrime.securesms.registration
+
+interface VerifyProcessor {
+ fun hasResult(): Boolean
+ fun isServerSentError(): Boolean
+}
diff --git a/app/src/main/res/layout/activity_change_number_lock.xml b/app/src/main/res/layout/activity_change_number_lock.xml
new file mode 100644
index 0000000000..21b02118af
--- /dev/null
+++ b/app/src/main/res/layout/activity_change_number_lock.xml
@@ -0,0 +1,77 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index bbd02b0dd3..7855510ff5 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -3531,6 +3531,16 @@
Update PIN
Keep old pin?
+
+ It looks like you tried to change your number but we were unable to determine if it was successful.\n\nRechecking now…
+ Change status confirmed
+ Your number has been confirmed as %1$s. If this is not your new number, please restart the change number process.
+ Change status unconfirmed
+ We could not determine the status of your change number request.\n\n(Error: %1$s)
+ Retry
+ Leave
+ Submit debug log
+
Keyboard
Enter key sends
diff --git a/libsignal/service/src/main/java/org/whispersystems/signalservice/api/SignalServiceAccountManager.java b/libsignal/service/src/main/java/org/whispersystems/signalservice/api/SignalServiceAccountManager.java
index 2527b1cb1f..ddaceec66c 100644
--- a/libsignal/service/src/main/java/org/whispersystems/signalservice/api/SignalServiceAccountManager.java
+++ b/libsignal/service/src/main/java/org/whispersystems/signalservice/api/SignalServiceAccountManager.java
@@ -65,6 +65,7 @@ import org.whispersystems.signalservice.internal.push.RemoteConfigResponse;
import org.whispersystems.signalservice.internal.push.RequestVerificationCodeResponse;
import org.whispersystems.signalservice.internal.push.SignalServiceProtos;
import org.whispersystems.signalservice.internal.push.VerifyAccountResponse;
+import org.whispersystems.signalservice.internal.push.WhoAmIResponse;
import org.whispersystems.signalservice.internal.push.http.ProfileCipherOutputStreamFactory;
import org.whispersystems.signalservice.internal.storage.protos.ManifestRecord;
import org.whispersystems.signalservice.internal.storage.protos.ReadOperation;
@@ -169,6 +170,10 @@ public class SignalServiceAccountManager {
return this.pushServiceSocket.getOwnUuid();
}
+ public WhoAmIResponse getWhoAmI() throws IOException {
+ return this.pushServiceSocket.getWhoAmI();
+ }
+
public KeyBackupService getKeyBackupService(KeyStore iasKeyStore,
String enclaveName,
byte[] serviceId,
diff --git a/libsignal/service/src/main/java/org/whispersystems/signalservice/internal/push/PushServiceSocket.java b/libsignal/service/src/main/java/org/whispersystems/signalservice/internal/push/PushServiceSocket.java
index bce80d5540..acfcee9dea 100644
--- a/libsignal/service/src/main/java/org/whispersystems/signalservice/internal/push/PushServiceSocket.java
+++ b/libsignal/service/src/main/java/org/whispersystems/signalservice/internal/push/PushServiceSocket.java
@@ -328,6 +328,10 @@ public class PushServiceSocket {
}
}
+ public WhoAmIResponse getWhoAmI() throws IOException {
+ return JsonUtil.fromJson(makeServiceRequest(WHO_AM_I, "GET", null), WhoAmIResponse.class);
+ }
+
public VerifyAccountResponse verifyAccountCode(String verificationCode, String signalingKey, int registrationId, boolean fetchesMessages,
String pin, String registrationLock,
byte[] unidentifiedAccessKey, boolean unrestrictedUnidentifiedAccess,
diff --git a/libsignal/service/src/main/java/org/whispersystems/signalservice/internal/push/WhoAmIResponse.java b/libsignal/service/src/main/java/org/whispersystems/signalservice/internal/push/WhoAmIResponse.java
index 6d50117f7f..8ca14e141e 100644
--- a/libsignal/service/src/main/java/org/whispersystems/signalservice/internal/push/WhoAmIResponse.java
+++ b/libsignal/service/src/main/java/org/whispersystems/signalservice/internal/push/WhoAmIResponse.java
@@ -6,7 +6,14 @@ public class WhoAmIResponse {
@JsonProperty
private String uuid;
+ @JsonProperty
+ private String number;
+
public String getUuid() {
return uuid;
}
+
+ public String getNumber() {
+ return number;
+ }
}