Fix external apps opening inside Signal's task - #14988
Closed
theabhishekchandra wants to merge 1 commit into
Closed
Conversation
Fixes signalapp#14946 Tapping a link that a non-browser app handles (e.g. x.com with the X app installed) launches that app's activity into Signal's own task. Recents then shows a single card carrying Signal's icon and label whose contents are the other app, and the other app sits on top of Signal's back stack rather than beside it. Three separate paths hand a URL to another app, and none set FLAG_ACTIVITY_NEW_TASK: - LinkActions.openUrl, reached from CommunicationActions.openBrowserLink (~71 call sites). It set the flag only when the context was not an Activity. That condition comes from 26cb17e "Fix browser not opening in certain contexts", which added it because Android *requires* the flag from a non-Activity context. It was never a decision about task placement; on the Activity path the flag was simply absent. - ConversationFragment.openLink, used by onUrlClicked and onLinkPreviewClicked, which builds its own intent and never goes through LinkActions. The Browser.EXTRA_CREATE_NEW_TAB / EXTRA_APPLICATION_ID extras it sets only influence browsers, so they do nothing for a link claimed by a non-browser app. - LongClickCopySpan, which did not override onClick and so inherited URLSpan's bare startActivity. This is the click target wherever the UrlClickHandler declines: the story viewer (which uses the span directly), the long-message view, message details and starred messages. Set FLAG_ACTIVITY_NEW_TASK on all three so the handling app gets its own task, as it would when the same link is opened anywhere else on the device. LongClickCopySpan now routes through openBrowserLink rather than re-implementing the launch, so the number of parallel link launchers goes down rather than up. Renamed encourageNewBrowserTab() to prepareExternalLink(), since it no longer only concerns browser tabs. Note this is only observable when a non-browser app handles the link. Chrome's ChromeTabbedActivity is singleTask and always takes its own task regardless of the flag, so browser links are unaffected either way. Signal's own https handlers, StickerPackPreviewActivity and DeepLinkEntryActivity, both use the default taskAffinity, so a signal.me or signal.art link still resolves into the existing Signal task rather than creating a second one. Added LinkActionsTest and LongClickCopySpanTest; the flag assertions fail against current main.
theabhishekchandra
force-pushed
the
fix/external-links-own-task
branch
from
September 3, 2026 20:36
a21ebef to
6175509
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
First time contributor checklist
Contributor checklist
signal.orgwithlaunchMode="standard"and its owntaskAffinityto stand in for X, and captured before/after against real builds ofmainwith and without this patch.Fixes #1234syntaxDescription
Tapping a link handled by a non-browser app launches that app into Signal's task. Recents shows one card with Signal's icon and label whose contents are the other app, and the other app lands on top of Signal's back stack instead of beside it.
Same device, same tap on a link, only this patch differing. On the left the single Recents card is labelled Signal (Staging) but its contents are the other app; on the right the other app has its own card and Signal keeps its own. The red screen is the stub standing in for X.
Three paths hand a URL to another app, and none set
FLAG_ACTIVITY_NEW_TASK:LinkActions.openUrl— viaCommunicationActions.openBrowserLink(~71 call sites). It set the flag only when the context was not an Activity. That guard comes from 26cb17e ("Fix browser not opening in certain contexts"), added because Android requires the flag from a non-Activity context. It was never a decision about task placement — on the Activity path the flag was just missing.ConversationFragment.openLink— used byonUrlClickedandonLinkPreviewClicked. Builds its own intent and never goes throughLinkActions. TheBrowser.EXTRA_CREATE_NEW_TAB/EXTRA_APPLICATION_IDextras only influence browsers, so they do nothing for a link a non-browser app has registered to handle.LongClickCopySpan— never overrodeonClick, so it inheritedURLSpan's barestartActivity. This is the click target wherever theUrlClickHandlerdeclines: the story viewer (which uses the span directly, with no handler), the long-message view, message details and starred messages.LongClickCopySpannow delegates toopenBrowserLinkinstead of re-implementing the launch, so this reduces the number of parallel link launchers rather than adding another.encourageNewBrowserTab()is renamedprepareExternalLink(), since it no longer only concerns browser tabs.Scope. Only observable when a non-browser app handles the link. Chrome's
ChromeTabbedActivityissingleTaskand always takes its own task regardless of the flag, so browser links behave identically before and after.Why
NEW_TASKis safe hereStickerPackPreviewActivity,DeepLinkEntryActivity) both use the defaulttaskAffinity, so asignal.me/signal.artlink reuses the existing Signal task rather than creating a second Signal card.Verification
Unit tests in
LinkActionsTestandLongClickCopySpanTest; the flag assertions fail against currentmainand pass with this change (confirmed by stashing each fix independently).On device (Vivo V2036, Android 13), same build and same tap, only the patch differing:
mainsz=2)0x00x10000000On
mainthe single Recents card is labelled "Signal" but its contents are the other app, and Back from it lands on Signal's own screen. With the fix there are two cards.