Skip to content

Fix external apps opening inside Signal's task - #14988

Closed
theabhishekchandra wants to merge 1 commit into
signalapp:mainfrom
theabhishekchandra:fix/external-links-own-task
Closed

Fix external apps opening inside Signal's task#14988
theabhishekchandra wants to merge 1 commit into
signalapp:mainfrom
theabhishekchandra:fix/external-links-own-task

Conversation

@theabhishekchandra

@theabhishekchandra theabhishekchandra commented Sep 3, 2026

Copy link
Copy Markdown
Contributor

First time contributor checklist

Contributor checklist

  • I am following the Code Style Guidelines
  • I have tested my contribution on these devices:
    • Vivo V2036, Android 13. The reporter's setup (X installed, Android 16) isn't directly reproducible here — no non-browser app on this device claims https links, and Chrome is immune — so I built a minimal stub registered for signal.org with launchMode="standard" and its own taskAffinity to stand in for X, and captured before/after against real builds of main with and without this patch.
  • My contribution is fully baked and ready to be merged as is
  • I ensure that all the open issues my contribution fixes are mentioned in the commit message of my first commit using the Fixes #1234 syntax

Description

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:

  1. LinkActions.openUrl — via CommunicationActions.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.

  2. ConversationFragment.openLink — used by onUrlClicked and onLinkPreviewClicked. Builds its own intent and never goes through LinkActions. The Browser.EXTRA_CREATE_NEW_TAB / EXTRA_APPLICATION_ID extras only influence browsers, so they do nothing for a link a non-browser app has registered to handle.

  3. LongClickCopySpan — never overrode onClick, so it inherited URLSpan's bare startActivity. This is the click target wherever the UrlClickHandler declines: the story viewer (which uses the span directly, with no handler), the long-message view, message details and starred messages.

LongClickCopySpan now delegates to openBrowserLink instead of re-implementing the launch, so this reduces the number of parallel link launchers rather than adding another. encourageNewBrowserTab() is renamed prepareExternalLink(), since it no longer only concerns browser tabs.

Scope. 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 behave identically before and after.

Why NEW_TASK is safe here

  • No Custom Tabs anywhere in the tree, so nothing relies on staying in-task.
  • Signal's own https handlers (StickerPackPreviewActivity, DeepLinkEntryActivity) both use the default taskAffinity, so a signal.me / signal.art link reuses the existing Signal task rather than creating a second Signal card.
  • Back navigation is preserved — verified on device, Back from the other app still returns to Signal.

Verification

Unit tests in LinkActionsTest and LongClickCopySpanTest; the flag assertions fail against current main and 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:

Signal task other app's task flags Signal sent
main 359 359 (same task, sz=2) 0x0
with fix 361 362 (own task) 0x10000000

On main the 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.

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
theabhishekchandra force-pushed the fix/external-links-own-task branch from a21ebef to 6175509 Compare September 3, 2026 20:36
@theabhishekchandra
theabhishekchandra deleted the fix/external-links-own-task branch September 3, 2026 20:49
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant