Improve and centralize e164 utils.

This commit is contained in:
Greyson Parrelli
2025-03-03 10:42:21 -05:00
parent 0fdcc1c027
commit 9c473fb570
99 changed files with 748 additions and 1826 deletions

View File

@@ -684,7 +684,7 @@ public final class ContactSelectionListFragment extends LoggingFragment {
boolean isUnknown = contact instanceof ContactSearchKey.UnknownRecipientKey;
SelectedContact selectedContact = contact.requireSelectedContact();
if (!canSelectSelf && !selectedContact.hasUsername() && Recipient.self().getId().equals(selectedContact.getOrCreateRecipientId(requireContext()))) {
if (!canSelectSelf && !selectedContact.hasUsername() && Recipient.self().getId().equals(selectedContact.getOrCreateRecipientId())) {
Toast.makeText(requireContext(), R.string.ContactSelectionListFragment_you_do_not_need_to_add_yourself_to_the_group, Toast.LENGTH_SHORT).show();
return;
}
@@ -840,7 +840,7 @@ public final class ContactSelectionListFragment extends LoggingFragment {
contactChipViewModel.add(selectedContact);
} else {
SimpleTask.run(getViewLifecycleOwner().getLifecycle(),
() -> Recipient.resolved(selectedContact.getOrCreateRecipientId(requireContext())),
() -> Recipient.resolved(selectedContact.getOrCreateRecipientId()),
resolved -> contactChipViewModel.add(selectedContact));
}
}

View File

@@ -237,7 +237,7 @@ public class InviteActivity extends PassphraseRequiredActivity implements Contac
if (context == null) return null;
for (SelectedContact contact : contacts) {
RecipientId recipientId = contact.getOrCreateRecipientId(context);
RecipientId recipientId = contact.getOrCreateRecipientId();
Recipient recipient = Recipient.resolved(recipientId);
MessageSender.send(context, OutgoingMessage.sms(recipient, message), -1L, MessageSender.SendType.SMS, null, null);

View File

@@ -130,7 +130,7 @@ public class NewConversationActivity extends ContactSelectionActivity
AlertDialog progress = SimpleProgressDialog.show(this);
SimpleTask.run(getLifecycle(), () -> RecipientRepository.lookupNewE164(this, number), result -> {
SimpleTask.run(getLifecycle(), () -> RecipientRepository.lookupNewE164(number), result -> {
progress.dismiss();
if (result instanceof RecipientRepository.LookupResult.Success) {

View File

@@ -58,7 +58,7 @@ public class PushContactSelectionActivity extends ContactSelectionActivity {
protected final void onFinishedSelection() {
Intent resultIntent = getIntent();
List<SelectedContact> selectedContacts = contactsFragment.getSelectedContacts();
List<RecipientId> recipients = Stream.of(selectedContacts).map(sc -> sc.getOrCreateRecipientId(this)).toList();
List<RecipientId> recipients = Stream.of(selectedContacts).map(sc -> sc.getOrCreateRecipientId()).toList();
resultIntent.putParcelableArrayListExtra(KEY_SELECTED_RECIPIENTS, new ArrayList<>(recipients));

View File

@@ -47,12 +47,19 @@ public class SmsSendtoActivity extends Activity {
nextIntent.putExtra(Intent.EXTRA_TEXT, destination.getBody());
Toast.makeText(this, R.string.ConversationActivity_specify_recipient, Toast.LENGTH_LONG).show();
} else {
Recipient recipient = Recipient.external(this, destination.getDestination());
long threadId = SignalDatabase.threads().getOrCreateThreadIdFor(recipient);
Recipient recipient = Recipient.external(destination.getDestination());
nextIntent = ConversationIntents.createBuilderSync(this, recipient.getId(), threadId)
.withDraftText(destination.getBody())
.build();
if (recipient != null) {
long threadId = SignalDatabase.threads().getOrCreateThreadIdFor(recipient);
nextIntent = ConversationIntents.createBuilderSync(this, recipient.getId(), threadId)
.withDraftText(destination.getBody())
.build();
} else {
nextIntent = new Intent(this, NewConversationActivity.class);
nextIntent.putExtra(Intent.EXTRA_TEXT, destination.getBody());
Toast.makeText(this, R.string.ConversationActivity_specify_recipient, Toast.LENGTH_LONG).show();
}
}
return nextIntent;
}

View File

@@ -16,11 +16,10 @@ import org.thoughtcrime.securesms.database.RecipientTable
import org.thoughtcrime.securesms.database.SQLiteDatabase
import org.thoughtcrime.securesms.database.SignalDatabase
import org.thoughtcrime.securesms.database.model.databaseprotos.RecipientExtras
import org.thoughtcrime.securesms.dependencies.AppDependencies
import org.thoughtcrime.securesms.phonenumbers.PhoneNumberFormatter
import org.thoughtcrime.securesms.profiles.ProfileName
import org.thoughtcrime.securesms.recipients.Recipient
import org.thoughtcrime.securesms.recipients.RecipientId
import org.thoughtcrime.securesms.util.SignalE164Util
import org.whispersystems.signalservice.api.push.ServiceId.ACI
import org.whispersystems.signalservice.api.push.ServiceId.PNI
@@ -112,6 +111,6 @@ private fun Contact.toLocalExtras(): RecipientExtras {
private val Contact.formattedE164: String?
get() {
return e164?.let {
PhoneNumberFormatter.get(AppDependencies.application).format(e164.toString())
SignalE164Util.formatAsE164(e164.toString())
}
}

View File

@@ -7,7 +7,6 @@ import androidx.core.util.Consumer;
import org.signal.core.util.concurrent.SignalExecutors;
import org.signal.core.util.logging.Log;
import org.thoughtcrime.securesms.database.RecipientTable;
import org.thoughtcrime.securesms.database.SignalDatabase;
import org.thoughtcrime.securesms.database.model.RecipientRecord;
import org.thoughtcrime.securesms.groups.GroupChangeBusyException;
@@ -17,8 +16,6 @@ import org.thoughtcrime.securesms.recipients.RecipientId;
import org.thoughtcrime.securesms.recipients.RecipientUtil;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.stream.Collectors;
@@ -56,7 +53,12 @@ class BlockedUsersRepository {
void createAndBlock(@NonNull String number, @NonNull Runnable success) {
SignalExecutors.BOUNDED.execute(() -> {
RecipientUtil.blockNonGroup(context, Recipient.external(context, number));
Recipient recipient = Recipient.external(number);
if (recipient != null) {
RecipientUtil.blockNonGroup(context, recipient);
} else {
Log.w(TAG, "Failed to create Recipient for number! Invalid input.");
}
success.run();
});
}

View File

@@ -49,7 +49,7 @@ class NewCallActivity : ContactSelectionActivity(), ContactSelectionListFragment
val progress = SimpleProgressDialog.show(this)
SimpleTask.run(lifecycle, { RecipientRepository.lookupNewE164(this, number!!) }, { result ->
SimpleTask.run(lifecycle, { RecipientRepository.lookupNewE164(number!!) }, { result ->
progress.dismiss()
when (result) {

View File

@@ -7,7 +7,6 @@ import android.os.Build;
import android.os.Bundle;
import android.text.Annotation;
import android.text.Editable;
import android.text.InputType;
import android.text.Selection;
import android.text.SpannableString;
import android.text.SpannableStringBuilder;

View File

@@ -121,7 +121,7 @@ public class SharedContactView extends LinearLayout implements RecipientForeverO
presentContact(contact);
presentAvatar(contact.getAvatarAttachment() != null ? contact.getAvatarAttachment().getUri() : null);
presentActionButtons(ContactUtil.getRecipients(getContext(), contact));
presentActionButtons(ContactUtil.getRecipients(contact));
for (LiveRecipient recipient : activeRecipients.values()) {
recipient.observeForever(this);

View File

@@ -6,10 +6,10 @@ import android.graphics.drawable.Drawable;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import org.signal.core.util.StringUtil;
import org.thoughtcrime.securesms.components.emoji.parsing.EmojiParser;
import org.thoughtcrime.securesms.emoji.EmojiSource;
import org.thoughtcrime.securesms.emoji.ObsoleteEmoji;
import org.signal.core.util.StringUtil;
import org.thoughtcrime.securesms.util.Util;
import java.util.HashSet;

View File

@@ -18,11 +18,11 @@ import org.thoughtcrime.securesms.components.settings.app.subscription.StripeRep
import org.thoughtcrime.securesms.help.HelpFragment
import org.thoughtcrime.securesms.keyvalue.SettingsValues
import org.thoughtcrime.securesms.keyvalue.SignalStore
import org.thoughtcrime.securesms.phonenumbers.PhoneNumberFormatter
import org.thoughtcrime.securesms.recipients.Recipient
import org.thoughtcrime.securesms.service.KeyCachingService
import org.thoughtcrime.securesms.util.CachedInflater
import org.thoughtcrime.securesms.util.DynamicTheme
import org.thoughtcrime.securesms.util.SignalE164Util
import org.thoughtcrime.securesms.util.navigation.safeNavigate
private const val START_LOCATION = "app.settings.start.location"
@@ -106,7 +106,7 @@ class AppSettingsActivity : DSLSettingsActivity(), InAppPaymentComponent {
when (intent.getStringExtra(EXTRA_PERFORM_ACTION_ON_CREATE)) {
ACTION_CHANGE_NUMBER_SUCCESS -> {
MaterialAlertDialogBuilder(this)
.setMessage(getString(R.string.ChangeNumber__your_phone_number_has_changed_to_s, PhoneNumberFormatter.prettyPrint(Recipient.self().requireE164())))
.setMessage(getString(R.string.ChangeNumber__your_phone_number_has_changed_to_s, SignalE164Util.prettyPrint(Recipient.self().requireE164())))
.setPositiveButton(R.string.ChangeNumber__okay, null)
.show()
}

View File

@@ -71,10 +71,10 @@ import org.thoughtcrime.securesms.components.settings.app.subscription.completed
import org.thoughtcrime.securesms.compose.ComposeFragment
import org.thoughtcrime.securesms.compose.StatusBarColorNestedScrollConnection
import org.thoughtcrime.securesms.database.model.InAppPaymentSubscriberRecord
import org.thoughtcrime.securesms.phonenumbers.PhoneNumberFormatter
import org.thoughtcrime.securesms.profiles.ProfileName
import org.thoughtcrime.securesms.recipients.Recipient
import org.thoughtcrime.securesms.util.CommunicationActions
import org.thoughtcrime.securesms.util.SignalE164Util
import org.thoughtcrime.securesms.util.Util
import org.thoughtcrime.securesms.util.navigation.safeNavigate
@@ -575,7 +575,7 @@ private fun BioRow(
self.e164
} else {
remember(self.e164) {
PhoneNumberFormatter.prettyPrint(self.e164)
SignalE164Util.prettyPrint(self.e164)
}
}

View File

@@ -17,9 +17,9 @@ 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.SignalE164Util
/**
* A captive activity that can determine if an interrupted/erred change number request
@@ -72,7 +72,7 @@ class ChangeNumberLockActivity : PassphraseRequiredActivity() {
MaterialAlertDialogBuilder(this)
.setTitle(R.string.ChangeNumberLockActivity__change_status_confirmed)
.setMessage(getString(R.string.ChangeNumberLockActivity__your_number_has_been_confirmed_as_s, PhoneNumberFormatter.prettyPrint(SignalStore.account.e164!!)))
.setMessage(getString(R.string.ChangeNumberLockActivity__your_number_has_been_confirmed_as_s, SignalE164Util.prettyPrint(SignalStore.account.e164!!)))
.setPositiveButton(android.R.string.ok) { _, _ ->
startActivity(MainActivity.clearTop(this))
finish()

View File

@@ -18,8 +18,6 @@ import org.thoughtcrime.securesms.components.settings.DSLConfiguration
import org.thoughtcrime.securesms.components.settings.DSLSettingsFragment
import org.thoughtcrime.securesms.components.settings.DSLSettingsText
import org.thoughtcrime.securesms.components.settings.configure
import org.thoughtcrime.securesms.keyvalue.SignalStore
import org.thoughtcrime.securesms.phonenumbers.PhoneNumberFormatter
import org.thoughtcrime.securesms.util.CommunicationActions
import org.thoughtcrime.securesms.util.SpanUtil
import org.thoughtcrime.securesms.util.ViewUtil
@@ -162,14 +160,6 @@ class AdvancedPrivacySettingsFragment : DSLSettingsFragment(R.string.preferences
}
}
private fun getPushToggleSummary(isPushEnabled: Boolean): String {
return if (isPushEnabled) {
PhoneNumberFormatter.prettyPrint(SignalStore.account.e164!!)
} else {
getString(R.string.preferences__free_private_messages_and_calls)
}
}
@Suppress("DEPRECATION")
private fun registerNetworkReceiver() {
val context: Context? = context

View File

@@ -12,7 +12,7 @@ import org.thoughtcrime.securesms.jobs.RefreshAttributesJob
import org.thoughtcrime.securesms.jobs.RefreshOwnProfileJob
import org.thoughtcrime.securesms.keyvalue.SettingsValues
import org.thoughtcrime.securesms.keyvalue.SignalStore
import org.thoughtcrime.securesms.phonenumbers.PhoneNumberFormatter
import org.thoughtcrime.securesms.util.SignalE164Util
import org.thoughtcrime.securesms.util.SingleLiveEvent
import org.thoughtcrime.securesms.util.TextSecurePreferences
import org.thoughtcrime.securesms.util.livedata.Store
@@ -89,7 +89,7 @@ class AdvancedPrivacySettingsViewModel(
}
private fun getCensorshipCircumventionState(): CensorshipCircumventionState {
val countryCode: Int = PhoneNumberFormatter.getLocalCountryCode()
val countryCode: Int = SignalE164Util.getLocalCountryCode()
val isCountryCodeCensoredByDefault: Boolean = AppDependencies.signalServiceNetworkAccess.isCountryCodeCensoredByDefault(countryCode)
val enabledState: SettingsValues.CensorshipCircumventionEnabled = SignalStore.settings.censorshipCircumventionEnabled
val hasInternet: Boolean = NetworkConstraint.isMet(AppDependencies.application)

View File

@@ -9,7 +9,6 @@ import io.reactivex.rxjava3.disposables.CompositeDisposable
import io.reactivex.rxjava3.disposables.Disposable
import io.reactivex.rxjava3.kotlin.plusAssign
import io.reactivex.rxjava3.schedulers.Schedulers
import org.thoughtcrime.securesms.dependencies.AppDependencies
import org.thoughtcrime.securesms.recipients.Recipient
import org.thoughtcrime.securesms.recipients.RecipientId
import org.thoughtcrime.securesms.util.rx.RxStore
@@ -74,7 +73,7 @@ class ContactChipViewModel : ViewModel() {
private fun getOrCreateRecipientId(selectedContact: SelectedContact): Single<RecipientId> {
return Single.fromCallable {
selectedContact.getOrCreateRecipientId(AppDependencies.application)
selectedContact.getOrCreateRecipientId()
}
}
}

View File

@@ -12,7 +12,7 @@ import org.signal.libsignal.protocol.util.Pair;
import org.thoughtcrime.securesms.contacts.paged.ContactSearchSortOrder;
import org.thoughtcrime.securesms.database.RecipientTable;
import org.thoughtcrime.securesms.database.SignalDatabase;
import org.thoughtcrime.securesms.phonenumbers.PhoneNumberFormatter;
import org.thoughtcrime.securesms.util.SignalE164Util;
import org.thoughtcrime.securesms.util.Util;
import java.util.ArrayList;
@@ -59,7 +59,7 @@ public class ContactRepository {
String email = CursorUtil.requireString(cursor, RecipientTable.EMAIL);
if (phone != null) {
phone = PhoneNumberFormatter.prettyPrint(phone);
phone = SignalE164Util.prettyPrint(phone);
}
return Util.getFirstNonEmpty(phone, email);

View File

@@ -16,9 +16,9 @@ import org.thoughtcrime.securesms.database.SignalDatabase;
import org.thoughtcrime.securesms.dependencies.AppDependencies;
import org.thoughtcrime.securesms.jobs.DirectoryRefreshJob;
import org.thoughtcrime.securesms.keyvalue.SignalStore;
import org.thoughtcrime.securesms.phonenumbers.PhoneNumberFormatter;
import org.thoughtcrime.securesms.recipients.Recipient;
import org.signal.core.util.SetUtil;
import org.thoughtcrime.securesms.util.SignalE164Util;
import java.io.IOException;
import java.util.List;
@@ -56,7 +56,8 @@ public class ContactsSyncAdapter extends AbstractThreadedSyncAdapter {
Set<String> allSystemE164s = SystemContactsRepository.getAllDisplayNumbers(context)
.stream()
.map(number -> PhoneNumberFormatter.get(context).format(number))
.map(number -> SignalE164Util.formatAsE164(number))
.filter(it -> it != null)
.collect(Collectors.toSet());
Set<String> knownSystemE164s = SignalDatabase.recipients().getAllE164s();
Set<String> unknownSystemE164s = SetUtil.difference(allSystemE164s, knownSystemE164s);
@@ -71,7 +72,8 @@ public class ContactsSyncAdapter extends AbstractThreadedSyncAdapter {
} else if (unknownSystemE164s.size() > 0) {
List<Recipient> recipients = Stream.of(unknownSystemE164s)
.filter(s -> s.startsWith("+"))
.map(s -> Recipient.external(getContext(), s))
.map(s -> Recipient.external(s))
.filter(it -> it != null)
.toList();
Log.i(TAG, "There are " + unknownSystemE164s.size() + " unknown E164s, which are now " + recipients.size() + " recipients. Only syncing these specific contacts.");

View File

@@ -1,7 +1,5 @@
package org.thoughtcrime.securesms.contacts;
import android.content.Context;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
@@ -45,11 +43,16 @@ public final class SelectedContact {
this.chatType = chatType;
}
public @NonNull RecipientId getOrCreateRecipientId(@NonNull Context context) {
public @NonNull RecipientId getOrCreateRecipientId() {
if (recipientId != null) {
return recipientId;
} else if (number != null) {
return Recipient.external(context, number).getId();
Recipient recipient = Recipient.external(number);
if (recipient != null) {
return recipient.getId();
} else {
throw new AssertionError("Invalid phone number provided!");
}
} else {
throw new AssertionError();
}

View File

@@ -7,6 +7,7 @@ import androidx.annotation.WorkerThread
import org.signal.contacts.SystemContactsRepository
import org.signal.contacts.SystemContactsRepository.ContactIterator
import org.signal.contacts.SystemContactsRepository.ContactPhoneDetails
import org.signal.core.util.E164Util
import org.signal.core.util.Stopwatch
import org.signal.core.util.StringUtil
import org.signal.core.util.logging.Log
@@ -19,7 +20,6 @@ import org.thoughtcrime.securesms.mms.IncomingMessage
import org.thoughtcrime.securesms.notifications.NotificationChannels
import org.thoughtcrime.securesms.notifications.v2.ConversationId
import org.thoughtcrime.securesms.permissions.Permissions
import org.thoughtcrime.securesms.phonenumbers.PhoneNumberFormatter
import org.thoughtcrime.securesms.profiles.ProfileName
import org.thoughtcrime.securesms.recipients.Recipient
import org.thoughtcrime.securesms.recipients.RecipientId
@@ -141,8 +141,9 @@ object ContactDiscovery {
)
}
private fun phoneNumberFormatter(context: Context): (String) -> String {
return { PhoneNumberFormatter.get(context).format(it) }
private fun phoneNumberFormatter(): (String) -> String? {
val formatter = E164Util.createFormatterForE164(SignalStore.account.e164!!)
return { formatter.formatAsE164(it) }
}
private fun refreshRecipients(
@@ -171,13 +172,13 @@ object ContactDiscovery {
contactsProvider = {
if (useFullSync) {
Log.d(TAG, "Doing a full system contact sync. There are ${result.registeredIds.size} contacts to get info for.")
SystemContactsRepository.getAllSystemContacts(context, phoneNumberFormatter(context))
SystemContactsRepository.getAllSystemContacts(context, phoneNumberFormatter())
} else {
Log.d(TAG, "Doing a partial system contact sync. There are ${result.registeredIds.size} contacts to get info for.")
SystemContactsRepository.getContactDetailsByQueries(
context = context,
queries = Recipient.resolvedList(result.registeredIds).mapNotNull { it.e164.orElse(null) },
e164Formatter = phoneNumberFormatter(context)
e164Formatter = phoneNumberFormatter()
)
}
},
@@ -235,7 +236,7 @@ object ContactDiscovery {
private fun syncRecipientsWithSystemContacts(
context: Context,
rewrites: Map<String, String>,
contactsProvider: () -> ContactIterator = { SystemContactsRepository.getAllSystemContacts(context, phoneNumberFormatter(context)) },
contactsProvider: () -> ContactIterator = { SystemContactsRepository.getAllSystemContacts(context, phoneNumberFormatter()) },
clearInfoForMissingContacts: Boolean
) {
val localNumber: String = SignalStore.account.e164 ?: ""
@@ -259,8 +260,10 @@ object ContactDiscovery {
ProfileName.EMPTY
}
val recipient: Recipient = Recipient.externalContact(realNumber) ?: continue
handle.setSystemContactInfo(
Recipient.externalContact(realNumber).id,
recipient.id,
profileName,
phoneDetails.displayName,
phoneDetails.photoUri,

View File

@@ -11,10 +11,10 @@ import org.thoughtcrime.securesms.database.RecipientTable.CdsV2Result
import org.thoughtcrime.securesms.database.SignalDatabase
import org.thoughtcrime.securesms.dependencies.AppDependencies
import org.thoughtcrime.securesms.keyvalue.SignalStore
import org.thoughtcrime.securesms.phonenumbers.PhoneNumberFormatter
import org.thoughtcrime.securesms.recipients.Recipient
import org.thoughtcrime.securesms.recipients.RecipientId
import org.thoughtcrime.securesms.util.RemoteConfig
import org.thoughtcrime.securesms.util.SignalE164Util
import org.whispersystems.signalservice.api.push.exceptions.CdsiInvalidTokenException
import org.whispersystems.signalservice.api.push.exceptions.CdsiResourceExhaustedException
import org.whispersystems.signalservice.api.services.CdsiV2Service
@@ -45,7 +45,7 @@ object ContactDiscoveryRefreshV2 {
@JvmStatic
fun refreshAll(context: Context, timeoutMs: Long? = null): ContactDiscovery.RefreshResult {
val recipientE164s: Set<String> = SignalDatabase.recipients.getAllE164s().sanitize()
val systemE164s: Set<String> = SystemContactsRepository.getAllDisplayNumbers(context).toE164s(context).sanitize()
val systemE164s: Set<String> = SystemContactsRepository.getAllDisplayNumbers(context).toE164s().sanitize()
return refreshInternal(
recipientE164s = recipientE164s,
@@ -246,8 +246,8 @@ object ContactDiscoveryRefreshV2 {
.toSet()
}
private fun Set<String>.toE164s(context: Context): Set<String> {
return this.map { PhoneNumberFormatter.get(context).format(it) }.toSet()
private fun Set<String>.toE164s(): Set<String> {
return this.mapNotNull { SignalE164Util.formatAsE164(it) }.toSet()
}
private fun Set<String>.sanitize(): Set<String> {

View File

@@ -27,10 +27,10 @@ import org.thoughtcrime.securesms.contactshare.Contact.Phone;
import org.thoughtcrime.securesms.contactshare.Contact.PostalAddress;
import org.thoughtcrime.securesms.database.SignalDatabase;
import org.thoughtcrime.securesms.mms.PartAuthority;
import org.thoughtcrime.securesms.phonenumbers.PhoneNumberFormatter;
import org.thoughtcrime.securesms.profiles.ProfileName;
import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.recipients.RecipientId;
import org.thoughtcrime.securesms.util.SignalE164Util;
import org.thoughtcrime.securesms.util.SpanUtil;
import java.io.IOException;
@@ -120,8 +120,8 @@ public final class ContactUtil {
}
}
public static @NonNull String getNormalizedPhoneNumber(@NonNull Context context, @Nullable String number) {
return PhoneNumberFormatter.get(context).format(number);
public static @Nullable String getNormalizedPhoneNumber(@Nullable String number) {
return SignalE164Util.formatAsE164(number != null ? number : "");
}
@MainThread
@@ -142,11 +142,11 @@ public final class ContactUtil {
}
}
public static List<RecipientId> getRecipients(@NonNull Context context, @NonNull Contact contact) {
public static List<RecipientId> getRecipients(@NonNull Contact contact) {
return contact
.getPhoneNumbers()
.stream()
.map(phone -> PhoneNumberFormatter.get(context).formatOrNull(phone.getNumber()))
.map(phone -> SignalE164Util.formatAsE164(phone.getNumber()))
.filter(number -> number != null)
.map(phone -> SignalDatabase.recipients().getOrInsertFromE164(phone))
.collect(Collectors.toList());

View File

@@ -97,7 +97,7 @@ public class SharedContactDetailsActivity extends PassphraseRequiredActivity {
initViews();
presentContact(contact);
presentActionButtons(ContactUtil.getRecipients(this, contact));
presentActionButtons(ContactUtil.getRecipients(contact));
presentAvatar(contact.getAvatarAttachment() != null ? contact.getAvatarAttachment().getUri() : null);
for (LiveRecipient recipient : activeRecipients.values()) {

View File

@@ -19,9 +19,9 @@ import org.thoughtcrime.securesms.contactshare.Contact.Name;
import org.thoughtcrime.securesms.contactshare.Contact.Phone;
import org.thoughtcrime.securesms.contactshare.Contact.PostalAddress;
import org.thoughtcrime.securesms.mms.PartAuthority;
import org.thoughtcrime.securesms.phonenumbers.PhoneNumberFormatter;
import org.thoughtcrime.securesms.providers.BlobProvider;
import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.util.SignalE164Util;
import java.io.IOException;
import java.io.InputStream;
@@ -130,7 +130,11 @@ public class SharedContactRepository {
List<PhoneDetails> phoneDetails = SystemContactsRepository.getPhoneDetails(context, contactId);
for (PhoneDetails phone : phoneDetails) {
String number = ContactUtil.getNormalizedPhoneNumber(context, phone.getNumber());
String number = ContactUtil.getNormalizedPhoneNumber(phone.getNumber());
if (number == null) {
continue;
}
Phone existing = numberMap.get(number);
Phone candidate = new Phone(number, VCardUtil.phoneTypeFromContactType(phone.getType()), phone.getLabel());
@@ -182,7 +186,12 @@ public class SharedContactRepository {
}
for (Phone phoneNumber : phoneNumbers) {
AvatarInfo recipientAvatar = getRecipientAvatarInfo(PhoneNumberFormatter.get(context).format(phoneNumber.getNumber()));
String formattedNumber = SignalE164Util.formatAsE164(phoneNumber.getNumber());
if (formattedNumber == null) {
continue;
}
AvatarInfo recipientAvatar = getRecipientAvatarInfo(formattedNumber);
if (recipientAvatar != null) {
return recipientAvatar;
}
@@ -202,7 +211,11 @@ public class SharedContactRepository {
@WorkerThread
private @Nullable AvatarInfo getRecipientAvatarInfo(String address) {
Recipient recipient = Recipient.external(context, address);
Recipient recipient = Recipient.external(address);
if (recipient == null) {
return null;
}
ContactPhoto contactPhoto = recipient.getContactPhoto();
if (contactPhoto != null) {

View File

@@ -57,7 +57,6 @@ import androidx.annotation.DimenRes;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.core.content.ContextCompat;
import androidx.core.graphics.ColorUtils;
import androidx.lifecycle.LifecycleOwner;
import androidx.media3.common.MediaItem;
import androidx.recyclerview.widget.RecyclerView;
@@ -82,7 +81,6 @@ import org.thoughtcrime.securesms.attachments.DatabaseAttachment;
import org.thoughtcrime.securesms.badges.BadgeImageView;
import org.thoughtcrime.securesms.badges.gifts.GiftMessageView;
import org.thoughtcrime.securesms.badges.gifts.OpenableGift;
import org.thoughtcrime.securesms.billing.upgrade.UpgradeToStartMediaBackupSheet;
import org.thoughtcrime.securesms.calls.links.CallLinks;
import org.thoughtcrime.securesms.components.AlertView;
import org.thoughtcrime.securesms.components.AudioView;
@@ -146,7 +144,6 @@ import org.thoughtcrime.securesms.util.MessageRecordUtil;
import org.thoughtcrime.securesms.util.PlaceholderURLSpan;
import org.thoughtcrime.securesms.util.Projection;
import org.thoughtcrime.securesms.util.ProjectionList;
import org.thoughtcrime.securesms.util.RemoteConfig;
import org.thoughtcrime.securesms.util.SearchUtil;
import org.thoughtcrime.securesms.util.ThemeUtil;
import org.thoughtcrime.securesms.util.UrlClickHandler;

View File

@@ -55,12 +55,12 @@ import org.thoughtcrime.securesms.databinding.V2ConversationItemTextOnlyOutgoing
import org.thoughtcrime.securesms.giph.mp4.GiphyMp4PlaybackPolicyEnforcer
import org.thoughtcrime.securesms.groups.v2.GroupDescriptionUtil
import org.thoughtcrime.securesms.keyvalue.SignalStore
import org.thoughtcrime.securesms.phonenumbers.PhoneNumberFormatter
import org.thoughtcrime.securesms.recipients.Recipient
import org.thoughtcrime.securesms.recipients.ui.about.AboutSheet
import org.thoughtcrime.securesms.util.CachedInflater
import org.thoughtcrime.securesms.util.Projection
import org.thoughtcrime.securesms.util.ProjectionList
import org.thoughtcrime.securesms.util.SignalE164Util
import org.thoughtcrime.securesms.util.adapter.mapping.MappingViewHolder
import org.thoughtcrime.securesms.util.adapter.mapping.PagingMappingAdapter
import java.util.Locale
@@ -576,7 +576,7 @@ class ConversationAdapterV2(
conversationBanner.hideUnverifiedNameSubtitle()
}
val subtitle: String? = recipient.takeIf { it.shouldShowE164 }?.e164?.map { e164: String? -> PhoneNumberFormatter.prettyPrint(e164!!) }?.orElse(null)
val subtitle: String? = recipient.takeIf { it.shouldShowE164 }?.e164?.map { e164: String? -> SignalE164Util.prettyPrint(e164!!) }?.orElse(null)
if (subtitle == null || subtitle == title) {
conversationBanner.hideSubtitle()
} else {

View File

@@ -74,7 +74,6 @@ import org.thoughtcrime.securesms.database.model.ThreadRecord;
import org.thoughtcrime.securesms.database.model.UpdateDescription;
import org.thoughtcrime.securesms.glide.GlideLiveDataTarget;
import org.thoughtcrime.securesms.mms.DecryptableStreamUriLoader;
import org.thoughtcrime.securesms.phonenumbers.PhoneNumberFormatter;
import org.thoughtcrime.securesms.recipients.LiveRecipient;
import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.recipients.RecipientId;
@@ -84,6 +83,7 @@ import org.thoughtcrime.securesms.util.DateUtils;
import org.thoughtcrime.securesms.util.ExpirationUtil;
import org.thoughtcrime.securesms.util.MediaUtil;
import org.thoughtcrime.securesms.util.SearchUtil;
import org.thoughtcrime.securesms.util.SignalE164Util;
import org.thoughtcrime.securesms.util.SpanUtil;
import org.thoughtcrime.securesms.util.Util;
import org.thoughtcrime.securesms.util.livedata.LiveDataUtil;
@@ -660,7 +660,7 @@ public final class ConversationListItem extends ConstraintLayout implements Bind
return emphasisAdded(context, context.getString(R.string.ThreadRecord_message_history_has_been_merged), defaultTint);
} else if (MessageTypes.isSessionSwitchoverType(thread.getType())) {
if (thread.getRecipient().getE164().isPresent()) {
return emphasisAdded(context, context.getString(R.string.ThreadRecord_s_belongs_to_s, PhoneNumberFormatter.prettyPrint(thread.getRecipient().requireE164()), thread.getRecipient().getDisplayName(context)), defaultTint);
return emphasisAdded(context, context.getString(R.string.ThreadRecord_s_belongs_to_s, SignalE164Util.prettyPrint(thread.getRecipient().requireE164()), thread.getRecipient().getDisplayName(context)), defaultTint);
} else {
return emphasisAdded(context, context.getString(R.string.ThreadRecord_safety_number_changed), defaultTint);
}

View File

@@ -16,7 +16,6 @@ import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import org.signal.core.util.logging.Log;
import org.signal.libsignal.protocol.IdentityKey;
import org.signal.libsignal.protocol.InvalidKeyException;
import org.thoughtcrime.securesms.dependencies.AppDependencies;
import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.recipients.RecipientId;
import org.signal.core.util.Base64;
@@ -53,7 +52,12 @@ public class IdentityKeyMismatch {
if (!TextUtils.isEmpty(recipientId)) {
return RecipientId.from(recipientId);
} else {
return Recipient.external(AppDependencies.getApplication(), address).getId();
Recipient recipient = Recipient.external(address);
if (recipient != null) {
return recipient.getId();
} else {
return RecipientId.UNKNOWN;
}
}
}

View File

@@ -7,7 +7,6 @@ import androidx.annotation.NonNull;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonProperty;
import org.thoughtcrime.securesms.dependencies.AppDependencies;
import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.recipients.RecipientId;
@@ -34,7 +33,12 @@ public class NetworkFailure {
if (!TextUtils.isEmpty(recipientId)) {
return RecipientId.from(recipientId);
} else {
return Recipient.external(AppDependencies.getApplication(), address).getId();
Recipient recipient = Recipient.external(address);
if (recipient != null) {
return recipient.getId();
} else {
return RecipientId.UNKNOWN;
}
}
}

View File

@@ -39,11 +39,11 @@ import org.thoughtcrime.securesms.groups.GroupId
import org.thoughtcrime.securesms.jobs.PreKeysSyncJob
import org.thoughtcrime.securesms.keyvalue.SignalStore
import org.thoughtcrime.securesms.notifications.NotificationChannels
import org.thoughtcrime.securesms.phonenumbers.PhoneNumberFormatter
import org.thoughtcrime.securesms.profiles.ProfileName
import org.thoughtcrime.securesms.storage.StorageSyncHelper
import org.thoughtcrime.securesms.util.FileUtils
import org.thoughtcrime.securesms.util.ServiceUtil
import org.thoughtcrime.securesms.util.SignalE164Util
import org.thoughtcrime.securesms.util.Triple
import org.thoughtcrime.securesms.util.Util
import org.whispersystems.signalservice.api.push.DistributionId
@@ -379,7 +379,7 @@ object V149_LegacyMigrations : SignalDatabaseMigration {
db.rawQuery("SELECT recipient_ids, system_display_name, signal_profile_name, notification, vibrate FROM recipient_preferences WHERE notification NOT NULL OR vibrate != 0", null).use { cursor ->
while (cursor != null && cursor.moveToNext()) {
val rawAddress: String = cursor.getString(cursor.getColumnIndexOrThrow("recipient_ids"))
val address: String = PhoneNumberFormatter.get(context).format(rawAddress)
val address: String = SignalE164Util.formatAsE164(rawAddress) ?: ""
val systemName: String = cursor.getString(cursor.getColumnIndexOrThrow("system_display_name"))
val profileName: String = cursor.getString(cursor.getColumnIndexOrThrow("signal_profile_name"))
val messageSound: String? = cursor.getString(cursor.getColumnIndexOrThrow("notification"))

View File

@@ -1,58 +0,0 @@
package org.thoughtcrime.securesms.database.loaders;
import android.content.Context;
import androidx.loader.content.AsyncTaskLoader;
import com.google.i18n.phonenumbers.PhoneNumberUtil;
import org.whispersystems.signalservice.api.util.PhoneNumberFormatter;
import java.text.Collator;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
public final class CountryListLoader extends AsyncTaskLoader<ArrayList<Map<String, String>>> {
public CountryListLoader(Context context) {
super(context);
}
@Override
public ArrayList<Map<String, String>> loadInBackground() {
Set<String> regions = PhoneNumberUtil.getInstance().getSupportedRegions();
ArrayList<Map<String, String>> results = new ArrayList<>(regions.size());
for (String region : regions) {
Map<String, String> data = new HashMap<>(2);
data.put("country_name", PhoneNumberFormatter.getRegionDisplayNameLegacy(region));
data.put("country_code", "+" +PhoneNumberUtil.getInstance().getCountryCodeForRegion(region));
results.add(data);
}
Collections.sort(results, new RegionComparator());
return results;
}
private static class RegionComparator implements Comparator<Map<String, String>> {
private final Collator collator;
RegionComparator() {
collator = Collator.getInstance();
collator.setStrength(Collator.PRIMARY);
}
@Override
public int compare(Map<String, String> lhs, Map<String, String> rhs) {
String a = lhs.get("country_name");
String b = rhs.get("country_name");
return collator.compare(a, b);
}
}
}

View File

@@ -13,7 +13,6 @@ import androidx.annotation.VisibleForTesting;
import androidx.core.content.ContextCompat;
import org.signal.core.util.BidiUtil;
import org.signal.core.util.StringUtil;
import org.signal.storageservice.protos.groups.AccessControl;
import org.signal.storageservice.protos.groups.Member;
import org.signal.storageservice.protos.groups.local.DecryptedApproveMember;
@@ -61,22 +60,16 @@ import org.thoughtcrime.securesms.backup.v2.proto.GroupV2MigrationSelfInvitedUpd
import org.thoughtcrime.securesms.backup.v2.proto.GroupV2MigrationUpdate;
import org.thoughtcrime.securesms.backup.v2.proto.SelfInvitedOtherUserToGroupUpdate;
import org.thoughtcrime.securesms.backup.v2.proto.SelfInvitedToGroupUpdate;
import org.thoughtcrime.securesms.database.model.databaseprotos.DecryptedGroupV2Context;
import org.thoughtcrime.securesms.database.model.databaseprotos.GV2UpdateDescription;
import org.thoughtcrime.securesms.groups.GV2AccessLevelUtil;
import org.thoughtcrime.securesms.groups.GroupMigrationMembershipChange;
import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.recipients.RecipientId;
import org.thoughtcrime.securesms.util.ExpirationUtil;
import org.thoughtcrime.securesms.util.SpanUtil;
import org.thoughtcrime.securesms.util.Util;
import org.whispersystems.signalservice.api.groupsv2.DecryptedGroupUtil;
import org.whispersystems.signalservice.api.push.ServiceId;
import org.whispersystems.signalservice.api.push.ServiceId.ACI;
import org.whispersystems.signalservice.api.push.ServiceIds;
import org.whispersystems.signalservice.api.util.UuidUtil;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;

View File

@@ -35,7 +35,6 @@ import com.annimon.stream.Stream;
import org.signal.core.util.Base64;
import org.signal.core.util.BidiUtil;
import org.signal.core.util.StringUtil;
import org.signal.core.util.logging.Log;
import org.signal.storageservice.protos.groups.local.DecryptedGroup;
import org.signal.storageservice.protos.groups.local.DecryptedGroupChange;
@@ -56,13 +55,13 @@ import org.thoughtcrime.securesms.emoji.EmojiSource;
import org.thoughtcrime.securesms.emoji.JumboEmoji;
import org.thoughtcrime.securesms.groups.GroupMigrationMembershipChange;
import org.thoughtcrime.securesms.keyvalue.SignalStore;
import org.thoughtcrime.securesms.phonenumbers.PhoneNumberFormatter;
import org.thoughtcrime.securesms.profiles.ProfileName;
import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.recipients.RecipientId;
import org.thoughtcrime.securesms.util.DateUtils;
import org.thoughtcrime.securesms.util.ExpirationUtil;
import org.thoughtcrime.securesms.util.GroupUtil;
import org.thoughtcrime.securesms.util.SignalE164Util;
import org.thoughtcrime.securesms.util.Util;
import org.whispersystems.signalservice.api.groupsv2.DecryptedGroupUtil;
import org.whispersystems.signalservice.api.push.ServiceId;
@@ -254,7 +253,7 @@ public abstract class MessageRecord extends DisplayRecord {
if (event.previousE164.isEmpty()) {
return fromRecipient(getFromRecipient(), r -> context.getString(R.string.MessageRecord_your_message_history_with_s_and_another_chat_has_been_merged, r.getDisplayName(context)), R.drawable.ic_thread_merge_16);
} else {
return fromRecipient(getFromRecipient(), r -> context.getString(R.string.MessageRecord_your_message_history_with_s_and_their_number_s_has_been_merged, r.getDisplayName(context), PhoneNumberFormatter.prettyPrint(event.previousE164)), R.drawable.ic_thread_merge_16);
return fromRecipient(getFromRecipient(), r -> context.getString(R.string.MessageRecord_your_message_history_with_s_and_their_number_s_has_been_merged, r.getDisplayName(context), SignalE164Util.prettyPrint(event.previousE164)), R.drawable.ic_thread_merge_16);
}
} catch (IOException e) {
throw new AssertionError(e);
@@ -266,7 +265,7 @@ public abstract class MessageRecord extends DisplayRecord {
if (event.e164.isEmpty()) {
return fromRecipient(getFromRecipient(), r -> context.getString(R.string.MessageRecord_your_safety_number_with_s_has_changed, r.getDisplayName(context)), R.drawable.ic_update_safety_number_16);
} else {
return fromRecipient(getFromRecipient(), r -> context.getString(R.string.MessageRecord_s_belongs_to_s, PhoneNumberFormatter.prettyPrint(event.e164), r.getDisplayName(context)), R.drawable.ic_update_info_16);
return fromRecipient(getFromRecipient(), r -> context.getString(R.string.MessageRecord_s_belongs_to_s, SignalE164Util.prettyPrint(event.e164), r.getDisplayName(context)), R.drawable.ic_update_info_16);
}
} catch (IOException e) {
throw new AssertionError(e);
@@ -487,7 +486,7 @@ public abstract class MessageRecord extends DisplayRecord {
} else if (profileChangeDetails.learnedProfileName != null) {
String previouslyKnownAs;
if (!Util.isEmpty(profileChangeDetails.learnedProfileName.e164)) {
previouslyKnownAs = PhoneNumberFormatter.prettyPrint(profileChangeDetails.learnedProfileName.e164);
previouslyKnownAs = SignalE164Util.prettyPrint(profileChangeDetails.learnedProfileName.e164);
} else {
previouslyKnownAs = profileChangeDetails.learnedProfileName.username;
}

View File

@@ -6,6 +6,7 @@ import androidx.core.util.Consumer;
import com.annimon.stream.Stream;
import com.google.i18n.phonenumbers.PhoneNumberUtil;
import org.signal.core.util.E164Util;
import org.signal.core.util.concurrent.SignalExecutors;
import org.signal.core.util.logging.Log;
import org.thoughtcrime.securesms.components.settings.app.subscription.InAppPaymentsRepository;
@@ -16,7 +17,6 @@ import org.thoughtcrime.securesms.database.model.InAppPaymentSubscriberRecord;
import org.thoughtcrime.securesms.dependencies.AppDependencies;
import org.thoughtcrime.securesms.groups.GroupManager;
import org.thoughtcrime.securesms.util.ServiceUtil;
import org.whispersystems.signalservice.api.util.PhoneNumberFormatter;
import org.whispersystems.signalservice.internal.EmptyResponse;
import org.whispersystems.signalservice.internal.ServiceResponse;
@@ -36,7 +36,7 @@ class DeleteAccountRepository {
}
@NonNull String getRegionDisplayName(@NonNull String region) {
return PhoneNumberFormatter.getRegionDisplayName(region).orElse("");
return E164Util.getRegionDisplayName(region).orElse("");
}
int getRegionCountryCode(@NonNull String region) {
@@ -121,7 +121,7 @@ class DeleteAccountRepository {
}
private static @NonNull Country getCountryForRegion(@NonNull String region) {
return new Country(PhoneNumberFormatter.getRegionDisplayName(region).orElse(""),
return new Country(E164Util.getRegionDisplayName(region).orElse(""),
PhoneNumberUtil.getInstance().getCountryCodeForRegion(region),
region);
}

View File

@@ -116,7 +116,7 @@ public class AddMembersActivity extends PushContactSelectionActivity implements
AlertDialog progress = SimpleProgressDialog.show(this);
SimpleTask.run(getLifecycle(), () -> RecipientRepository.lookupNewE164(this, number), result -> {
SimpleTask.run(getLifecycle(), () -> RecipientRepository.lookupNewE164(number), result -> {
progress.dismiss();
if (result instanceof RecipientRepository.LookupResult.Success) {

View File

@@ -23,7 +23,7 @@ final class AddMembersRepository {
@WorkerThread
RecipientId getOrCreateRecipientId(@NonNull SelectedContact selectedContact) {
return selectedContact.getOrCreateRecipientId(context);
return selectedContact.getOrCreateRecipientId();
}
@WorkerThread

View File

@@ -159,7 +159,7 @@ public final class AddToGroupsActivity extends ContactSelectionActivity {
private void handleNextPressed() {
List<RecipientId> groupsRecipientIds = Stream.of(contactsFragment.getSelectedContacts())
.map(selectedContact -> selectedContact.getOrCreateRecipientId(this))
.map(selectedContact -> selectedContact.getOrCreateRecipientId())
.toList();
viewModel.onContinueWithSelection(groupsRecipientIds);

View File

@@ -124,7 +124,7 @@ public class CreateGroupActivity extends ContactSelectionActivity implements Con
AlertDialog progress = SimpleProgressDialog.show(this);
SimpleTask.run(getLifecycle(), () -> RecipientRepository.lookupNewE164(this, number), result -> {
SimpleTask.run(getLifecycle(), () -> RecipientRepository.lookupNewE164(number), result -> {
progress.dismiss();
if (result instanceof RecipientRepository.LookupResult.Success) {
@@ -194,7 +194,7 @@ public class CreateGroupActivity extends ContactSelectionActivity implements Con
SimpleTask.run(getLifecycle(), () -> {
List<RecipientId> ids = contactsFragment.getSelectedContacts()
.stream()
.map(selectedContact -> selectedContact.getOrCreateRecipientId(this))
.map(selectedContact -> selectedContact.getOrCreateRecipientId())
.collect(Collectors.toList());
List<Recipient> resolved = Recipient.resolvedList(ids);

View File

@@ -0,0 +1,15 @@
/*
* Copyright 2025 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.thoughtcrime.securesms.jobmanager.migrations
import org.thoughtcrime.securesms.jobmanager.JobMigration
/**
* Used as a replacement for another JobMigration that is no longer necessary.
*/
class DeprecatedJobMigration(version: Int) : JobMigration(version) {
override fun migrate(jobData: JobData): JobData = jobData
}

View File

@@ -1,90 +0,0 @@
package org.thoughtcrime.securesms.jobmanager.migrations;
import android.content.Context;
import androidx.annotation.NonNull;
import org.signal.core.util.logging.Log;
import org.signal.libsignal.zkgroup.InvalidInputException;
import org.signal.libsignal.zkgroup.groups.GroupMasterKey;
import org.thoughtcrime.securesms.groups.GroupId;
import org.thoughtcrime.securesms.jobmanager.JobMigration;
import org.thoughtcrime.securesms.jobmanager.JsonJobData;
import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.recipients.RecipientId;
import org.signal.core.util.Base64;
import org.whispersystems.signalservice.api.push.ServiceId;
import org.whispersystems.signalservice.api.push.SignalServiceAddress;
import org.whispersystems.signalservice.internal.serialize.protos.SignalServiceContentProto;
import java.io.IOException;
import java.util.Optional;
/**
* We changed the format of the queue key for legacy PushProcessMessageJob
* to have the recipient ID in it, so this migrates existing jobs to be in that format.
*/
public class PushProcessMessageQueueJobMigration extends JobMigration {
private static final String TAG = Log.tag(PushProcessMessageQueueJobMigration.class);
private final Context context;
public PushProcessMessageQueueJobMigration(@NonNull Context context) {
super(6);
this.context = context;
}
@Override
public @NonNull JobData migrate(@NonNull JobData jobData) {
if ("PushProcessJob".equals(jobData.getFactoryKey())) {
Log.i(TAG, "Found a PushProcessMessageJob to migrate.");
try {
return migratePushProcessMessageJob(context, jobData);
} catch (IOException | InvalidInputException e) {
Log.w(TAG, "Failed to migrate message job.", e);
return jobData;
}
}
return jobData;
}
private static @NonNull JobData migratePushProcessMessageJob(@NonNull Context context, @NonNull JobData jobData) throws IOException, InvalidInputException {
JsonJobData data = JsonJobData.deserialize(jobData.getData());
String suffix = "";
if (data.getInt("message_state") == 0) {
SignalServiceContentProto proto = SignalServiceContentProto.ADAPTER.decode(Base64.decode(data.getString("message_content")));
if (proto != null && proto.content != null && proto.content.dataMessage != null && proto.content.dataMessage.groupV2 != null) {
Log.i(TAG, "Migrating a group message.");
GroupId groupId = GroupId.v2(new GroupMasterKey(proto.content.dataMessage.groupV2.masterKey.toByteArray()));
Recipient recipient = Recipient.externalGroupExact(groupId);
suffix = recipient.getId().toQueueKey();
} else if (proto != null && proto.metadata != null && proto.metadata.address != null) {
Log.i(TAG, "Migrating an individual message.");
ServiceId senderServiceId = ServiceId.parseOrThrow(proto.metadata.address.uuid);
String senderE164 = proto.metadata.address.e164;
SignalServiceAddress sender = new SignalServiceAddress(senderServiceId, Optional.ofNullable(senderE164));
suffix = RecipientId.from(sender).toQueueKey();
}
} else {
Log.i(TAG, "Migrating an exception message.");
String exceptionSender = data.getString("exception_sender");
GroupId exceptionGroup = GroupId.parseNullableOrThrow(data.getStringOrDefault("exception_groupId", null));
if (exceptionGroup != null) {
suffix = Recipient.externalGroupExact(exceptionGroup).getId().toQueueKey();
} else if (exceptionSender != null) {
suffix = Recipient.external(context, exceptionSender).getId().toQueueKey();
}
}
return jobData.withQueueKey("__PUSH_PROCESS_JOB__" + suffix);
}
}

View File

@@ -1,59 +0,0 @@
package org.thoughtcrime.securesms.jobmanager.migrations;
import androidx.annotation.NonNull;
import org.thoughtcrime.securesms.jobmanager.JsonJobData;
import org.thoughtcrime.securesms.jobmanager.JobMigration;
/**
* Fixes things that went wrong in {@link RecipientIdJobMigration}. In particular, some jobs didn't
* have some necessary data fields carried over. Thankfully they're relatively non-critical, so
* we'll just swap them out with failing jobs if they're missing something.
*/
public class RecipientIdFollowUpJobMigration extends JobMigration {
public RecipientIdFollowUpJobMigration() {
this(3);
}
RecipientIdFollowUpJobMigration(int endVersion) {
super(endVersion);
}
@Override
public @NonNull JobData migrate(@NonNull JobData jobData) {
switch(jobData.getFactoryKey()) {
case "RequestGroupInfoJob": return migrateRequestGroupInfoJob(jobData);
case "SendDeliveryReceiptJob": return migrateSendDeliveryReceiptJob(jobData);
default:
return jobData;
}
}
private static @NonNull JobData migrateRequestGroupInfoJob(@NonNull JobData jobData) {
JsonJobData data = JsonJobData.deserialize(jobData.getData());
if (!data.hasString("source") || !data.hasString("group_id")) {
return failingJobData();
} else {
return jobData;
}
}
private static @NonNull JobData migrateSendDeliveryReceiptJob(@NonNull JobData jobData) {
JsonJobData data = JsonJobData.deserialize(jobData.getData());
if (!data.hasString("recipient") ||
!data.hasLong("message_id") ||
!data.hasLong("timestamp"))
{
return failingJobData();
} else {
return jobData;
}
}
private static JobData failingJobData() {
return JobData.FAILING_JOB_DATA;
}
}

View File

@@ -1,11 +0,0 @@
package org.thoughtcrime.securesms.jobmanager.migrations;
/**
* Unfortunately there was a bug in {@link RecipientIdFollowUpJobMigration} that requires it to be
* run again.
*/
public class RecipientIdFollowUpJobMigration2 extends RecipientIdFollowUpJobMigration {
public RecipientIdFollowUpJobMigration2() {
super(4);
}
}

View File

@@ -1,263 +0,0 @@
package org.thoughtcrime.securesms.jobmanager.migrations;
import android.app.Application;
import androidx.annotation.NonNull;
import androidx.annotation.VisibleForTesting;
import com.fasterxml.jackson.annotation.JsonProperty;
import org.thoughtcrime.securesms.jobmanager.JsonJobData;
import org.thoughtcrime.securesms.jobmanager.JobMigration;
import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.recipients.RecipientId;
import org.thoughtcrime.securesms.util.JsonUtils;
import java.io.IOException;
import java.io.Serializable;
public class RecipientIdJobMigration extends JobMigration {
private final Application application;
public RecipientIdJobMigration(@NonNull Application application) {
super(2);
this.application = application;
}
@Override
public @NonNull JobData migrate(@NonNull JobData jobData) {
switch(jobData.getFactoryKey()) {
case "MultiDeviceContactUpdateJob": return migrateMultiDeviceContactUpdateJob(jobData);
case "MultiDeviceRevealUpdateJob": return migrateMultiDeviceViewOnceOpenJob(jobData);
case "RequestGroupInfoJob": return migrateRequestGroupInfoJob(jobData);
case "SendDeliveryReceiptJob": return migrateSendDeliveryReceiptJob(jobData);
case "MultiDeviceVerifiedUpdateJob": return migrateMultiDeviceVerifiedUpdateJob(jobData);
case "RetrieveProfileJob": return migrateRetrieveProfileJob(jobData);
case "PushGroupSendJob": return migratePushGroupSendJob(jobData);
case "PushGroupUpdateJob": return migratePushGroupUpdateJob(jobData);
case "DirectoryRefreshJob": return migrateDirectoryRefreshJob(jobData);
case "RetrieveProfileAvatarJob": return migrateRetrieveProfileAvatarJob(jobData);
case "MultiDeviceReadUpdateJob": return migrateMultiDeviceReadUpdateJob(jobData);
case "PushTextSendJob": return migratePushTextSendJob(jobData);
case "PushMediaSendJob": return migratePushMediaSendJob(jobData);
case "SmsSendJob": return migrateSmsSendJob(jobData);
default: return jobData;
}
}
private @NonNull JobData migrateMultiDeviceContactUpdateJob(@NonNull JobData jobData) {
JsonJobData data = JsonJobData.deserialize(jobData.getData());
String address = data.hasString("address") ? data.getString("address") : null;
JsonJobData updatedData = new JsonJobData.Builder().putString("recipient", address != null ? Recipient.external(application, address).getId().serialize() : null)
.putBoolean("force_sync", data.getBoolean("force_sync"))
.build();
return jobData.withData(updatedData.serialize());
}
private @NonNull JobData migrateMultiDeviceViewOnceOpenJob(@NonNull JobData jobData) {
JsonJobData data = JsonJobData.deserialize(jobData.getData());
try {
String rawOld = data.getString("message_id");
OldSerializableSyncMessageId old = JsonUtils.fromJson(rawOld, OldSerializableSyncMessageId.class);
Recipient recipient = Recipient.external(application, old.sender);
NewSerializableSyncMessageId updated = new NewSerializableSyncMessageId(recipient.getId().serialize(), old.timestamp);
String rawUpdated = JsonUtils.toJson(updated);
JsonJobData updatedData = new JsonJobData.Builder().putString("message_id", rawUpdated).build();
return jobData.withData(updatedData.serialize());
} catch (IOException e) {
throw new AssertionError(e);
}
}
private @NonNull JobData migrateRequestGroupInfoJob(@NonNull JobData jobData) {
JsonJobData data = JsonJobData.deserialize(jobData.getData());
String address = data.getString("source");
Recipient recipient = Recipient.external(application, address);
JsonJobData updatedData = new JsonJobData.Builder().putString("source", recipient.getId().serialize())
.putString("group_id", data.getString("group_id"))
.build();
return jobData.withData(updatedData.serialize());
}
private @NonNull JobData migrateSendDeliveryReceiptJob(@NonNull JobData jobData) {
JsonJobData data = JsonJobData.deserialize(jobData.getData());
String address = data.getString("address");
Recipient recipient = Recipient.external(application, address);
JsonJobData updatedData = new JsonJobData.Builder().putString("recipient", recipient.getId().serialize())
.putLong("message_id", data.getLong("message_id"))
.putLong("timestamp", data.getLong("timestamp"))
.build();
return jobData.withData(updatedData.serialize());
}
private @NonNull JobData migrateMultiDeviceVerifiedUpdateJob(@NonNull JobData jobData) {
JsonJobData data = JsonJobData.deserialize(jobData.getData());
String address = data.getString("destination");
Recipient recipient = Recipient.external(application, address);
JsonJobData updatedData = new JsonJobData.Builder().putString("destination", recipient.getId().serialize())
.putString("identity_key", data.getString("identity_key"))
.putInt("verified_status", data.getInt("verified_status"))
.putLong("timestamp", data.getLong("timestamp"))
.build();
return jobData.withData(updatedData.serialize());
}
private @NonNull JobData migrateRetrieveProfileJob(@NonNull JobData jobData) {
JsonJobData data = JsonJobData.deserialize(jobData.getData());
String address = data.getString("address");
Recipient recipient = Recipient.external(application, address);
JsonJobData updatedData = new JsonJobData.Builder().putString("recipient", recipient.getId().serialize()).build();
return jobData.withData(updatedData.serialize());
}
private @NonNull JobData migratePushGroupSendJob(@NonNull JobData jobData) {
JsonJobData data = JsonJobData.deserialize(jobData.getData());
// noinspection ConstantConditions
Recipient queueRecipient = Recipient.external(application, jobData.getQueueKey());
String address = data.hasString("filter_address") ? data.getString("filter_address") : null;
RecipientId recipientId = address != null ? Recipient.external(application, address).getId() : null;
JsonJobData updatedData = new JsonJobData.Builder().putString("filter_recipient", recipientId != null ? recipientId.serialize() : null)
.putLong("message_id", data.getLong("message_id"))
.build();
return jobData.withQueueKey(queueRecipient.getId().toQueueKey())
.withData(updatedData.serialize());
}
private @NonNull JobData migratePushGroupUpdateJob(@NonNull JobData jobData) {
JsonJobData data = JsonJobData.deserialize(jobData.getData());
String address = data.getString("source");
Recipient recipient = Recipient.external(application, address);
JsonJobData updatedData = new JsonJobData.Builder().putString("source", recipient.getId().serialize())
.putString("group_id", data.getString("group_id"))
.build();
return jobData.withData(updatedData.serialize());
}
private @NonNull JobData migrateDirectoryRefreshJob(@NonNull JobData jobData) {
JsonJobData data = JsonJobData.deserialize(jobData.getData());
String address = data.hasString("address") ? data.getString("address") : null;
Recipient recipient = address != null ? Recipient.external(application, address) : null;
JsonJobData updatedData = new JsonJobData.Builder().putString("recipient", recipient != null ? recipient.getId().serialize() : null)
.putBoolean("notify_of_new_users", data.getBoolean("notify_of_new_users"))
.build();
return jobData.withData(updatedData.serialize());
}
private @NonNull JobData migrateRetrieveProfileAvatarJob(@NonNull JobData jobData) {
JsonJobData data = JsonJobData.deserialize(jobData.getData());
//noinspection ConstantConditions
String queueAddress = jobData.getQueueKey().substring("RetrieveProfileAvatarJob".length());
Recipient queueRecipient = Recipient.external(application, queueAddress);
String address = data.getString("address");
Recipient recipient = Recipient.external(application, address);
JsonJobData updatedData = new JsonJobData.Builder().putString("recipient", recipient.getId().serialize())
.putString("profile_avatar", data.getString("profile_avatar"))
.build();
return jobData.withQueueKey("RetrieveProfileAvatarJob::" + queueRecipient.getId().toQueueKey())
.withData(updatedData.serialize());
}
private @NonNull JobData migrateMultiDeviceReadUpdateJob(@NonNull JobData jobData) {
JsonJobData data = JsonJobData.deserialize(jobData.getData());
try {
String[] rawOld = data.getStringArray("message_ids");
String[] rawUpdated = new String[rawOld.length];
for (int i = 0; i < rawOld.length; i++) {
OldSerializableSyncMessageId old = JsonUtils.fromJson(rawOld[i], OldSerializableSyncMessageId.class);
Recipient recipient = Recipient.external(application, old.sender);
NewSerializableSyncMessageId updated = new NewSerializableSyncMessageId(recipient.getId().serialize(), old.timestamp);
rawUpdated[i] = JsonUtils.toJson(updated);
}
JsonJobData updatedData = new JsonJobData.Builder().putStringArray("message_ids", rawUpdated).build();
return jobData.withData(updatedData.serialize());
} catch (IOException e) {
throw new AssertionError(e);
}
}
private @NonNull JobData migratePushTextSendJob(@NonNull JobData jobData) {
JsonJobData data = JsonJobData.deserialize(jobData.getData());
//noinspection ConstantConditions
Recipient recipient = Recipient.external(application, jobData.getQueueKey());
return jobData.withQueueKey(recipient.getId().toQueueKey());
}
private @NonNull JobData migratePushMediaSendJob(@NonNull JobData jobData) {
JsonJobData data = JsonJobData.deserialize(jobData.getData());
//noinspection ConstantConditions
Recipient recipient = Recipient.external(application, jobData.getQueueKey());
return jobData.withQueueKey(recipient.getId().toQueueKey());
}
private @NonNull JobData migrateSmsSendJob(@NonNull JobData jobData) {
JsonJobData data = JsonJobData.deserialize(jobData.getData());
//noinspection ConstantConditions
if (jobData.getQueueKey() != null) {
Recipient recipient = Recipient.external(application, jobData.getQueueKey());
return jobData.withQueueKey(recipient.getId().toQueueKey());
} else {
return jobData;
}
}
@VisibleForTesting
static class OldSerializableSyncMessageId implements Serializable {
private static final long serialVersionUID = 1L;
@JsonProperty
private final String sender;
@JsonProperty
private final long timestamp;
OldSerializableSyncMessageId(@JsonProperty("sender") String sender, @JsonProperty("timestamp") long timestamp) {
this.sender = sender;
this.timestamp = timestamp;
}
}
@VisibleForTesting
static class NewSerializableSyncMessageId implements Serializable {
private static final long serialVersionUID = 1L;
@JsonProperty
private final String recipientId;
@JsonProperty
private final long timestamp;
NewSerializableSyncMessageId(@JsonProperty("recipientId") String recipientId, @JsonProperty("timestamp") long timestamp) {
this.recipientId = recipientId;
this.timestamp = timestamp;
}
}
}

View File

@@ -28,7 +28,6 @@ import org.thoughtcrime.securesms.mms.MmsException;
import org.thoughtcrime.securesms.mms.OutgoingMessage;
import org.thoughtcrime.securesms.ratelimit.ProofRequiredExceptionHandler;
import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.recipients.RecipientId;
import org.thoughtcrime.securesms.recipients.RecipientUtil;
import org.thoughtcrime.securesms.service.ExpiringMessageManager;
import org.thoughtcrime.securesms.transport.RetryLaterException;
@@ -211,10 +210,14 @@ public class IndividualSendJob extends PushSendJob {
AppDependencies.getJobManager().add(new DirectoryRefreshJob(false));
} catch (UntrustedIdentityException uie) {
warn(TAG, "Failure", uie);
RecipientId recipientId = Recipient.external(context, uie.getIdentifier()).getId();
database.addMismatchedIdentity(messageId, recipientId, uie.getIdentityKey());
Recipient recipient = Recipient.external(uie.getIdentifier());
if (recipient == null) {
Log.w(TAG, "Failed to create a Recipient for the identifier!");
return;
}
database.addMismatchedIdentity(messageId, recipient.getId(), uie.getIdentityKey());
database.markAsSentFailed(messageId);
RetrieveProfileJob.enqueue(recipientId);
RetrieveProfileJob.enqueue(recipient.getId());
} catch (ProofRequiredException e) {
ProofRequiredExceptionHandler.Result result = ProofRequiredExceptionHandler.handle(context, e, SignalDatabase.threads().getRecipientForThreadId(threadId), threadId, messageId);
if (result.isRetry()) {

View File

@@ -30,14 +30,11 @@ import org.thoughtcrime.securesms.jobmanager.impl.RestoreAttachmentConstraintObs
import org.thoughtcrime.securesms.jobmanager.impl.SqlCipherMigrationConstraint;
import org.thoughtcrime.securesms.jobmanager.impl.SqlCipherMigrationConstraintObserver;
import org.thoughtcrime.securesms.jobmanager.impl.WifiConstraint;
import org.thoughtcrime.securesms.jobmanager.migrations.DeprecatedJobMigration;
import org.thoughtcrime.securesms.jobmanager.migrations.DonationReceiptRedemptionJobMigration;
import org.thoughtcrime.securesms.jobmanager.migrations.GroupCallPeekJobDataMigration;
import org.thoughtcrime.securesms.jobmanager.migrations.PushDecryptMessageJobEnvelopeMigration;
import org.thoughtcrime.securesms.jobmanager.migrations.PushProcessMessageJobMigration;
import org.thoughtcrime.securesms.jobmanager.migrations.PushProcessMessageQueueJobMigration;
import org.thoughtcrime.securesms.jobmanager.migrations.RecipientIdFollowUpJobMigration;
import org.thoughtcrime.securesms.jobmanager.migrations.RecipientIdFollowUpJobMigration2;
import org.thoughtcrime.securesms.jobmanager.migrations.RecipientIdJobMigration;
import org.thoughtcrime.securesms.jobmanager.migrations.RetrieveProfileJobMigration;
import org.thoughtcrime.securesms.jobmanager.migrations.SendReadReceiptsJobMigration;
import org.thoughtcrime.securesms.jobmanager.migrations.SenderKeyDistributionSendJobRecipientMigration;
@@ -243,7 +240,7 @@ public final class JobManagerFactories {
put(SenderKeyDistributionSendJob.KEY, new SenderKeyDistributionSendJob.Factory());
put(SendDeliveryReceiptJob.KEY, new SendDeliveryReceiptJob.Factory());
put(SendPaymentsActivatedJob.KEY, new SendPaymentsActivatedJob.Factory());
put(SendReadReceiptJob.KEY, new SendReadReceiptJob.Factory(application));
put(SendReadReceiptJob.KEY, new SendReadReceiptJob.Factory());
put(SendRetryReceiptJob.KEY, new SendRetryReceiptJob.Factory());
put(SendViewedReceiptJob.KEY, new SendViewedReceiptJob.Factory(application));
put(StorageRotateManifestJob.KEY, new StorageRotateManifestJob.Factory());
@@ -415,11 +412,11 @@ public final class JobManagerFactories {
}
public static List<JobMigration> getJobMigrations(@NonNull Application application) {
return Arrays.asList(new RecipientIdJobMigration(application),
new RecipientIdFollowUpJobMigration(),
new RecipientIdFollowUpJobMigration2(),
return Arrays.asList(new DeprecatedJobMigration(2),
new DeprecatedJobMigration(3),
new DeprecatedJobMigration(4),
new SendReadReceiptsJobMigration(SignalDatabase.messages()),
new PushProcessMessageQueueJobMigration(application),
new DeprecatedJobMigration(6),
new RetrieveProfileJobMigration(),
new PushDecryptMessageJobEnvelopeMigration(),
new SenderKeyDistributionSendJobRecipientMigration(),

View File

@@ -71,14 +71,17 @@ class MultiDeviceContactSyncJob(parameters: Parameters, private val attachmentPo
private fun processContactFile(inputStream: InputStream) {
val deviceContacts = DeviceContactsInputStream(inputStream)
val recipients = SignalDatabase.recipients
val threads = SignalDatabase.threads
var contact: DeviceContact? = deviceContacts.read()
while (contact != null) {
val recipient = if (contact.aci.isPresent) {
val recipient: Recipient? = if (contact.aci.isPresent) {
Recipient.externalPush(SignalServiceAddress(contact.aci.get(), contact.e164.orElse(null)))
} else {
Recipient.external(context, contact.e164.get())
Recipient.external(contact.e164.get())
}
if (recipient == null) {
continue
}
if (recipient.isSelf) {

View File

@@ -5,10 +5,8 @@
package org.thoughtcrime.securesms.jobs
import android.content.Context
import androidx.annotation.WorkerThread
import org.signal.core.util.logging.Log
import org.thoughtcrime.securesms.dependencies.AppDependencies
import org.thoughtcrime.securesms.groups.GroupId
import org.thoughtcrime.securesms.jobmanager.Job
import org.thoughtcrime.securesms.jobmanager.JsonJobData
@@ -92,14 +90,16 @@ class PushProcessMessageErrorJob private constructor(
@WorkerThread
private fun createParameters(exceptionMetadata: ExceptionMetadata): Parameters {
val context: Context = AppDependencies.application
val recipient: Recipient? = exceptionMetadata.groupId?.let { Recipient.externalPossiblyMigratedGroup(it) } ?: Recipient.external(exceptionMetadata.sender)
val recipient = exceptionMetadata.groupId?.let { Recipient.externalPossiblyMigratedGroup(it) } ?: Recipient.external(context, exceptionMetadata.sender)
if (recipient == null) {
Log.w(TAG, "Unable to create Recipient for the requested identifier!")
}
return Parameters.Builder()
.setMaxAttempts(Parameters.UNLIMITED)
.addConstraint(ChangeNumberConstraint.KEY)
.setQueue(PushProcessMessageJob.getQueueName(recipient.id))
.setQueue(recipient?.let { PushProcessMessageJob.getQueueName(it.id) })
.build()
}
}

View File

@@ -1,8 +1,6 @@
package org.thoughtcrime.securesms.jobs;
import android.app.Application;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;
@@ -223,12 +221,6 @@ public class SendReadReceiptJob extends BaseJob {
public static final class Factory implements Job.Factory<SendReadReceiptJob> {
private final Application application;
public Factory(@NonNull Application application) {
this.application = application;
}
@Override
public @NonNull SendReadReceiptJob create(@NonNull Parameters parameters, @Nullable byte[] serializedData) {
JsonJobData data = JsonJobData.deserialize(serializedData);
@@ -239,8 +231,7 @@ public class SendReadReceiptJob extends BaseJob {
List<String> rawMessageIds = data.hasStringArray(KEY_MESSAGE_IDS) ? data.getStringArrayAsList(KEY_MESSAGE_IDS) : Collections.emptyList();
List<MessageId> messageIds = rawMessageIds.stream().map(MessageId::deserialize).collect(Collectors.toList());
long threadId = data.getLong(KEY_THREAD);
RecipientId recipientId = data.hasString(KEY_RECIPIENT) ? RecipientId.from(data.getString(KEY_RECIPIENT))
: Recipient.external(application, data.getString(KEY_ADDRESS)).getId();
RecipientId recipientId = RecipientId.from(data.getString(KEY_RECIPIENT));
for (long id : ids) {
sentTimestamps.add(id);

View File

@@ -248,8 +248,7 @@ public class SendViewedReceiptJob extends BaseJob {
List<String> rawMessageIds = data.hasStringArray(KEY_MESSAGE_IDS) ? data.getStringArrayAsList(KEY_MESSAGE_IDS) : Collections.emptyList();
List<MessageId> messageIds = rawMessageIds.stream().map(MessageId::deserialize).collect(Collectors.toList());
long threadId = data.getLong(KEY_THREAD);
RecipientId recipientId = data.hasString(KEY_RECIPIENT) ? RecipientId.from(data.getString(KEY_RECIPIENT))
: Recipient.external(application, data.getString(KEY_ADDRESS)).getId();
RecipientId recipientId = RecipientId.from(data.getString(KEY_RECIPIENT));
return new SendViewedReceiptJob(parameters, threadId, recipientId, syncTimestamps, messageIds, timestamp);
}

View File

@@ -15,8 +15,8 @@ import org.thoughtcrime.securesms.contacts.sync.ContactDiscovery
import org.thoughtcrime.securesms.database.SignalDatabase
import org.thoughtcrime.securesms.jobmanager.Job
import org.thoughtcrime.securesms.permissions.Permissions
import org.thoughtcrime.securesms.phonenumbers.PhoneNumberFormatter
import org.thoughtcrime.securesms.storage.StorageSyncHelper
import org.thoughtcrime.securesms.util.SignalE164Util
import java.lang.Exception
/**
@@ -109,7 +109,7 @@ class SyncSystemContactLinksJob private constructor(parameters: Parameters) : Ba
messagePrompt = { e164 -> context.getString(R.string.ContactsDatabase_message_s, e164) },
callPrompt = { e164 -> context.getString(R.string.ContactsDatabase_signal_call_s, e164) },
videoCallPrompt = { e164 -> context.getString(R.string.ContactsDatabase_signal_video_call_s, e164) },
e164Formatter = { number -> PhoneNumberFormatter.get(context).format(number) },
e164Formatter = { number -> SignalE164Util.formatAsE164(number) },
messageMimetype = MESSAGE_MIMETYPE,
callMimetype = CALL_MIMETYPE,
videoCallMimetype = VIDEO_CALL_MIMETYPE,

View File

@@ -342,7 +342,12 @@ open class MessageContentProcessor(private val context: Context) {
}
fun processException(messageState: MessageState, exceptionMetadata: ExceptionMetadata, timestamp: Long) {
val sender = Recipient.external(context, exceptionMetadata.sender)
val sender = Recipient.external(exceptionMetadata.sender)
if (sender == null) {
warn("Failed to create Recipient for identifier: $messageState")
return
}
if (sender.isBlocked) {
warn("Ignoring exception content from blocked sender, message state: $messageState")

View File

@@ -10,6 +10,7 @@ import com.squareup.wire.internal.toUnmodifiableList
import org.signal.core.util.PendingIntentFlags
import org.signal.core.util.isAbsent
import org.signal.core.util.logging.Log
import org.signal.core.util.logging.logW
import org.signal.core.util.roundedString
import org.signal.libsignal.metadata.InvalidMetadataMessageException
import org.signal.libsignal.metadata.InvalidMetadataVersionException
@@ -223,8 +224,9 @@ object MessageDecryptor {
Log.w(TAG, "${logPrefix(envelope, e)} Retry receipts disabled! Enqueuing a session reset job, which will also insert an error message.", e, true)
followUpOperations += FollowUpOperation {
val sender: Recipient = Recipient.external(context, e.sender)
AutomaticSessionResetJob(sender.id, e.senderDevice, envelope.timestamp!!).asChain()
Recipient.external(e.sender)?.let {
AutomaticSessionResetJob(it.id, e.senderDevice, envelope.timestamp!!).asChain()
} ?: null.logW(TAG, "${logPrefix(envelope, e)} Failed to create a recipient with the provided identifier!")
}
Result.Ignore(envelope, serverDeliveredTimestamp, followUpOperations.toUnmodifiableList())
@@ -281,7 +283,7 @@ object MessageDecryptor {
val contentHint: ContentHint = ContentHint.fromType(protocolException.contentHint)
val senderDevice: Int = protocolException.senderDevice
val receivedTimestamp: Long = System.currentTimeMillis()
val sender: Recipient = Recipient.external(context, protocolException.sender)
val sender: Recipient = Recipient.external(protocolException.sender) ?: return Result.Ignore(envelope, serverDeliveredTimestamp, followUpOperations)
val senderServiceId: ServiceId? = ServiceId.parseOrNull(protocolException.sender)
if (sender.isSelf) {

View File

@@ -99,6 +99,7 @@ import org.thoughtcrime.securesms.util.IdentityUtil
import org.thoughtcrime.securesms.util.MediaUtil
import org.thoughtcrime.securesms.util.MessageConstraintsUtil
import org.thoughtcrime.securesms.util.RemoteConfig
import org.thoughtcrime.securesms.util.SignalE164Util
import org.thoughtcrime.securesms.util.TextSecurePreferences
import org.thoughtcrime.securesms.util.Util
import org.whispersystems.signalservice.api.crypto.EnvelopeMetadata
@@ -1724,7 +1725,9 @@ object SyncMessageProcessor {
}
threadE164 != null -> {
SignalDatabase.recipients.getOrInsertFromE164(threadE164!!)
SignalE164Util.formatAsE164(threadE164!!)?.let {
SignalDatabase.recipients.getOrInsertFromE164(threadE164!!)
}
}
else -> null

View File

@@ -62,10 +62,12 @@ public class AvatarMigrationJob extends MigrationJob {
for (File file : files) {
try {
if (isValidFileName(file.getName())) {
Recipient recipient = Recipient.external(context, file.getName());
Recipient recipient = Recipient.external(file.getName());
byte[] data = StreamUtil.readFully(new FileInputStream(file));
AvatarHelper.setAvatar(context, recipient.getId(), new ByteArrayInputStream(data));
if (recipient != null) {
AvatarHelper.setAvatar(context, recipient.getId(), new ByteArrayInputStream(data));
}
} else {
Log.w(TAG, "Invalid file name! Can't migrate this file. It'll just get deleted.");
}

View File

@@ -19,8 +19,8 @@ import org.thoughtcrime.securesms.database.RecipientTable.Companion.PNI_COLUMN
import org.thoughtcrime.securesms.database.RecipientTable.Companion.TABLE_NAME
import org.thoughtcrime.securesms.database.SignalDatabase
import org.thoughtcrime.securesms.jobmanager.Job
import org.thoughtcrime.securesms.phonenumbers.PhoneNumberFormatter
import org.thoughtcrime.securesms.recipients.RecipientId
import org.thoughtcrime.securesms.util.SignalE164Util
/**
* Through testing, we've discovered that some badly-formatted e164's wound up in the e164 column of the recipient table.
@@ -89,7 +89,7 @@ internal class BadE164MigrationJob(
continue
}
val formattedNumber = PhoneNumberFormatter.get(context).formatOrNull(entry.e164)
val formattedNumber = SignalE164Util.formatAsE164(entry.e164)
if (formattedNumber == null) {
Log.w(TAG, "We have encountered an e164-only recipient, with messages, whose value characters are all valid, but still remains completely unparsable.")
continue

View File

@@ -54,8 +54,17 @@ public class QuoteId {
public static @Nullable QuoteId deserialize(@NonNull Context context, @NonNull String serialized) {
try {
JSONObject json = new JSONObject(serialized);
RecipientId id = json.has(AUTHOR) ? RecipientId.from(json.getString(AUTHOR))
: Recipient.external(context, json.getString(AUTHOR_DEPRECATED)).getId();
RecipientId id;
if (json.has(AUTHOR)) {
id = RecipientId.from(json.getString(AUTHOR));
} else {
Recipient recipient = Recipient.external(json.getString(AUTHOR_DEPRECATED));
if (recipient != null) {
id = recipient.getId();
} else {
return null;
}
}
return new QuoteId(json.getLong(ID), id);
} catch (JSONException e) {

View File

@@ -28,6 +28,7 @@ import com.google.android.material.bottomsheet.BottomSheetDialog;
import com.google.android.material.bottomsheet.BottomSheetDialogFragment;
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
import org.signal.core.util.StringUtil;
import org.signal.core.util.ThreadUtil;
import org.signal.core.util.logging.Log;
import org.thoughtcrime.securesms.BiometricDeviceAuthentication;
@@ -42,7 +43,6 @@ import org.thoughtcrime.securesms.payments.preferences.PaymentsHomeFragmentDirec
import org.thoughtcrime.securesms.payments.preferences.RecipientHasNotEnabledPaymentsDialog;
import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.util.BottomSheetUtil;
import org.signal.core.util.StringUtil;
import org.thoughtcrime.securesms.util.ServiceUtil;
import org.thoughtcrime.securesms.util.WindowUtil;
import org.thoughtcrime.securesms.util.adapter.mapping.MappingModelList;

View File

@@ -20,6 +20,7 @@ import androidx.fragment.app.FragmentActivity;
import androidx.lifecycle.ViewModelProvider;
import androidx.navigation.Navigation;
import org.signal.core.util.StringUtil;
import org.signal.core.util.logging.Log;
import org.thoughtcrime.securesms.LoggingFragment;
import org.thoughtcrime.securesms.R;
@@ -34,7 +35,6 @@ import org.thoughtcrime.securesms.payments.State;
import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.util.DateUtils;
import org.thoughtcrime.securesms.util.SpanUtil;
import org.signal.core.util.StringUtil;
import org.thoughtcrime.securesms.util.views.LearnMoreTextView;
import org.whispersystems.signalservice.api.payments.Money;

View File

@@ -1,244 +0,0 @@
package org.thoughtcrime.securesms.phonenumbers;
import android.content.Context;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.google.i18n.phonenumbers.NumberParseException;
import com.google.i18n.phonenumbers.PhoneNumberUtil;
import com.google.i18n.phonenumbers.Phonenumber;
import com.google.i18n.phonenumbers.ShortNumberInfo;
import org.signal.core.util.BidiUtil;
import org.signal.core.util.logging.Log;
import org.signal.libsignal.protocol.util.Pair;
import org.thoughtcrime.securesms.dependencies.AppDependencies;
import org.thoughtcrime.securesms.groups.GroupId;
import org.thoughtcrime.securesms.keyvalue.SignalStore;
import org.signal.core.util.SetUtil;
import org.signal.core.util.StringUtil;
import org.thoughtcrime.securesms.util.Util;
import java.util.Optional;
import java.util.Set;
import java.util.concurrent.atomic.AtomicReference;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class PhoneNumberFormatter {
private static final String TAG = Log.tag(PhoneNumberFormatter.class);
private static final String UNKNOWN_NUMBER = "Unknown";
private static final Set<String> EXCLUDE_FROM_MANUAL_SHORTCODE_4 = SetUtil.newHashSet("AC", "NC", "NU", "TK");
private static final Set<Integer> NATIONAL_FORMAT_COUNTRY_CODES = SetUtil.newHashSet(1 /*US*/, 44 /*UK*/);
private static final Pattern US_NO_AREACODE = Pattern.compile("^(\\d{7})$");
private static final Pattern BR_NO_AREACODE = Pattern.compile("^(9?\\d{8})$");
private static final AtomicReference<Pair<String, PhoneNumberFormatter>> cachedFormatter = new AtomicReference<>();
private final Optional<PhoneNumber> localNumber;
private final String localCountryCode;
private final PhoneNumberUtil phoneNumberUtil = PhoneNumberUtil.getInstance();
private final Pattern ALPHA_PATTERN = Pattern.compile("[a-zA-Z]");
public static @NonNull PhoneNumberFormatter get(Context context) {
String localNumber = SignalStore.account().getE164();
if (!Util.isEmpty(localNumber)) {
Pair<String, PhoneNumberFormatter> cached = cachedFormatter.get();
if (cached != null && cached.first().equals(localNumber)) return cached.second();
PhoneNumberFormatter formatter = new PhoneNumberFormatter(localNumber);
cachedFormatter.set(new Pair<>(localNumber, formatter));
return formatter;
} else {
return new PhoneNumberFormatter(Util.getSimCountryIso(context).orElse("US"), true);
}
}
PhoneNumberFormatter(@NonNull String localNumberString) {
try {
Phonenumber.PhoneNumber libNumber = phoneNumberUtil.parse(localNumberString, null);
int countryCode = libNumber.getCountryCode();
this.localNumber = Optional.of(new PhoneNumber(localNumberString, countryCode, parseAreaCode(localNumberString, countryCode)));
this.localCountryCode = phoneNumberUtil.getRegionCodeForNumber(libNumber);
} catch (NumberParseException e) {
throw new AssertionError(e);
}
}
PhoneNumberFormatter(@NonNull String localCountryCode, boolean countryCode) {
this.localNumber = Optional.empty();
this.localCountryCode = localCountryCode;
}
public static @NonNull String prettyPrint(@NonNull String e164) {
return BidiUtil.forceLtr(get(AppDependencies.getApplication()).prettyPrintFormat(e164));
}
public @NonNull String prettyPrintFormat(@NonNull String e164) {
try {
Phonenumber.PhoneNumber parsedNumber = phoneNumberUtil.parse(e164, localCountryCode);
if (localNumber.isPresent() &&
localNumber.get().countryCode == parsedNumber.getCountryCode() &&
NATIONAL_FORMAT_COUNTRY_CODES.contains(localNumber.get().getCountryCode()))
{
return BidiUtil.isolateBidi(phoneNumberUtil.format(parsedNumber, PhoneNumberUtil.PhoneNumberFormat.NATIONAL));
} else {
return BidiUtil.isolateBidi(phoneNumberUtil.format(parsedNumber, PhoneNumberUtil.PhoneNumberFormat.INTERNATIONAL));
}
} catch (NumberParseException e) {
Log.w(TAG, "Failed to format number: " + e.toString());
return BidiUtil.isolateBidi(e164);
}
}
public static int getLocalCountryCode() {
Optional<PhoneNumber> localNumber = get(AppDependencies.getApplication()).localNumber;
return localNumber != null && localNumber.isPresent() ? localNumber.get().countryCode : 0;
}
public @Nullable String formatOrNull(@Nullable String number) {
String formatted = format(number);
if (formatted.equals(UNKNOWN_NUMBER)) {
return null;
}
return formatted;
}
public @NonNull String format(@Nullable String number) {
if (number == null) return UNKNOWN_NUMBER;
if (GroupId.isEncodedGroup(number)) return number;
if (ALPHA_PATTERN.matcher(number).find()) return number.trim();
String bareNumber = number.replaceAll("[^0-9+]", "");
if (bareNumber.length() == 0) {
if (number.trim().length() == 0) return "Unknown";
else return number.trim();
}
if (bareNumber.length() <= 6) {
return bareNumber;
}
if (isShortCode(bareNumber, localCountryCode)) {
Log.i(TAG, "Recognized number as short code.");
return bareNumber;
}
String processedNumber = applyAreaCodeRules(localNumber, bareNumber);
try {
Phonenumber.PhoneNumber parsedNumber = phoneNumberUtil.parse(processedNumber, localCountryCode);
String formatted = phoneNumberUtil.format(parsedNumber, PhoneNumberUtil.PhoneNumberFormat.E164);
if (formatted.startsWith("+")) {
return formatted;
} else {
throw new NumberParseException(NumberParseException.ErrorType.NOT_A_NUMBER, "After formatting, the number did not start with +! hasRawInput: " + parsedNumber.hasRawInput());
}
} catch (NumberParseException e) {
Log.w(TAG, e.toString());
if (bareNumber.charAt(0) == '+') {
return bareNumber;
}
if (localNumber.isPresent()) {
String localNumberImprecise = localNumber.get().getE164Number();
if (localNumberImprecise.charAt(0) == '+') {
localNumberImprecise = localNumberImprecise.substring(1);
}
if (localNumberImprecise.length() == bareNumber.length() || bareNumber.length() > localNumberImprecise.length()) {
return "+" + number;
}
int difference = localNumberImprecise.length() - bareNumber.length();
return "+" + localNumberImprecise.substring(0, difference) + bareNumber;
} else {
String countryCode = String.valueOf(phoneNumberUtil.getCountryCodeForRegion(localCountryCode));
return "+" + (bareNumber.startsWith(countryCode) ? bareNumber : countryCode + bareNumber);
}
}
}
private boolean isShortCode(@NonNull String bareNumber, String localCountryCode) {
try {
Phonenumber.PhoneNumber parsedNumber = phoneNumberUtil.parse(bareNumber, localCountryCode);
return ShortNumberInfo.getInstance().isValidShortNumberForRegion(parsedNumber, localCountryCode);
} catch (NumberParseException e) {
return false;
}
}
private @Nullable String parseAreaCode(@NonNull String e164Number, int countryCode) {
switch (countryCode) {
case 1:
return e164Number.substring(2, 5);
case 55:
return e164Number.substring(3, 5);
}
return null;
}
private @NonNull String applyAreaCodeRules(@NonNull Optional<PhoneNumber> localNumber, @NonNull String testNumber) {
if (!localNumber.isPresent() || !localNumber.get().getAreaCode().isPresent()) {
return testNumber;
}
Matcher matcher;
switch (localNumber.get().getCountryCode()) {
case 1:
matcher = US_NO_AREACODE.matcher(testNumber);
if (matcher.matches()) {
return localNumber.get().getAreaCode() + matcher.group();
}
break;
case 55:
matcher = BR_NO_AREACODE.matcher(testNumber);
if (matcher.matches()) {
return localNumber.get().getAreaCode() + matcher.group();
}
}
return testNumber;
}
private static class PhoneNumber {
private final String e164Number;
private final int countryCode;
private final Optional<String> areaCode;
PhoneNumber(String e164Number, int countryCode, @Nullable String areaCode) {
this.e164Number = e164Number;
this.countryCode = countryCode;
this.areaCode = Optional.ofNullable(areaCode);
}
String getE164Number() {
return e164Number;
}
int getCountryCode() {
return countryCode;
}
Optional<String> getAreaCode() {
return areaCode;
}
}
}

View File

@@ -9,17 +9,16 @@ import androidx.lifecycle.ViewModel;
import androidx.lifecycle.ViewModelProvider;
import org.signal.core.util.BidiUtil;
import org.signal.core.util.StringUtil;
import org.signal.core.util.logging.Log;
import org.thoughtcrime.securesms.conversation.colors.AvatarColor;
import org.thoughtcrime.securesms.groups.GroupId;
import org.thoughtcrime.securesms.keyvalue.PhoneNumberPrivacyValues;
import org.thoughtcrime.securesms.keyvalue.PhoneNumberPrivacyValues.PhoneNumberDiscoverabilityMode;
import org.thoughtcrime.securesms.keyvalue.SignalStore;
import org.thoughtcrime.securesms.mediasend.Media;
import org.thoughtcrime.securesms.profiles.ProfileName;
import org.thoughtcrime.securesms.profiles.edit.EditProfileRepository.UploadResult;
import org.thoughtcrime.securesms.util.SingleLiveEvent;
import org.signal.core.util.StringUtil;
import org.thoughtcrime.securesms.util.livedata.LiveDataUtil;
import java.util.Arrays;

View File

@@ -1,6 +1,5 @@
package org.thoughtcrime.securesms.profiles.manage;
import android.content.res.ColorStateList;
import android.graphics.drawable.Drawable;
import android.os.Bundle;
import android.text.Editable;
@@ -16,7 +15,6 @@ import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.StringRes;
import androidx.appcompat.widget.Toolbar;
import androidx.core.content.ContextCompat;
import androidx.fragment.app.Fragment;
import androidx.lifecycle.ViewModelProvider;
import androidx.navigation.Navigation;

View File

@@ -18,10 +18,10 @@ import androidx.lifecycle.ViewModelProvider;
import androidx.navigation.Navigation;
import org.signal.core.util.EditTextUtil;
import org.signal.core.util.StringUtil;
import org.thoughtcrime.securesms.R;
import org.thoughtcrime.securesms.profiles.ProfileName;
import org.thoughtcrime.securesms.recipients.Recipient;
import org.signal.core.util.StringUtil;
import org.thoughtcrime.securesms.util.ViewUtil;
import org.thoughtcrime.securesms.util.text.AfterTextChanged;
import org.thoughtcrime.securesms.util.views.CircularProgressMaterialButton;

View File

@@ -8,9 +8,9 @@ import androidx.lifecycle.MutableLiveData;
import androidx.lifecycle.Transformations;
import androidx.lifecycle.ViewModel;
import org.signal.core.util.StringUtil;
import org.thoughtcrime.securesms.profiles.ProfileName;
import org.thoughtcrime.securesms.util.SingleLiveEvent;
import org.signal.core.util.StringUtil;
public final class EditProfileNameViewModel extends ViewModel {

View File

@@ -40,9 +40,9 @@ import org.thoughtcrime.securesms.groups.GroupId
import org.thoughtcrime.securesms.keyvalue.SignalStore
import org.thoughtcrime.securesms.notifications.NotificationChannels
import org.thoughtcrime.securesms.phonenumbers.NumberUtil
import org.thoughtcrime.securesms.phonenumbers.PhoneNumberFormatter
import org.thoughtcrime.securesms.profiles.ProfileName
import org.thoughtcrime.securesms.service.webrtc.links.CallLinkRoomId
import org.thoughtcrime.securesms.util.SignalE164Util
import org.thoughtcrime.securesms.util.UsernameUtil.isValidUsernameForSearch
import org.thoughtcrime.securesms.util.Util
import org.thoughtcrime.securesms.wallpaper.ChatWallpaper
@@ -556,7 +556,7 @@ class Recipient(
}
if (name.isBlank() && e164Value.isNotNullOrBlank()) {
name = PhoneNumberFormatter.prettyPrint(e164Value)
name = SignalE164Util.prettyPrint(e164Value)
}
if (name.isBlank() && emailValue != null) {
@@ -587,7 +587,7 @@ class Recipient(
}
if (name.isBlank() && e164Value.isNotNullOrBlank()) {
name = PhoneNumberFormatter.prettyPrint(e164Value)
name = SignalE164Util.prettyPrint(e164Value)
}
if (name.isBlank()) {
@@ -901,6 +901,37 @@ class Recipient(
return externalPush(serviceId, null)
}
/**
* Returns a fully-populated [Recipient] based off of a ServiceId and phone number, creating one
* in the database if necessary. We want both piece of information so we're able to associate them
* both together, depending on which are available.
*
* In particular, while we may eventually get the ACI of a user created via a phone number
* (through a directory sync), the only way we can store the phone number is by retrieving it from
* sent messages and whatnot. So we should store it when available.
*/
@JvmStatic
@WorkerThread
private fun externalPush(serviceId: ServiceId, e164: String?): Recipient {
if (ACI.UNKNOWN == serviceId || PNI.UNKNOWN == serviceId) {
throw AssertionError()
}
val recipientId = RecipientId.from(SignalServiceAddress(serviceId, e164))
val resolved = resolved(recipientId)
if (resolved.id != recipientId) {
Log.w(TAG, "Resolved $recipientId, but got back a recipient with ${resolved.id}")
}
if (!resolved.isRegistered) {
Log.w(TAG, "External push was locally marked unregistered. Marking as registered.")
SignalDatabase.recipients.markRegistered(recipientId, serviceId)
}
return resolved
}
/**
* Create a recipient with a full (ACI, PNI, E164) tuple. It is assumed that the association between the PNI and serviceId is trusted.
* That means it must be from either storage service (with the verified field set) or a PNI verification message.
@@ -927,39 +958,6 @@ class Recipient(
return resolved
}
/**
* Returns a fully-populated [Recipient] based off of a ServiceId and phone number, creating one
* in the database if necessary. We want both piece of information so we're able to associate them
* both together, depending on which are available.
*
* In particular, while we may eventually get the ACI of a user created via a phone number
* (through a directory sync), the only way we can store the phone number is by retrieving it from
* sent messages and whatnot. So we should store it when available.
*/
@JvmStatic
@WorkerThread
fun externalPush(serviceId: ServiceId?, e164: String?): Recipient {
if (ACI.UNKNOWN == serviceId || PNI.UNKNOWN == serviceId) {
throw AssertionError()
}
val recipientId = RecipientId.from(SignalServiceAddress(serviceId, e164))
val resolved = resolved(recipientId)
if (resolved.id != recipientId) {
Log.w(TAG, "Resolved $recipientId, but got back a recipient with ${resolved.id}")
}
if (!resolved.isRegistered && serviceId != null) {
Log.w(TAG, "External push was locally marked unregistered. Marking as registered.")
SignalDatabase.recipients.markRegistered(recipientId, serviceId)
} else if (!resolved.isRegistered) {
Log.w(TAG, "External push was locally marked unregistered, but we don't have an ACI, so we can't do anything.", Throwable())
}
return resolved
}
/**
* A safety wrapper around [.external] for when you know you're using an
* identifier for a system contact, and therefore always want to prevent interpreting it as a
@@ -969,13 +967,14 @@ class Recipient(
*/
@JvmStatic
@WorkerThread
fun externalContact(identifier: String): Recipient {
fun externalContact(identifier: String): Recipient? {
val id: RecipientId = if (UuidUtil.isUuid(identifier)) {
throw AssertionError("UUIDs are not valid system contact identifiers!")
} else if (NumberUtil.isValidEmail(identifier)) {
SignalDatabase.recipients.getOrInsertFromEmail(identifier)
} else {
SignalDatabase.recipients.getOrInsertFromE164(identifier)
val e164 = SignalE164Util.formatAsE164(identifier) ?: return null
SignalDatabase.recipients.getOrInsertFromE164(e164)
}
return resolved(id)
@@ -1027,10 +1026,13 @@ class Recipient(
* If the identifier is a UUID of a Signal user, prefer using
* [.externalPush] or its overload, as this will let us associate
* the phone number with the recipient.
*
* Important: If the identifier cannot be considered a valid UUID, groupId, email, or phone number,
* this will return null.
*/
@JvmStatic
@WorkerThread
fun external(context: Context, identifier: String): Recipient {
fun external(identifier: String): Recipient? {
val serviceId = ServiceId.parseOrNull(identifier, logFailures = false)
val id: RecipientId = if (serviceId != null) {
@@ -1042,7 +1044,7 @@ class Recipient(
} else if (isValidUsernameForSearch(identifier)) {
throw IllegalArgumentException("Creating a recipient based on username alone is not supported!")
} else {
val e164 = PhoneNumberFormatter.get(context).format(identifier)
val e164: String = SignalE164Util.formatAsE164(identifier) ?: return null
SignalDatabase.recipients.getOrInsertFromE164(e164)
}

View File

@@ -5,13 +5,12 @@
package org.thoughtcrime.securesms.recipients
import android.content.Context
import androidx.annotation.WorkerThread
import org.signal.core.util.logging.Log
import org.thoughtcrime.securesms.contacts.sync.ContactDiscovery
import org.thoughtcrime.securesms.database.SignalDatabase
import org.thoughtcrime.securesms.phonenumbers.NumberUtil
import org.thoughtcrime.securesms.phonenumbers.PhoneNumberFormatter
import org.thoughtcrime.securesms.util.SignalE164Util
import java.io.IOException
/**
@@ -29,10 +28,10 @@ object RecipientRepository {
*/
@WorkerThread
@JvmStatic
fun lookupNewE164(context: Context, inputE164: String): LookupResult {
val e164 = PhoneNumberFormatter.get(context).format(inputE164)
fun lookupNewE164(inputE164: String): LookupResult {
val e164 = SignalE164Util.formatAsE164(inputE164)
if (!NumberUtil.isVisuallyValidNumber(e164)) {
if (e164 == null || !NumberUtil.isVisuallyValidNumber(e164)) {
return LookupResult.InvalidEntry
}

View File

@@ -51,10 +51,10 @@ import org.thoughtcrime.securesms.components.emoji.EmojiTextView
import org.thoughtcrime.securesms.compose.ComposeBottomSheetDialogFragment
import org.thoughtcrime.securesms.conversation.v2.UnverifiedProfileNameBottomSheet
import org.thoughtcrime.securesms.nicknames.ViewNoteSheet
import org.thoughtcrime.securesms.phonenumbers.PhoneNumberFormatter
import org.thoughtcrime.securesms.recipients.Recipient
import org.thoughtcrime.securesms.recipients.RecipientId
import org.thoughtcrime.securesms.stories.settings.my.SignalConnectionsBottomSheetDialogFragment
import org.thoughtcrime.securesms.util.SignalE164Util
import org.thoughtcrime.securesms.util.viewModel
/**
@@ -103,7 +103,7 @@ class AboutSheet : ComposeBottomSheetDialogFragment() {
hasAvatar = recipient.get().profileAvatarFileDetails.hasFile(),
recipientForAvatar = recipient.get(),
formattedE164 = if (recipient.get().hasE164 && recipient.get().shouldShowE164) {
PhoneNumberFormatter.get(requireContext()).prettyPrintFormat(recipient.get().requireE164())
SignalE164Util.prettyPrint(recipient.get().requireE164())
} else {
null
},

View File

@@ -68,6 +68,7 @@ import org.signal.core.ui.Previews
import org.signal.core.ui.Scaffolds
import org.signal.core.ui.TextFields
import org.signal.core.ui.theme.SignalTheme
import org.signal.core.util.E164Util
import org.signal.core.util.getParcelableExtraCompat
import org.thoughtcrime.securesms.PassphraseRequiredActivity
import org.thoughtcrime.securesms.R
@@ -80,7 +81,6 @@ import org.thoughtcrime.securesms.registration.ui.countrycode.CountryCodeSelectS
import org.thoughtcrime.securesms.registration.ui.countrycode.CountryCodeState
import org.thoughtcrime.securesms.util.DynamicNoActionBarTheme
import org.thoughtcrime.securesms.util.viewModel
import org.whispersystems.signalservice.api.util.PhoneNumberFormatter
import org.signal.core.ui.R as CoreUiR
/**
@@ -193,9 +193,9 @@ class FindByActivity : PassphraseRequiredActivity() {
} else {
val formattedNumber = remember(state.userEntry) {
val cleansed = state.userEntry.removePrefix(state.selectedCountry.countryCode.toString())
PhoneNumberFormatter.formatE164(state.selectedCountry.countryCode.toString(), cleansed)
E164Util.formatAsE164WithCountryCodeForDisplay(state.selectedCountry.countryCode.toString(), cleansed)
}
stringResource(id = R.string.FindByActivity__s_is_not_a_valid_phone_number, formattedNumber)
stringResource(id = R.string.FindByActivity__s_is_not_a_valid_phone_number, state.userEntry)
}
Dialogs.SimpleAlertDialog(
@@ -232,7 +232,7 @@ class FindByActivity : PassphraseRequiredActivity() {
} else {
val formattedNumber = remember(state.userEntry) {
val cleansed = state.userEntry.removePrefix(state.selectedCountry.countryCode.toString())
PhoneNumberFormatter.formatE164(state.selectedCountry.countryCode.toString(), cleansed)
E164Util.formatAsE164WithCountryCodeForDisplay(state.selectedCountry.countryCode.toString(), cleansed)
}
stringResource(id = R.string.FindByActivity__s_is_not_a_signal_user_would, formattedNumber)
}

View File

@@ -81,7 +81,7 @@ class FindByViewModel(
val e164 = "+$countryCode$nationalNumber"
return when (val result = RecipientRepository.lookupNewE164(context, e164)) {
return when (val result = RecipientRepository.lookupNewE164(e164)) {
RecipientRepository.LookupResult.InvalidEntry -> FindByResult.InvalidEntry
RecipientRepository.LookupResult.NetworkError -> FindByResult.NetworkError
is RecipientRepository.LookupResult.NotFound -> FindByResult.NotFound(result.recipientId)

View File

@@ -6,7 +6,7 @@
package org.thoughtcrime.securesms.registration.ui.countrycode
import com.google.i18n.phonenumbers.PhoneNumberUtil
import org.whispersystems.signalservice.api.util.PhoneNumberFormatter
import org.signal.core.util.E164Util
import java.text.Collator
import java.util.Locale
@@ -25,7 +25,7 @@ object CountryUtils {
return PhoneNumberUtil.getInstance().supportedRegions
.map { region ->
Country(
name = PhoneNumberFormatter.getRegionDisplayName(region).orElse(""),
name = E164Util.getRegionDisplayName(region).orElse(""),
emoji = countryToEmoji(region),
countryCode = PhoneNumberUtil.getInstance().getCountryCodeForRegion(region),
regionCode = region
@@ -39,7 +39,7 @@ object CountryUtils {
return COMMON_COUNTRIES
.map { region ->
Country(
name = PhoneNumberFormatter.getRegionDisplayName(region).orElse(""),
name = E164Util.getRegionDisplayName(region).orElse(""),
emoji = countryToEmoji(region),
countryCode = PhoneNumberUtil.getInstance().getCountryCodeForRegion(region),
regionCode = region

View File

@@ -45,7 +45,6 @@ import org.thoughtcrime.securesms.R
import org.thoughtcrime.securesms.components.ViewBinderDelegate
import org.thoughtcrime.securesms.databinding.FragmentRegistrationEnterPhoneNumberBinding
import org.thoughtcrime.securesms.dependencies.AppDependencies
import org.thoughtcrime.securesms.phonenumbers.PhoneNumberFormatter
import org.thoughtcrime.securesms.registration.data.RegistrationRepository
import org.thoughtcrime.securesms.registration.data.network.Challenge
import org.thoughtcrime.securesms.registration.data.network.RegisterAccountResult
@@ -65,6 +64,7 @@ import org.thoughtcrime.securesms.registration.util.CountryPrefix
import org.thoughtcrime.securesms.util.CommunicationActions
import org.thoughtcrime.securesms.util.Dialogs
import org.thoughtcrime.securesms.util.PlayServicesUtil
import org.thoughtcrime.securesms.util.SignalE164Util
import org.thoughtcrime.securesms.util.SpanUtil
import org.thoughtcrime.securesms.util.SupportEmailUtil
import org.thoughtcrime.securesms.util.ViewUtil
@@ -619,7 +619,7 @@ class EnterPhoneNumberFragment : LoggingFragment(R.layout.fragment_registration_
}
val message: CharSequence = SpannableStringBuilder().apply {
append(SpanUtil.bold(PhoneNumberFormatter.prettyPrint(phoneNumber.toE164())))
append(SpanUtil.bold(SignalE164Util.prettyPrint(phoneNumber.toE164())))
if (!canSkipSms) {
append("\n\n")
append(getString(R.string.RegistrationActivity_a_verification_code_will_be_sent_to_this_number))

View File

@@ -13,13 +13,13 @@ import com.google.i18n.phonenumbers.PhoneNumberUtil
import com.google.i18n.phonenumbers.Phonenumber.PhoneNumber
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.update
import org.signal.core.util.E164Util
import org.signal.core.util.logging.Log
import org.thoughtcrime.securesms.registration.data.RegistrationRepository
import org.thoughtcrime.securesms.registration.ui.countrycode.Country
import org.thoughtcrime.securesms.registration.ui.countrycode.CountryUtils
import org.thoughtcrime.securesms.registration.util.CountryPrefix
import org.thoughtcrime.securesms.util.Util
import org.whispersystems.signalservice.api.util.PhoneNumberFormatter
/**
* ViewModel for the phone number entry screen.
@@ -74,7 +74,7 @@ class EnterPhoneNumberViewModel : ViewModel() {
countryPrefixIndex = prefixIndex,
phoneNumberRegionCode = regionCode,
country = existingCountry ?: Country(
name = PhoneNumberFormatter.getRegionDisplayName(regionCode).orElse(""),
name = E164Util.getRegionDisplayName(regionCode).orElse(""),
emoji = CountryUtils.countryToEmoji(regionCode),
countryCode = countryCode,
regionCode = regionCode
@@ -122,7 +122,7 @@ class EnterPhoneNumberViewModel : ViewModel() {
val regionCode = supportedCountryPrefixes[matchingIndex].regionCode
val matchedCountry = Country(
name = PhoneNumberFormatter.getRegionDisplayName(regionCode).orElse(""),
name = E164Util.getRegionDisplayName(regionCode).orElse(""),
emoji = CountryUtils.countryToEmoji(regionCode),
countryCode = digits,
regionCode = regionCode

View File

@@ -19,12 +19,12 @@ import androidx.annotation.Nullable;
import com.google.i18n.phonenumbers.AsYouTypeFormatter;
import com.google.i18n.phonenumbers.PhoneNumberUtil;
import org.signal.core.util.E164Util;
import org.thoughtcrime.securesms.R;
import org.thoughtcrime.securesms.components.LabeledEditText;
import org.thoughtcrime.securesms.registration.ui.countrycode.Country;
import org.thoughtcrime.securesms.registration.ui.countrycode.CountryUtils;
import org.thoughtcrime.securesms.registration.viewmodel.NumberViewState;
import org.whispersystems.signalservice.api.util.PhoneNumberFormatter;
/**
* Handle the logic and formatting of phone number input specifically for change number flows.
@@ -248,7 +248,7 @@ public final class ChangeNumberInputController {
}
if (!isUpdating) {
Country country = new Country(CountryUtils.countryToEmoji(regionCode), PhoneNumberFormatter.getRegionDisplayName(regionCode).orElse(""), countryCode, regionCode);
Country country = new Country(CountryUtils.countryToEmoji(regionCode), E164Util.getRegionDisplayName(regionCode).orElse(""), countryCode, regionCode);
callbacks.setCountry(country);
}
}

View File

@@ -10,7 +10,7 @@ import com.google.i18n.phonenumbers.NumberParseException;
import com.google.i18n.phonenumbers.PhoneNumberUtil;
import com.google.i18n.phonenumbers.Phonenumber;
import org.whispersystems.signalservice.api.util.PhoneNumberFormatter;
import org.signal.core.util.E164Util;
import java.util.Objects;
@@ -42,7 +42,7 @@ public final class NumberViewState implements Parcelable {
return nationalNumber;
}
public String getCountryDisplayName() {
public @Nullable String getCountryDisplayName() {
if (selectedCountryName != null) {
return selectedCountryName;
}
@@ -58,7 +58,7 @@ public final class NumberViewState implements Parcelable {
}
String regionCode = util.getRegionCodeForCountryCode(countryCode);
return PhoneNumberFormatter.getRegionDisplayNameLegacy(regionCode);
return E164Util.getRegionDisplayName(regionCode).orElse(null);
}
/**
@@ -70,7 +70,7 @@ public final class NumberViewState implements Parcelable {
String regionCode = util.getRegionCodeForNumber(phoneNumber);
if (regionCode != null) {
return PhoneNumberFormatter.getRegionDisplayNameLegacy(regionCode);
return E164Util.getRegionDisplayName(regionCode).orElse(null);
}
} catch (NumberParseException e) {
@@ -80,7 +80,7 @@ public final class NumberViewState implements Parcelable {
}
public boolean isValid() {
return PhoneNumberFormatter.isValidNumber(getE164Number(), Integer.toString(getCountryCode()));
return E164Util.isValidNumberForRegistration(Integer.toString(getCountryCode()), getE164Number());
}
@Override
@@ -123,7 +123,7 @@ public final class NumberViewState implements Parcelable {
}
private static String getConfiguredE164Number(int countryCode, String number) {
return PhoneNumberFormatter.formatE164(String.valueOf(countryCode), number);
return E164Util.formatAsE164WithCountryCodeForDisplay(String.valueOf(countryCode), number);
}
private static Phonenumber.PhoneNumber getPhoneNumber(@NonNull PhoneNumberUtil util, @NonNull String e164Number)

View File

@@ -46,7 +46,6 @@ import org.thoughtcrime.securesms.R
import org.thoughtcrime.securesms.components.ViewBinderDelegate
import org.thoughtcrime.securesms.databinding.FragmentRegistrationEnterPhoneNumberBinding
import org.thoughtcrime.securesms.dependencies.AppDependencies
import org.thoughtcrime.securesms.phonenumbers.PhoneNumberFormatter
import org.thoughtcrime.securesms.registration.data.network.Challenge
import org.thoughtcrime.securesms.registration.data.network.RegisterAccountResult
import org.thoughtcrime.securesms.registration.data.network.RegistrationResult
@@ -66,6 +65,7 @@ import org.thoughtcrime.securesms.registrationv3.ui.RegistrationViewModel
import org.thoughtcrime.securesms.util.CommunicationActions
import org.thoughtcrime.securesms.util.Dialogs
import org.thoughtcrime.securesms.util.PlayServicesUtil
import org.thoughtcrime.securesms.util.SignalE164Util
import org.thoughtcrime.securesms.util.SpanUtil
import org.thoughtcrime.securesms.util.SupportEmailUtil
import org.thoughtcrime.securesms.util.ViewUtil
@@ -644,7 +644,7 @@ class EnterPhoneNumberFragment : LoggingFragment(R.layout.fragment_registration_
}
val message: CharSequence = SpannableStringBuilder().apply {
append(SpanUtil.bold(PhoneNumberFormatter.prettyPrint(phoneNumber.toE164())))
append(SpanUtil.bold(SignalE164Util.prettyPrint(phoneNumber.toE164())))
if (!canSkipSms) {
append("\n\n")
append(getString(R.string.RegistrationActivity_a_verification_code_will_be_sent_to_this_number))

View File

@@ -13,13 +13,13 @@ import com.google.i18n.phonenumbers.PhoneNumberUtil
import com.google.i18n.phonenumbers.Phonenumber.PhoneNumber
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.update
import org.signal.core.util.E164Util
import org.signal.core.util.logging.Log
import org.thoughtcrime.securesms.registration.ui.countrycode.Country
import org.thoughtcrime.securesms.registration.ui.countrycode.CountryUtils
import org.thoughtcrime.securesms.registration.util.CountryPrefix
import org.thoughtcrime.securesms.registrationv3.data.RegistrationRepository
import org.thoughtcrime.securesms.util.Util
import org.whispersystems.signalservice.api.util.PhoneNumberFormatter
/**
* ViewModel for the phone number entry screen.
@@ -74,7 +74,7 @@ class EnterPhoneNumberViewModel : ViewModel() {
countryPrefixIndex = prefixIndex,
phoneNumberRegionCode = regionCode,
country = existingCountry ?: Country(
name = PhoneNumberFormatter.getRegionDisplayName(regionCode).orElse(""),
name = E164Util.getRegionDisplayName(regionCode).orElse(""),
emoji = CountryUtils.countryToEmoji(regionCode),
countryCode = countryCode,
regionCode = regionCode
@@ -122,7 +122,7 @@ class EnterPhoneNumberViewModel : ViewModel() {
val regionCode = supportedCountryPrefixes[matchingIndex].regionCode
val matchedCountry = Country(
name = PhoneNumberFormatter.getRegionDisplayName(regionCode).orElse(""),
name = E164Util.getRegionDisplayName(regionCode).orElse(""),
emoji = CountryUtils.countryToEmoji(regionCode),
countryCode = digits,
regionCode = regionCode

View File

@@ -92,7 +92,7 @@ abstract class BaseStoryRecipientSelectionFragment : Fragment(R.layout.stories_b
when (action) {
is BaseStoryRecipientSelectionViewModel.Action.ExitFlow -> exitFlow()
is BaseStoryRecipientSelectionViewModel.Action.GoToNextScreen -> goToNextScreen(
getAttachedContactSelectionFragment().selectedContacts.map { it.getOrCreateRecipientId(requireContext()) }.toSet()
getAttachedContactSelectionFragment().selectedContacts.map { it.getOrCreateRecipientId() }.toSet()
)
}
}

View File

@@ -310,7 +310,7 @@ public class CommunicationActions {
* If the url is a signal.me link it will handle it.
*/
public static void handlePotentialSignalMeUrl(@NonNull FragmentActivity activity, @NonNull String potentialUrl) {
String e164 = SignalMeUtil.parseE164FromLink(activity, potentialUrl);
String e164 = SignalMeUtil.parseE164FromLink(potentialUrl);
UsernameLinkComponents username = UsernameRepository.parseLink(potentialUrl);
if (e164 != null) {
@@ -426,7 +426,10 @@ public class CommunicationActions {
SimpleProgressDialog.DismissibleDialog dialog = SimpleProgressDialog.showDelayed(activity, 500, 500);
SimpleTask.run(() -> {
Recipient recipient = Recipient.external(activity, e164);
Recipient recipient = Recipient.external(e164);
if (recipient == null) {
return null;
}
if (!recipient.isRegistered() || !recipient.getHasServiceId()) {
try {
@@ -441,7 +444,7 @@ public class CommunicationActions {
}, recipient -> {
dialog.dismiss();
if (recipient.isRegistered() && recipient.getHasServiceId()) {
if (recipient != null && recipient.isRegistered() && recipient.getHasServiceId()) {
startConversation(activity, recipient, null);
} else {
new MaterialAlertDialogBuilder(activity)

View File

@@ -7,7 +7,6 @@ import androidx.annotation.Nullable;
import androidx.annotation.WorkerThread;
import org.signal.core.util.BidiUtil;
import org.signal.core.util.StringUtil;
import org.signal.core.util.logging.Log;
import org.signal.libsignal.zkgroup.InvalidInputException;
import org.signal.libsignal.zkgroup.groups.GroupMasterKey;

View File

@@ -0,0 +1,74 @@
/*
* Copyright 2025 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.thoughtcrime.securesms.util
import org.signal.core.util.E164Util
import org.thoughtcrime.securesms.dependencies.AppDependencies
import org.thoughtcrime.securesms.keyvalue.SignalStore
/**
* A wrapper around [E164Util] that automatically handles fetching our own number and caching formatters.
*/
object SignalE164Util {
private val cachedFormatters: MutableMap<String, E164Util.Formatter> = LRUCache(2)
private val defaultFormatter: E164Util.Formatter by lazy {
E164Util.Formatter(
localNumber = null,
localAreaCode = null,
localRegionCode = Util.getSimCountryIso(AppDependencies.application).orElse("US")
)
}
/**
* Formats the number for human-readable display. e.g. "(555) 555-5555"
*/
@JvmStatic
fun prettyPrint(input: String): String {
return getFormatter().prettyPrint(input)
}
/**
* Returns the country code for the local number, if present. Otherwise, it returns 0.
*/
fun getLocalCountryCode(): Int {
return getFormatter().localNumber?.countryCode ?: 0
}
/**
* Formats the number as an E164, or null if the number cannot be reasonably interpreted as a phone number.
* This does not check if the number is *valid* for a given region. Instead, it's very lenient and just
* does it's best to interpret the input string as a number that could be put into the E164 format.
*
* Note that shortcodes will not have leading '+' signs.
*
* In other words, if this method returns null, you likely do not have anything that could be considered
* a phone number.
*/
@JvmStatic
fun formatAsE164(input: String): String? {
return getFormatter().formatAsE164(input)
}
private fun getFormatter(): E164Util.Formatter {
val localNumber = SignalStore.account.e164 ?: return defaultFormatter
val formatter = cachedFormatters[localNumber]
if (formatter != null) {
return formatter
}
synchronized(cachedFormatters) {
val formatter = cachedFormatters[localNumber]
if (formatter != null) {
return formatter
}
val newFormatter = E164Util.createFormatterForE164(localNumber)
cachedFormatters[localNumber] = newFormatter
return newFormatter
}
}
}

View File

@@ -1,8 +1,6 @@
package org.thoughtcrime.securesms.util
import android.content.Context
import com.google.i18n.phonenumbers.PhoneNumberUtil
import org.thoughtcrime.securesms.phonenumbers.PhoneNumberFormatter
import java.util.Locale
internal object SignalMeUtil {
@@ -12,7 +10,7 @@ internal object SignalMeUtil {
* If this is a valid signal.me link and has a valid e164, it will return the e164. Otherwise, it will return null.
*/
@JvmStatic
fun parseE164FromLink(context: Context, link: String?): String? {
fun parseE164FromLink(link: String?): String? {
if (link.isNullOrBlank()) {
return null
}
@@ -21,7 +19,7 @@ internal object SignalMeUtil {
val e164: String = match.groups[2]?.value ?: return@let null
if (PhoneNumberUtil.getInstance().isPossibleNumber(e164, Locale.getDefault().country)) {
PhoneNumberFormatter.get(context).format(e164)
SignalE164Util.formatAsE164(e164)
} else {
null
}

View File

@@ -32,8 +32,8 @@ public class VoiceCallShare extends Activity {
if (cursor != null && cursor.moveToNext()) {
String destination = cursor.getString(cursor.getColumnIndexOrThrow(ContactsContract.RawContacts.Data.DATA1));
SimpleTask.run(() -> Recipient.external(this, destination), recipient -> {
if (!TextUtils.isEmpty(destination)) {
SimpleTask.run(() -> Recipient.external(destination), recipient -> {
if (recipient != null && !TextUtils.isEmpty(destination)) {
if (VIDEO_CALL_MIME_TYPE.equals(getIntent().getType())) {
AppDependencies.getSignalCallManager().startOutgoingVideoCall(recipient);
} else {