From 2e4ac7ede195a71517defe19cee1c2e8eca2e727 Mon Sep 17 00:00:00 2001 From: Greyson Parrelli Date: Wed, 6 Mar 2024 12:11:52 -0500 Subject: [PATCH] Always perform CDSI lookups when starting new chats. --- .../securesms/NewConversationActivity.java | 31 +++------ .../securesms/calls/new/NewCallActivity.kt | 52 +++++++-------- .../contacts/sync/ContactDiscovery.kt | 22 ++++++- .../sync/ContactDiscoveryRefreshV2.kt | 36 ++++++++++ .../conversation/v2/ConversationFragment.kt | 7 -- .../securesms/database/RecipientTable.kt | 13 ++++ .../ui/addmembers/AddMembersActivity.java | 33 +++++++++- .../ui/addtogroup/AddToGroupsActivity.java | 4 ++ .../ui/creategroup/CreateGroupActivity.java | 29 ++++++++- .../securesms/invites/InviteActions.kt | 22 ++----- .../recipients/RecipientRepository.kt | 65 +++++++++++++++++++ .../recipients/ui/findby/FindByActivity.kt | 29 ++++++--- .../recipients/ui/findby/FindByResult.kt | 1 + .../recipients/ui/findby/FindByViewModel.kt | 44 ++----------- app/src/main/res/values/strings.xml | 2 + 15 files changed, 264 insertions(+), 126 deletions(-) create mode 100644 app/src/main/java/org/thoughtcrime/securesms/recipients/RecipientRepository.kt diff --git a/app/src/main/java/org/thoughtcrime/securesms/NewConversationActivity.java b/app/src/main/java/org/thoughtcrime/securesms/NewConversationActivity.java index 8f641702b3..8bc3d37d51 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/NewConversationActivity.java +++ b/app/src/main/java/org/thoughtcrime/securesms/NewConversationActivity.java @@ -50,6 +50,7 @@ import org.thoughtcrime.securesms.groups.ui.creategroup.CreateGroupActivity; import org.thoughtcrime.securesms.keyvalue.SignalStore; import org.thoughtcrime.securesms.recipients.Recipient; import org.thoughtcrime.securesms.recipients.RecipientId; +import org.thoughtcrime.securesms.recipients.RecipientRepository; import org.thoughtcrime.securesms.recipients.ui.findby.FindByActivity; import org.thoughtcrime.securesms.recipients.ui.findby.FindByMode; import org.thoughtcrime.securesms.util.CommunicationActions; @@ -132,33 +133,19 @@ public class NewConversationActivity extends ContactSelectionActivity AlertDialog progress = SimpleProgressDialog.show(this); - SimpleTask.run(getLifecycle(), () -> { - Recipient resolved = Recipient.external(this, number); - - if (!resolved.isRegistered() || !resolved.hasServiceId()) { - Log.i(TAG, "[onContactSelected] Not registered or no UUID. Doing a directory refresh."); - try { - ContactDiscovery.refresh(this, resolved, false, TimeUnit.SECONDS.toMillis(10)); - resolved = Recipient.resolved(resolved.getId()); - } catch (IOException e) { - Log.w(TAG, "[onContactSelected] Failed to refresh directory for new contact."); - return null; - } - } - - return resolved; - }, resolved -> { + SimpleTask.run(getLifecycle(), () -> RecipientRepository.lookupNewE164(this, number), result -> { progress.dismiss(); - if (resolved != null) { + if (result instanceof RecipientRepository.LookupResult.Success) { + Recipient resolved = Recipient.resolved(((RecipientRepository.LookupResult.Success) result).getRecipientId()); if (smsSupported || resolved.isRegistered() && resolved.hasServiceId()) { launch(resolved); - } else { - new MaterialAlertDialogBuilder(this) - .setMessage(getString(R.string.NewConversationActivity__s_is_not_a_signal_user, resolved.getDisplayName(this))) - .setPositiveButton(android.R.string.ok, null) - .show(); } + } else if (result instanceof RecipientRepository.LookupResult.NotFound || result instanceof RecipientRepository.LookupResult.InvalidEntry) { + new MaterialAlertDialogBuilder(this) + .setMessage(getString(R.string.NewConversationActivity__s_is_not_a_signal_user, number)) + .setPositiveButton(android.R.string.ok, null) + .show(); } else { new MaterialAlertDialogBuilder(this) .setMessage(R.string.NetworkFailure__network_error_check_your_connection_and_try_again) diff --git a/app/src/main/java/org/thoughtcrime/securesms/calls/new/NewCallActivity.kt b/app/src/main/java/org/thoughtcrime/securesms/calls/new/NewCallActivity.kt index 810f12686f..8e6ce68ff4 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/calls/new/NewCallActivity.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/calls/new/NewCallActivity.kt @@ -16,16 +16,14 @@ import org.thoughtcrime.securesms.ContactSelectionListFragment import org.thoughtcrime.securesms.InviteActivity import org.thoughtcrime.securesms.R import org.thoughtcrime.securesms.contacts.ContactSelectionDisplayMode -import org.thoughtcrime.securesms.contacts.sync.ContactDiscovery.refresh import org.thoughtcrime.securesms.keyvalue.SignalStore import org.thoughtcrime.securesms.recipients.Recipient import org.thoughtcrime.securesms.recipients.RecipientId +import org.thoughtcrime.securesms.recipients.RecipientRepository import org.thoughtcrime.securesms.util.CommunicationActions import org.thoughtcrime.securesms.util.views.SimpleProgressDialog -import java.io.IOException import java.util.Optional import java.util.function.Consumer -import kotlin.time.Duration.Companion.seconds class NewCallActivity : ContactSelectionActivity(), ContactSelectionListFragment.NewCallCallback { @@ -46,38 +44,36 @@ class NewCallActivity : ContactSelectionActivity(), ContactSelectionListFragment Log.i(TAG, "[onContactSelected] Maybe creating a new recipient.") if (SignalStore.account().isRegistered) { Log.i(TAG, "[onContactSelected] Doing contact refresh.") + val progress = SimpleProgressDialog.show(this) - SimpleTask.run(lifecycle, { - var resolved = Recipient.external(this, number!!) - if (!resolved.isRegistered || !resolved.hasServiceId()) { - Log.i(TAG, "[onContactSelected] Not registered or no UUID. Doing a directory refresh.") - resolved = try { - refresh(this, resolved, false, 10.seconds.inWholeMilliseconds) - Recipient.resolved(resolved.id) - } catch (e: IOException) { - Log.w(TAG, "[onContactSelected] Failed to refresh directory for new contact.") - return@run null - } - } - resolved - }) { resolved: Recipient? -> + + SimpleTask.run(lifecycle, { RecipientRepository.lookupNewE164(this, number!!) }, { result -> progress.dismiss() - if (resolved != null) { - if (resolved.isRegistered && resolved.hasServiceId()) { - launch(resolved) - } else { + + when (result) { + is RecipientRepository.LookupResult.Success -> { + val resolved = Recipient.resolved(result.recipientId) + if (resolved.isRegistered && resolved.hasServiceId()) { + launch(resolved) + } + } + + is RecipientRepository.LookupResult.NotFound, + is RecipientRepository.LookupResult.InvalidEntry -> { MaterialAlertDialogBuilder(this) - .setMessage(getString(R.string.NewConversationActivity__s_is_not_a_signal_user, resolved.getDisplayName(this))) + .setMessage(getString(R.string.NewConversationActivity__s_is_not_a_signal_user, number)) + .setPositiveButton(android.R.string.ok, null) + .show() + } + + else -> { + MaterialAlertDialogBuilder(this) + .setMessage(R.string.NetworkFailure__network_error_check_your_connection_and_try_again) .setPositiveButton(android.R.string.ok, null) .show() } - } else { - MaterialAlertDialogBuilder(this) - .setMessage(R.string.NetworkFailure__network_error_check_your_connection_and_try_again) - .setPositiveButton(android.R.string.ok, null) - .show() } - } + }) } } callback.accept(true) diff --git a/app/src/main/java/org/thoughtcrime/securesms/contacts/sync/ContactDiscovery.kt b/app/src/main/java/org/thoughtcrime/securesms/contacts/sync/ContactDiscovery.kt index 7a2ac68bc8..01df117c08 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/contacts/sync/ContactDiscovery.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/contacts/sync/ContactDiscovery.kt @@ -27,6 +27,7 @@ import org.thoughtcrime.securesms.registration.RegistrationUtil import org.thoughtcrime.securesms.storage.StorageSyncHelper import org.thoughtcrime.securesms.util.TextSecurePreferences import org.thoughtcrime.securesms.util.Util +import org.whispersystems.signalservice.api.push.ServiceId import org.whispersystems.signalservice.api.push.SignalServiceAddress import org.whispersystems.signalservice.api.util.UuidUtil import java.io.IOException @@ -112,6 +113,19 @@ object ContactDiscovery { } } + /** + * Looks up the PNI/ACI for an E164. Only creates a recipient if the number is in the CDS directory. + * Use sparingly! This will always use up the user's CDS quota. Always prefer other syncing methods for bulk lookups. + * + * Returns a [LookupResult] if the E164 is in the CDS directory, or null if it is not. + * Important: Just because a user is not in the directory does not mean they are not registered. They could have discoverability off. + */ + @Throws(IOException::class) + @WorkerThread + fun lookupE164(e164: String): LookupResult? { + return ContactDiscoveryRefreshV2.lookupE164(e164) + } + @JvmStatic @WorkerThread fun syncRecipientInfoWithSystemContacts(context: Context) { @@ -278,7 +292,7 @@ object ContactDiscovery { /** * Whether or not a session exists with the provided recipient. */ - fun hasSession(id: RecipientId): Boolean { + private fun hasSession(id: RecipientId): Boolean { val recipient = Recipient.resolved(id) if (!recipient.hasServiceId()) { @@ -295,4 +309,10 @@ object ContactDiscovery { val registeredIds: Set, val rewrites: Map ) + + data class LookupResult( + val recipientId: RecipientId, + val pni: ServiceId.PNI, + val aci: ServiceId.ACI? + ) } diff --git a/app/src/main/java/org/thoughtcrime/securesms/contacts/sync/ContactDiscoveryRefreshV2.kt b/app/src/main/java/org/thoughtcrime/securesms/contacts/sync/ContactDiscoveryRefreshV2.kt index 7f6661340a..ae0ea14e64 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/contacts/sync/ContactDiscoveryRefreshV2.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/contacts/sync/ContactDiscoveryRefreshV2.kt @@ -86,6 +86,42 @@ object ContactDiscoveryRefreshV2 { } } + @Throws(IOException::class) + @WorkerThread + @Synchronized + fun lookupE164(e164: String): ContactDiscovery.LookupResult? { + val response: CdsiV2Service.Response = try { + ApplicationDependencies.getSignalServiceAccountManager().getRegisteredUsersWithCdsi( + emptySet(), + setOf(e164), + SignalDatabase.recipients.getAllServiceIdProfileKeyPairs(), + Optional.empty(), + BuildConfig.CDSI_MRENCLAVE, + 10_000, + if (FeatureFlags.useLibsignalNetForCdsiLookup()) BuildConfig.LIBSIGNAL_NET_ENV else null + ) { + Log.i(TAG, "Ignoring token for one-off lookup.") + } + } catch (e: CdsiResourceExhaustedException) { + Log.w(TAG, "CDS resource exhausted! Can try again in ${e.retryAfterSeconds} seconds.") + SignalStore.misc().cdsBlockedUtil = System.currentTimeMillis() + e.retryAfterSeconds.seconds.inWholeMilliseconds + throw e + } catch (e: CdsiInvalidTokenException) { + Log.w(TAG, "We did not provide a token, but still got a token error! Unexpected, but ignoring.") + throw e + } + + return response.results[e164]?.let { item -> + val id = SignalDatabase.recipients.processIndividualCdsLookup(e164 = e164, aci = item.aci.orElse(null), pni = item.pni) + + ContactDiscovery.LookupResult( + recipientId = id, + pni = item.pni, + aci = item.aci?.orElse(null) + ) + } + } + @Throws(IOException::class) private fun refreshInternal( recipientE164s: Set, diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationFragment.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationFragment.kt index a1ff526db2..01ac0b3502 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationFragment.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationFragment.kt @@ -2867,11 +2867,8 @@ class ConversationFragment : } override fun onInviteToSignalClicked() { - val recipient = viewModel.recipientSnapshot ?: return InviteActions.inviteUserToSignal( requireContext(), - recipient, - binding.conversationInputPanel.embeddedTextEditor::appendInvite, this@ConversationFragment::startActivity ) } @@ -3328,8 +3325,6 @@ class ConversationFragment : InviteActions.inviteUserToSignal( context = requireContext(), - recipient = recipient, - appendInviteToComposer = composeText::appendInvite, launchIntent = this@ConversationFragment::startActivity ) } @@ -3742,8 +3737,6 @@ class ConversationFragment : override fun onInviteToSignal(recipient: Recipient) { InviteActions.inviteUserToSignal( context = requireContext(), - recipient = recipient, - appendInviteToComposer = null, launchIntent = this@ConversationFragment::startActivity ) } diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/RecipientTable.kt b/app/src/main/java/org/thoughtcrime/securesms/database/RecipientTable.kt index 2bb09b1e83..28232029ec 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/RecipientTable.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/database/RecipientTable.kt @@ -433,6 +433,15 @@ open class RecipientTable(context: Context, databaseHelper: SignalDatabase) : Da return readableDatabase.exists(TABLE_NAME).where("$ACI_COLUMN = ? AND $PNI_COLUMN = ?", serviceId.toString(), pni.toString()).run() } + fun getByE164IfRegisteredAndDiscoverable(e164: String): RecipientId? { + return readableDatabase + .select(ID) + .from(TABLE_NAME) + .where("$E164 = ? AND $REGISTERED = ${RegisteredState.REGISTERED.id} AND $PHONE_NUMBER_DISCOVERABLE = ${PhoneNumberDiscoverableState.DISCOVERABLE.id} AND ($PNI_COLUMN NOT NULL OR $ACI_COLUMN NOT NULL)", e164) + .run() + .readToSingleObject { RecipientId.from(it.requireLong(ID)) } + } + @JvmOverloads fun getAndPossiblyMerge(serviceId: ServiceId?, e164: String?, changeSelf: Boolean = false): RecipientId { require(serviceId != null || e164 != null) { "Must provide an ACI or E164!" } @@ -2267,6 +2276,10 @@ open class RecipientTable(context: Context, databaseHelper: SignalDatabase) : Da getAndPossiblyMerge(null, record.pni, record.e164) } + fun processIndividualCdsLookup(aci: ACI?, pni: PNI, e164: String): RecipientId { + return getAndPossiblyMerge(aci = aci, pni = pni, e164 = e164) + } + /** * Processes CDSv2 results, merging recipients as necessary. Does not mark users as * registered. diff --git a/app/src/main/java/org/thoughtcrime/securesms/groups/ui/addmembers/AddMembersActivity.java b/app/src/main/java/org/thoughtcrime/securesms/groups/ui/addmembers/AddMembersActivity.java index 846ce02030..1f7d7fb140 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/groups/ui/addmembers/AddMembersActivity.java +++ b/app/src/main/java/org/thoughtcrime/securesms/groups/ui/addmembers/AddMembersActivity.java @@ -8,11 +8,13 @@ import android.widget.Toast; import androidx.activity.result.ActivityResultLauncher; import androidx.annotation.NonNull; +import androidx.appcompat.app.AlertDialog; import androidx.lifecycle.ViewModelProvider; import com.google.android.material.dialog.MaterialAlertDialogBuilder; import org.signal.core.util.DimensionUnit; +import org.signal.core.util.concurrent.SimpleTask; import org.thoughtcrime.securesms.ContactSelectionActivity; import org.thoughtcrime.securesms.ContactSelectionListFragment; import org.thoughtcrime.securesms.PushContactSelectionActivity; @@ -21,9 +23,11 @@ import org.thoughtcrime.securesms.groups.GroupId; import org.thoughtcrime.securesms.groups.SelectionLimits; import org.thoughtcrime.securesms.recipients.Recipient; import org.thoughtcrime.securesms.recipients.RecipientId; +import org.thoughtcrime.securesms.recipients.RecipientRepository; import org.thoughtcrime.securesms.recipients.ui.findby.FindByActivity; import org.thoughtcrime.securesms.recipients.ui.findby.FindByMode; import org.thoughtcrime.securesms.util.Util; +import org.thoughtcrime.securesms.util.views.SimpleProgressDialog; import java.util.ArrayList; import java.util.Collections; @@ -104,9 +108,34 @@ public class AddMembersActivity extends PushContactSelectionActivity implements getContactFilterView().clear(); } - enableDone(); + if (recipientId.isPresent()) { + callback.accept(true); + enableDone(); + return; + } - callback.accept(true); + AlertDialog progress = SimpleProgressDialog.show(this); + + SimpleTask.run(getLifecycle(), () -> RecipientRepository.lookupNewE164(this, number), result -> { + progress.dismiss(); + + if (result instanceof RecipientRepository.LookupResult.Success) { + enableDone(); + callback.accept(true); + } else if (result instanceof RecipientRepository.LookupResult.NotFound || result instanceof RecipientRepository.LookupResult.InvalidEntry) { + new MaterialAlertDialogBuilder(this) + .setMessage(getString(R.string.NewConversationActivity__s_is_not_a_signal_user, number)) + .setPositiveButton(android.R.string.ok, null) + .show(); + callback.accept(false); + } else { + new MaterialAlertDialogBuilder(this) + .setMessage(R.string.NetworkFailure__network_error_check_your_connection_and_try_again) + .setPositiveButton(android.R.string.ok, null) + .show(); + callback.accept(false); + } + }); } @Override diff --git a/app/src/main/java/org/thoughtcrime/securesms/groups/ui/addtogroup/AddToGroupsActivity.java b/app/src/main/java/org/thoughtcrime/securesms/groups/ui/addtogroup/AddToGroupsActivity.java index 671fb9b711..4720470231 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/groups/ui/addtogroup/AddToGroupsActivity.java +++ b/app/src/main/java/org/thoughtcrime/securesms/groups/ui/addtogroup/AddToGroupsActivity.java @@ -8,17 +8,21 @@ import android.view.View; import android.widget.Toast; import androidx.annotation.NonNull; +import androidx.appcompat.app.AlertDialog; import androidx.lifecycle.ViewModelProvider; import com.annimon.stream.Stream; import com.google.android.material.dialog.MaterialAlertDialogBuilder; +import org.signal.core.util.concurrent.SimpleTask; import org.thoughtcrime.securesms.ContactSelectionActivity; import org.thoughtcrime.securesms.ContactSelectionListFragment; import org.thoughtcrime.securesms.R; import org.thoughtcrime.securesms.contacts.ContactSelectionDisplayMode; import org.thoughtcrime.securesms.groups.ui.addtogroup.AddToGroupViewModel.Event; import org.thoughtcrime.securesms.recipients.RecipientId; +import org.thoughtcrime.securesms.recipients.RecipientRepository; +import org.thoughtcrime.securesms.util.views.SimpleProgressDialog; import java.util.ArrayList; import java.util.Collections; diff --git a/app/src/main/java/org/thoughtcrime/securesms/groups/ui/creategroup/CreateGroupActivity.java b/app/src/main/java/org/thoughtcrime/securesms/groups/ui/creategroup/CreateGroupActivity.java index 37800e2d46..763c01c3b2 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/groups/ui/creategroup/CreateGroupActivity.java +++ b/app/src/main/java/org/thoughtcrime/securesms/groups/ui/creategroup/CreateGroupActivity.java @@ -9,6 +9,7 @@ import android.view.View; import androidx.activity.result.ActivityResultLauncher; import androidx.annotation.NonNull; import androidx.annotation.Nullable; +import androidx.appcompat.app.AlertDialog; import com.google.android.material.button.MaterialButton; import com.google.android.material.dialog.MaterialAlertDialogBuilder; @@ -27,6 +28,7 @@ import org.thoughtcrime.securesms.groups.ui.creategroup.details.AddGroupDetailsA import org.thoughtcrime.securesms.keyvalue.SignalStore; import org.thoughtcrime.securesms.recipients.Recipient; import org.thoughtcrime.securesms.recipients.RecipientId; +import org.thoughtcrime.securesms.recipients.RecipientRepository; import org.thoughtcrime.securesms.recipients.ui.findby.FindByActivity; import org.thoughtcrime.securesms.recipients.ui.findby.FindByMode; import org.thoughtcrime.securesms.util.FeatureFlags; @@ -117,7 +119,32 @@ public class CreateGroupActivity extends ContactSelectionActivity implements Con shrinkSkip(); - callback.accept(true); + if (recipientId.isPresent()) { + callback.accept(true); + return; + } + + AlertDialog progress = SimpleProgressDialog.show(this); + + SimpleTask.run(getLifecycle(), () -> RecipientRepository.lookupNewE164(this, number), result -> { + progress.dismiss(); + + if (result instanceof RecipientRepository.LookupResult.Success) { + callback.accept(true); + } else if (result instanceof RecipientRepository.LookupResult.NotFound || result instanceof RecipientRepository.LookupResult.InvalidEntry) { + new MaterialAlertDialogBuilder(this) + .setMessage(getString(R.string.NewConversationActivity__s_is_not_a_signal_user, number)) + .setPositiveButton(android.R.string.ok, null) + .show(); + callback.accept(false); + } else { + new MaterialAlertDialogBuilder(this) + .setMessage(R.string.NetworkFailure__network_error_check_your_connection_and_try_again) + .setPositiveButton(android.R.string.ok, null) + .show(); + callback.accept(false); + } + }); } @Override diff --git a/app/src/main/java/org/thoughtcrime/securesms/invites/InviteActions.kt b/app/src/main/java/org/thoughtcrime/securesms/invites/InviteActions.kt index ff7e38dba7..bc25e39238 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/invites/InviteActions.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/invites/InviteActions.kt @@ -5,10 +5,7 @@ import android.content.Intent import android.widget.Toast import androidx.annotation.MainThread import org.thoughtcrime.securesms.R -import org.thoughtcrime.securesms.keyvalue.SignalStore -import org.thoughtcrime.securesms.recipients.Recipient import org.thoughtcrime.securesms.util.CommunicationActions -import org.thoughtcrime.securesms.util.Util /** * Handles 'invite to signal' actions. @@ -25,29 +22,18 @@ object InviteActions { @MainThread fun inviteUserToSignal( context: Context, - recipient: Recipient, - appendInviteToComposer: ((String) -> Unit)?, launchIntent: (Intent) -> Unit ) { val inviteText = context.getString( R.string.ConversationActivity_lets_switch_to_signal, context.getString(R.string.install_url) ) + val intent = CommunicationActions.createIntentToShareTextViaShareSheet(inviteText) - if (appendInviteToComposer != null && Util.isDefaultSmsProvider(context) && SignalStore.misc().smsExportPhase.isSmsSupported()) { - appendInviteToComposer(inviteText) - } else if (recipient.hasSmsAddress()) { - launchIntent( - CommunicationActions.createIntentToComposeSmsThroughDefaultApp(recipient, inviteText) - ) + if (intent.resolveActivity(context.packageManager) != null) { + launchIntent(Intent.createChooser(intent, context.getString(R.string.InviteActivity_invite_to_signal))) } else { - val intent = CommunicationActions.createIntentToShareTextViaShareSheet(inviteText) - - if (intent.resolveActivity(context.packageManager) != null) { - launchIntent(Intent.createChooser(intent, context.getString(R.string.InviteActivity_invite_to_signal))) - } else { - Toast.makeText(context, R.string.InviteActivity_no_app_to_share_to, Toast.LENGTH_LONG).show() - } + Toast.makeText(context, R.string.InviteActivity_no_app_to_share_to, Toast.LENGTH_LONG).show() } } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/recipients/RecipientRepository.kt b/app/src/main/java/org/thoughtcrime/securesms/recipients/RecipientRepository.kt new file mode 100644 index 0000000000..db0f1e0caf --- /dev/null +++ b/app/src/main/java/org/thoughtcrime/securesms/recipients/RecipientRepository.kt @@ -0,0 +1,65 @@ +/* + * Copyright 2024 Signal Messenger, LLC + * SPDX-License-Identifier: AGPL-3.0-only + */ + +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 java.io.IOException + +/** + * We operate on recipients many places, but sometimes we find ourselves performing the same recipient-related operations in several locations. + * This is meant to be a place to put those common operations. + */ +object RecipientRepository { + + private val TAG = Log.tag(RecipientRepository::class.java) + + /** + * Attempts to lookup a potentially-new recipient by their e164. + * We will check locally first for a potential match, but may end up hitting the network. + * This will not create a new recipient if we could not find it in the CDSI directory. + */ + @WorkerThread + @JvmStatic + fun lookupNewE164(context: Context, inputE164: String): LookupResult { + val e164 = PhoneNumberFormatter.get(context).format(inputE164) + + if (!NumberUtil.isVisuallyValidNumber(e164)) { + return LookupResult.InvalidEntry + } + + val matchingFullRecipientId = SignalDatabase.recipients.getByE164IfRegisteredAndDiscoverable(e164) + if (matchingFullRecipientId != null) { + Log.i(TAG, "Already have a full, discoverable recipient for $e164. $matchingFullRecipientId") + return LookupResult.Success(matchingFullRecipientId) + } + + Log.i(TAG, "Need to lookup up $e164 with CDSI.") + + return try { + val result = ContactDiscovery.lookupE164(e164) + if (result == null) { + LookupResult.NotFound() + } else { + LookupResult.Success(result.recipientId) + } + } catch (e: IOException) { + return LookupResult.NetworkError + } + } + + sealed interface LookupResult { + data class Success(val recipientId: RecipientId) : LookupResult + object InvalidEntry : LookupResult + data class NotFound(val recipientId: RecipientId = RecipientId.UNKNOWN) : LookupResult + object NetworkError : LookupResult + } +} diff --git a/app/src/main/java/org/thoughtcrime/securesms/recipients/ui/findby/FindByActivity.kt b/app/src/main/java/org/thoughtcrime/securesms/recipients/ui/findby/FindByActivity.kt index 576ef8089c..ab8857e64b 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/recipients/ui/findby/FindByActivity.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/recipients/ui/findby/FindByActivity.kt @@ -84,7 +84,6 @@ import org.thoughtcrime.securesms.components.settings.app.usernamelinks.main.Use import org.thoughtcrime.securesms.invites.InviteActions import org.thoughtcrime.securesms.permissions.compose.Permissions import org.thoughtcrime.securesms.phonenumbers.PhoneNumberVisualTransformation -import org.thoughtcrime.securesms.recipients.Recipient import org.thoughtcrime.securesms.recipients.RecipientId import org.thoughtcrime.securesms.registration.util.CountryPrefix import org.thoughtcrime.securesms.util.viewModel @@ -161,6 +160,7 @@ class FindByActivity : PassphraseRequiredActivity() { FindByResult.InvalidEntry -> navController.navigate("invalid-entry") is FindByResult.NotFound -> navController.navigate("not-found/${result.recipientId.toLong()}") + is FindByResult.NetworkError -> navController.navigate("network-error") } } }, @@ -220,6 +220,16 @@ class FindByActivity : PassphraseRequiredActivity() { ) } + dialog( + route = "network-error" + ) { + Dialogs.SimpleMessageDialog( + message = getString(R.string.FindByActivity__network_error_dialog), + dismiss = getString(android.R.string.ok), + onDismiss = { navController.popBackStack() } + ) + } + dialog( route = "not-found/{recipientId}", arguments = listOf(navArgument("recipientId") { type = NavType.LongType }) @@ -260,15 +270,10 @@ class FindByActivity : PassphraseRequiredActivity() { dismiss = dismiss, onConfirm = { if (state.mode == FindByMode.PHONE_NUMBER) { - val recipientId = navBackStackEntry.arguments?.getLong("recipientId")?.takeIf { it > 0 }?.let { RecipientId.from(it) } ?: RecipientId.UNKNOWN - if (recipientId != RecipientId.UNKNOWN) { - InviteActions.inviteUserToSignal( - context, - Recipient.resolved(recipientId), - null, - this@FindByActivity::startActivity - ) - } + InviteActions.inviteUserToSignal( + context, + this@FindByActivity::startActivity + ) } }, onDismiss = { navController.popBackStack() } @@ -429,6 +434,10 @@ private fun Content( } } + if (state.isLookupInProgress) { + Dialogs.IndeterminateProgressDialog() + } + LaunchedEffect(Unit) { focusRequester.requestFocus() } diff --git a/app/src/main/java/org/thoughtcrime/securesms/recipients/ui/findby/FindByResult.kt b/app/src/main/java/org/thoughtcrime/securesms/recipients/ui/findby/FindByResult.kt index f845a54629..19547ad8f0 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/recipients/ui/findby/FindByResult.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/recipients/ui/findby/FindByResult.kt @@ -11,4 +11,5 @@ sealed interface FindByResult { data class Success(val recipientId: RecipientId) : FindByResult object InvalidEntry : FindByResult data class NotFound(val recipientId: RecipientId = RecipientId.UNKNOWN) : FindByResult + object NetworkError : FindByResult } diff --git a/app/src/main/java/org/thoughtcrime/securesms/recipients/ui/findby/FindByViewModel.kt b/app/src/main/java/org/thoughtcrime/securesms/recipients/ui/findby/FindByViewModel.kt index fdc392efcb..f31ca2e091 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/recipients/ui/findby/FindByViewModel.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/recipients/ui/findby/FindByViewModel.kt @@ -14,14 +14,11 @@ import androidx.lifecycle.viewModelScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.async import org.signal.core.util.concurrent.safeBlockingGet -import org.thoughtcrime.securesms.contacts.sync.ContactDiscovery -import org.thoughtcrime.securesms.phonenumbers.NumberUtil import org.thoughtcrime.securesms.profiles.manage.UsernameRepository import org.thoughtcrime.securesms.recipients.Recipient +import org.thoughtcrime.securesms.recipients.RecipientRepository import org.thoughtcrime.securesms.registration.util.CountryPrefix import org.thoughtcrime.securesms.util.UsernameUtil -import org.whispersystems.signalservice.api.util.PhoneNumberFormatter -import java.util.concurrent.TimeUnit class FindByViewModel( mode: FindByMode @@ -86,40 +83,13 @@ class FindByViewModel( val countryCode = stateSnapshot.selectedCountryPrefix.digits val nationalNumber = stateSnapshot.userEntry.removePrefix(countryCode.toString()) - val e164 = "$countryCode$nationalNumber" + val e164 = "+$countryCode$nationalNumber" - if (!NumberUtil.isVisuallyValidNumber(e164)) { - return FindByResult.InvalidEntry - } - - val recipient = try { - Recipient.external(context, e164) - } catch (e: Exception) { - return FindByResult.InvalidEntry - } - - return if (!recipient.isRegistered || !recipient.hasServiceId()) { - try { - ContactDiscovery.refresh(context, recipient, false, TimeUnit.SECONDS.toMillis(10)) - val resolved = Recipient.resolved(recipient.id) - if (!resolved.isRegistered) { - if (PhoneNumberFormatter.isValidNumber(nationalNumber, countryCode.toString())) { - FindByResult.NotFound(recipient.id) - } else { - FindByResult.InvalidEntry - } - } else { - FindByResult.Success(recipient.id) - } - } catch (e: Exception) { - if (PhoneNumberFormatter.isValidNumber(nationalNumber, countryCode.toString())) { - FindByResult.NotFound(recipient.id) - } else { - FindByResult.InvalidEntry - } - } - } else { - FindByResult.Success(recipient.id) + return when (val result = RecipientRepository.lookupNewE164(context, e164)) { + RecipientRepository.LookupResult.InvalidEntry -> FindByResult.InvalidEntry + RecipientRepository.LookupResult.NetworkError -> FindByResult.NetworkError + is RecipientRepository.LookupResult.NotFound -> FindByResult.NotFound(result.recipientId) + is RecipientRepository.LookupResult.Success -> FindByResult.Success(result.recipientId) } } } diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 4ae4d09760..6c774f46d8 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -6646,6 +6646,8 @@ Invite Scan QR code + + Encountered a network error. Try again later.