diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/items/V2ConversationItemUtils.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/items/V2ConversationItemUtils.kt index df6a932b88..3f577e87a3 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/items/V2ConversationItemUtils.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/items/V2ConversationItemUtils.kt @@ -11,10 +11,10 @@ import android.text.Spanned import android.text.style.URLSpan import android.text.util.Linkify import androidx.core.text.util.LinkifyCompat +import org.signal.core.util.addDetectedLinks import org.thoughtcrime.securesms.database.model.MessageRecord import org.thoughtcrime.securesms.util.InterceptableLongClickCopyLinkSpan import org.thoughtcrime.securesms.util.LinkUtil -import org.thoughtcrime.securesms.util.Linkification import org.thoughtcrime.securesms.util.UrlClickHandler import org.thoughtcrime.securesms.util.hasOnlyThumbnail @@ -34,7 +34,7 @@ object V2ConversationItemUtils { } LinkifyCompat.addLinks(messageBody, Linkify.EMAIL_ADDRESSES or Linkify.PHONE_NUMBERS) - Linkification.applyWebUrlSpans(messageBody) + messageBody.addDetectedLinks() messageBody.getSpans(0, messageBody.length, URLSpan::class.java).forEach { urlSpan -> val url = urlSpan.url diff --git a/app/src/main/java/org/thoughtcrime/securesms/groups/v2/GroupDescriptionUtil.java b/app/src/main/java/org/thoughtcrime/securesms/groups/v2/GroupDescriptionUtil.java index 974901600b..8a197670b4 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/groups/v2/GroupDescriptionUtil.java +++ b/app/src/main/java/org/thoughtcrime/securesms/groups/v2/GroupDescriptionUtil.java @@ -15,10 +15,10 @@ import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.core.text.util.LinkifyCompat; +import org.signal.core.util.LinkifierSpannableExtensionsKt; import org.thoughtcrime.securesms.R; import org.thoughtcrime.securesms.components.emoji.EmojiTextView; import org.thoughtcrime.securesms.util.LinkUtil; -import org.thoughtcrime.securesms.util.Linkification; import org.thoughtcrime.securesms.util.LongClickCopySpan; public final class GroupDescriptionUtil { @@ -38,7 +38,7 @@ public final class GroupDescriptionUtil { if (linkify) { LinkifyCompat.addLinks(descriptionSpannable, Linkify.EMAIL_ADDRESSES | Linkify.PHONE_NUMBERS); - Linkification.applyWebUrlSpans(descriptionSpannable); + LinkifierSpannableExtensionsKt.addDetectedLinks(descriptionSpannable); for (URLSpan urlSpan : descriptionSpannable.getSpans(0, descriptionSpannable.length(), URLSpan.class)) { String url = urlSpan.getURL(); diff --git a/app/src/main/java/org/thoughtcrime/securesms/linkpreview/LinkPreviewUtil.java b/app/src/main/java/org/thoughtcrime/securesms/linkpreview/LinkPreviewUtil.java index fc7ad779ba..e9a107ed9f 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/linkpreview/LinkPreviewUtil.java +++ b/app/src/main/java/org/thoughtcrime/securesms/linkpreview/LinkPreviewUtil.java @@ -11,7 +11,6 @@ import java.util.stream.Collectors; import org.signal.core.util.Linkifier; import org.thoughtcrime.securesms.util.DateUtils; import org.thoughtcrime.securesms.util.LinkUtil; -import org.thoughtcrime.securesms.util.Linkification; import org.signal.core.util.Util; import org.whispersystems.signalservice.api.util.OptionalUtil; @@ -51,7 +50,7 @@ public final class LinkPreviewUtil { * @return All URLs allowed as previews in the source text. */ public static @NonNull Links findValidPreviewUrls(@NonNull String text) { - List detected = Linkification.findWebLinks(text); + List detected = Linkifier.findLinks(text); if (detected.isEmpty()) { return Links.EMPTY; } diff --git a/app/src/main/java/org/thoughtcrime/securesms/logsubmit/SubmitDebugLogActivity.java b/app/src/main/java/org/thoughtcrime/securesms/logsubmit/SubmitDebugLogActivity.java index 503c2201c3..efa680ff95 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/logsubmit/SubmitDebugLogActivity.java +++ b/app/src/main/java/org/thoughtcrime/securesms/logsubmit/SubmitDebugLogActivity.java @@ -34,7 +34,6 @@ import org.thoughtcrime.securesms.components.ConversationSearchBottomBar; import org.thoughtcrime.securesms.components.ProgressCard; import org.thoughtcrime.securesms.components.SearchView; import org.thoughtcrime.securesms.util.DynamicTheme; -import org.thoughtcrime.securesms.util.Linkification; import org.thoughtcrime.securesms.util.LongClickCopySpan; import org.thoughtcrime.securesms.util.LongClickMovementMethod; import org.signal.core.ui.util.ThemeUtil; @@ -458,7 +457,7 @@ public class SubmitDebugLogActivity extends BaseActivity { TextView dialogView = new TextView(builder.getContext()); LongClickCopySpan longClickUrl = new LongClickCopySpan(url); - for (Linkifier.DetectedLink link : Linkification.findWebLinks(dialogText)) { + for (Linkifier.DetectedLink link : Linkifier.findLinks(dialogText)) { spannableDialogText.setSpan(longClickUrl, link.getStart(), link.getEnd(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); } diff --git a/app/src/main/java/org/thoughtcrime/securesms/nicknames/ViewNoteSheet.kt b/app/src/main/java/org/thoughtcrime/securesms/nicknames/ViewNoteSheet.kt index c95227ae9c..82fb5a4586 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/nicknames/ViewNoteSheet.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/nicknames/ViewNoteSheet.kt @@ -37,11 +37,11 @@ import org.signal.core.ui.compose.ComposeBottomSheetDialogFragment import org.signal.core.ui.compose.DayNightPreviews import org.signal.core.ui.compose.Previews import org.signal.core.ui.compose.SignalIcons +import org.signal.core.util.addDetectedLinks import org.signal.core.util.getParcelableCompat import org.thoughtcrime.securesms.R import org.thoughtcrime.securesms.components.emoji.EmojiTextView import org.thoughtcrime.securesms.recipients.RecipientId -import org.thoughtcrime.securesms.util.Linkification import org.thoughtcrime.securesms.util.viewModel import org.signal.core.ui.R as CoreUiR @@ -163,7 +163,7 @@ private fun ViewNoteBottomSheetContent( if (!isInspection) { LinkifyCompat.addLinks(spannable, Linkify.EMAIL_ADDRESSES or Linkify.PHONE_NUMBERS) } - Linkification.applyWebUrlSpans(spannable) + spannable.addDetectedLinks() it.text = spannable } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/stories/viewer/page/StoryViewerPageFragment.kt b/app/src/main/java/org/thoughtcrime/securesms/stories/viewer/page/StoryViewerPageFragment.kt index 648cb1fb18..64feded401 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/stories/viewer/page/StoryViewerPageFragment.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/stories/viewer/page/StoryViewerPageFragment.kt @@ -52,6 +52,7 @@ import org.signal.core.ui.permissions.Permissions import org.signal.core.util.Debouncer import org.signal.core.util.DimensionUnit import org.signal.core.util.ServiceUtil +import org.signal.core.util.addDetectedLinks import org.signal.core.util.concurrent.LifecycleDisposable import org.signal.core.util.dp import org.signal.core.util.getParcelableCompat @@ -95,7 +96,6 @@ import org.thoughtcrime.securesms.stories.viewer.views.StoryViewsBottomSheetDial import org.thoughtcrime.securesms.util.AvatarUtil import org.thoughtcrime.securesms.util.DateUtils import org.thoughtcrime.securesms.util.LinkUtil -import org.thoughtcrime.securesms.util.Linkification import org.thoughtcrime.securesms.util.LongClickCopySpan import org.thoughtcrime.securesms.util.LongClickMovementMethod import org.thoughtcrime.securesms.util.Projection @@ -995,7 +995,7 @@ class StoryViewerPageFragment : fun linkifyUrlLinks(spannable: Spannable) { LinkifyCompat.addLinks(spannable, Linkify.EMAIL_ADDRESSES or Linkify.PHONE_NUMBERS) - Linkification.applyWebUrlSpans(spannable) + spannable.addDetectedLinks() spannable.getSpans(0, spannable.length, URLSpan::class.java).forEach { urlSpan -> val url = urlSpan.url diff --git a/app/src/main/java/org/thoughtcrime/securesms/util/Linkification.kt b/app/src/main/java/org/thoughtcrime/securesms/util/Linkification.kt deleted file mode 100644 index 9f6edf3cdb..0000000000 --- a/app/src/main/java/org/thoughtcrime/securesms/util/Linkification.kt +++ /dev/null @@ -1,57 +0,0 @@ -/* - * Copyright 2026 Signal Messenger, LLC - * SPDX-License-Identifier: AGPL-3.0-only - */ - -package org.thoughtcrime.securesms.util - -import android.text.Spannable -import android.text.SpannableString -import android.text.style.URLSpan -import android.text.util.Linkify -import androidx.core.text.util.LinkifyCompat -import org.signal.core.util.Linkifier -import org.signal.core.util.Linkifier.DetectedLink -import org.signal.core.util.addDetectedLinks - -/** - * Temporary abstraction while we switch over to the new [Linkifier] via remote config. - * When the remote config is off, we fallback to the pre-existing android link logic. - */ -object Linkification { - - /** - * Adds [URLSpan]s for web URLs in [spannable]. Returns `true` if at least one span was added. - */ - @JvmStatic - fun applyWebUrlSpans(spannable: Spannable): Boolean { - return if (RemoteConfig.useNewLinkifier) { - spannable.addDetectedLinks() - } else { - LinkifyCompat.addLinks(spannable, Linkify.WEB_URLS) - } - } - - /** - * Finds web URLs in [text]. - */ - @JvmStatic - fun findWebLinks(text: CharSequence): List { - if (RemoteConfig.useNewLinkifier) { - return Linkifier.findLinks(text) - } - - val spannable = SpannableString(text) - if (!LinkifyCompat.addLinks(spannable, Linkify.WEB_URLS)) { - return emptyList() - } - - return spannable.getSpans(0, spannable.length, URLSpan::class.java).map { span -> - DetectedLink( - start = spannable.getSpanStart(span), - end = spannable.getSpanEnd(span), - url = span.url - ) - } - } -} diff --git a/app/src/main/java/org/thoughtcrime/securesms/util/RemoteConfig.kt b/app/src/main/java/org/thoughtcrime/securesms/util/RemoteConfig.kt index a775fee9e9..4fff871e4b 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/util/RemoteConfig.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/util/RemoteConfig.kt @@ -1402,17 +1402,6 @@ object RemoteConfig { hotSwappable = true ) - /** - * Whether to use our custom [org.signal.core.util.Linkifier] for web URL detection. - */ - @JvmStatic - @get:JvmName("useNewLinkifier") - val useNewLinkifier: Boolean by remoteBoolean( - key = "android.useNewLinkifier", - defaultValue = false, - hotSwappable = true - ) - /** * Whether screen sharing is available during calls. */ diff --git a/app/src/test/java/org/thoughtcrime/securesms/linkpreview/LinkPreviewUtilTest_findValidPreviewUrls.kt b/app/src/test/java/org/thoughtcrime/securesms/linkpreview/LinkPreviewUtilTest_findValidPreviewUrls.kt index 8503401e42..5c4c780fc5 100644 --- a/app/src/test/java/org/thoughtcrime/securesms/linkpreview/LinkPreviewUtilTest_findValidPreviewUrls.kt +++ b/app/src/test/java/org/thoughtcrime/securesms/linkpreview/LinkPreviewUtilTest_findValidPreviewUrls.kt @@ -1,34 +1,17 @@ package org.thoughtcrime.securesms.linkpreview import android.app.Application -import io.mockk.every -import io.mockk.mockkStatic -import io.mockk.unmockkStatic -import org.junit.After import org.junit.Assert -import org.junit.Before import org.junit.Test import org.junit.runner.RunWith import org.robolectric.RobolectricTestRunner import org.robolectric.annotation.Config -import org.thoughtcrime.securesms.util.RemoteConfig @Suppress("ClassName") @RunWith(RobolectricTestRunner::class) @Config(manifest = Config.NONE, application = Application::class) class LinkPreviewUtilTest_findValidPreviewUrls { - @Before - fun setup() { - mockkStatic(RemoteConfig::class) - every { RemoteConfig.useNewLinkifier } returns true - } - - @After - fun tearDown() { - unmockkStatic(RemoteConfig::class) - } - @Test fun no_links() { val links = LinkPreviewUtil.findValidPreviewUrls("No links")