From 5193dfa41254bc1b5dfe2340371357edcfb50e9a Mon Sep 17 00:00:00 2001 From: adbenitez Date: Fri, 1 May 2026 15:38:06 +0200 Subject: [PATCH 1/3] allow to open message links via actions in TalkBack menu --- .../securesms/ConversationItem.java | 36 +++++++++++++++++++ src/main/res/values/strings.xml | 3 ++ 2 files changed, 39 insertions(+) diff --git a/src/main/java/org/thoughtcrime/securesms/ConversationItem.java b/src/main/java/org/thoughtcrime/securesms/ConversationItem.java index a3c6a0ba6..f58609b62 100644 --- a/src/main/java/org/thoughtcrime/securesms/ConversationItem.java +++ b/src/main/java/org/thoughtcrime/securesms/ConversationItem.java @@ -24,6 +24,7 @@ import android.graphics.PorterDuff; import android.graphics.Rect; import android.os.Build; import android.text.SpannableString; +import android.text.Spanned; import android.text.TextUtils; import android.util.AttributeSet; import android.util.Log; @@ -36,6 +37,7 @@ import androidx.annotation.DimenRes; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.appcompat.app.AlertDialog; +import androidx.core.view.ViewCompat; import chat.delta.rpc.RpcException; import chat.delta.rpc.types.CallInfo; import chat.delta.rpc.types.CallState; @@ -44,6 +46,7 @@ import chat.delta.rpc.types.VcardContact; import com.b44t.messenger.DcChat; import com.b44t.messenger.DcContact; import com.b44t.messenger.DcMsg; +import java.util.ArrayList; import java.util.List; import java.util.Set; import org.thoughtcrime.securesms.calls.CallCoordinator; @@ -72,6 +75,7 @@ import org.thoughtcrime.securesms.mms.VcardSlide; import org.thoughtcrime.securesms.reactions.ReactionsConversationView; import org.thoughtcrime.securesms.recipients.Recipient; import org.thoughtcrime.securesms.util.Linkifier; +import org.thoughtcrime.securesms.util.LongClickCopySpan; import org.thoughtcrime.securesms.util.LongClickMovementMethod; import org.thoughtcrime.securesms.util.MediaUtil; import org.thoughtcrime.securesms.util.Util; @@ -119,6 +123,9 @@ public class ConversationItem extends BaseConversationItem { private Stub callViewStub; private @Nullable EventListener eventListener; + // IDs of accessibility actions registered via ViewCompat.addAccessibilityAction + private final List linkActionIds = new ArrayList<>(); + private int measureCalls; private int incomingBubbleColor; @@ -423,6 +430,12 @@ public class ConversationItem extends BaseConversationItem { bodyText.setClickable(false); bodyText.setFocusable(false); + // Remove any link actions registered for the previous message binding + for (int id : linkActionIds) { + ViewCompat.removeAccessibilityAction(this, id); + } + linkActionIds.clear(); + String text = messageRecord.getText(); if (messageRecord.getType() == DcMsg.DC_MSG_CALL || text.isEmpty()) { @@ -434,6 +447,29 @@ public class ConversationItem extends BaseConversationItem { } bodyText.setText(spannable); bodyText.setVisibility(View.VISIBLE); + + // Register a TalkBack "Actions" entry for each link in the message + if (spannable instanceof Spanned) { + Spanned spanned = (Spanned) spannable; + final TextView tv = bodyText; + for (LongClickCopySpan span : + spanned.getSpans(0, spanned.length(), LongClickCopySpan.class)) { + int start = spanned.getSpanStart(span); + int end = spanned.getSpanEnd(span); + if (start >= 0 && end > start && end <= spanned.length()) { + String linkText = spanned.subSequence(start, end).toString(); + String label = context.getString(R.string.accessibility_link_action, linkText); + linkActionIds.add( + ViewCompat.addAccessibilityAction( + this, + label, + (v, args) -> { + span.onClick(tv); + return true; + })); + } + } + } } int downloadState = messageRecord.getDownloadState(); diff --git a/src/main/res/values/strings.xml b/src/main/res/values/strings.xml index 4fbb496c6..710d39e34 100644 --- a/src/main/res/values/strings.xml +++ b/src/main/res/values/strings.xml @@ -1217,6 +1217,9 @@ Tap here to receive messages while Delta Chat is in the background. You already allowed Delta Chat to receive messages in the background.\n\nIf messages still do not arrive in background, please also check your system settings. + + Open %1$s + What\'s new?\n\n💯 End-to-end encryption is reliable and forever now. Padlocks 🔒 are gone!\n\n✉️ Classic email without end-to-end encryption is marked with a letter symbol\n\n😻 New enhanced profile screen for all your contacts\n\n🔲 New button for quick access to apps used in a chat\n\n❤️ Please donate to help us remain independent and continue to bring improvements: %1$s From 5d0e85b30dfba1dec64d548f34a0dfbf58204def Mon Sep 17 00:00:00 2001 From: adbenitez Date: Fri, 1 May 2026 15:41:06 +0200 Subject: [PATCH 2/3] update changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9a7c1acf8..c8fd7a118 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,7 @@ * Mark chats as unread (long tap a chat and select the corresponding option from the three-dot-menu) * Fix process of upgrading from a very old version of the app * Show more recent added stickers at the top of the sticker picker +* Allow to open links in messages via actions in TalkBack menu ## v2.49.0 2026-04 From 875f0e5c86a397ba93e9594969cab9605934c99e Mon Sep 17 00:00:00 2001 From: adbenitez Date: Mon, 4 May 2026 20:13:37 +0200 Subject: [PATCH 3/3] remove unnecessary check --- .../securesms/ConversationItem.java | 36 +++++++++---------- 1 file changed, 17 insertions(+), 19 deletions(-) diff --git a/src/main/java/org/thoughtcrime/securesms/ConversationItem.java b/src/main/java/org/thoughtcrime/securesms/ConversationItem.java index f58609b62..4ead84239 100644 --- a/src/main/java/org/thoughtcrime/securesms/ConversationItem.java +++ b/src/main/java/org/thoughtcrime/securesms/ConversationItem.java @@ -449,25 +449,23 @@ public class ConversationItem extends BaseConversationItem { bodyText.setVisibility(View.VISIBLE); // Register a TalkBack "Actions" entry for each link in the message - if (spannable instanceof Spanned) { - Spanned spanned = (Spanned) spannable; - final TextView tv = bodyText; - for (LongClickCopySpan span : - spanned.getSpans(0, spanned.length(), LongClickCopySpan.class)) { - int start = spanned.getSpanStart(span); - int end = spanned.getSpanEnd(span); - if (start >= 0 && end > start && end <= spanned.length()) { - String linkText = spanned.subSequence(start, end).toString(); - String label = context.getString(R.string.accessibility_link_action, linkText); - linkActionIds.add( - ViewCompat.addAccessibilityAction( - this, - label, - (v, args) -> { - span.onClick(tv); - return true; - })); - } + Spanned spanned = (Spanned) spannable; + final TextView tv = bodyText; + for (LongClickCopySpan span : + spanned.getSpans(0, spanned.length(), LongClickCopySpan.class)) { + int start = spanned.getSpanStart(span); + int end = spanned.getSpanEnd(span); + if (start >= 0 && end > start && end <= spanned.length()) { + String linkText = spanned.subSequence(start, end).toString(); + String label = context.getString(R.string.accessibility_link_action, linkText); + linkActionIds.add( + ViewCompat.addAccessibilityAction( + this, + label, + (v, args) -> { + span.onClick(tv); + return true; + })); } } }