Skip to content

fix(shareable): fund cash links copied from the Sharesheet - #1521

Merged
bmc08gt merged 1 commit into
code/cashfrom
fix/cash-link-copy-confirmation
Sep 21, 2026
Merged

bmc08gt merged 1 commit into
code/cashfrom
fix/cash-link-copy-confirmation

Conversation

@bmc08gt

@bmc08gt bmc08gt commented Sep 21, 2026

Copy link
Copy Markdown
Collaborator

Tapping the Sharesheet's own Copy button on a cash link produced no "Did you send the link?" modal, and the gift card was never funded, so the link the user pasted could not be collected. initiateGiftCardFunding only runs from onShared(ActionTaken), so a share the app does not notice is a link that does not work.

It is awkward to reproduce on purpose, because the sheet offers two different controls that both say Copy. A copy-to-clipboard app target, such as Drive's SendTextToClipboardActivity, is a real component and has always worked. The Sharesheet's own Copy chip in the preview header is not a component at all.

Why the receiver missed it

The same PendingIntent comes back two ways:

system Copy chip : extras {share_id, CHOOSER_RESULT}
Messages target  : extras {share_id, CHOOSER_RESULT, CHOSEN_COMPONENT}

ShareBroadcastReceiver read only EXTRA_CHOSEN_COMPONENT and did nothing when it was absent, which left ShareResult.CopiedToClipboard unreachable. Nothing in the codebase could produce it.

Why the clipboard fallback did not cover it

checkForShare() was meant to catch exactly this, and could not, for two independent reasons:

  1. It sat behind foregroundRefreshed.compareAndSet in onAppInForeground, which runs on ON_RESUME, but onAppInBackground resets that flag from ON_STOP. The Sharesheet is translucent and pauses the activity without ever stopping it, so the pass was still spent and the call returned early.
  2. Even once reached, the read happens before the window holds focus, and Android denies clipboard reads to an unfocused app: Denying clipboard access to com.flipcash.app.android, application is not in focus. A denied read reports no clip at all, which is indistinguishable from an empty clipboard.

The change

  • ShareBroadcastReceiver parses EXTRA_CHOOSER_RESULT when no component is attached and relays CHOOSER_RESULT_COPY as a new EXTRA_COPIED_TO_CLIPBOARD. That extra is API 35+, so the parse is guarded for minSdk 29.
  • InternalShareSheetController consumes it as CopiedToClipboard. awaitClipboard() polls every 50ms up to a 1s grace period so the read lands once focus arrives, which is the only route on API 29 to 34, where the Sharesheet reports the copy not at all.
  • checkForShare() moves above the foregroundRefreshed gate in RealSessionController, staying below the auth gate. Deduping the expensive refresh pass is unchanged and covered by a test.
  • report() cancels any poll still in flight, so a share the Sharesheet reports mid-wait is not answered twice. A blank entropy no longer matches, since it is contained in every string and would fund a link the user never shared.

15 tests cover both receiver routes, the clipboard wait and its grace expiry, double-report protection, and the lifecycle gate.

Tapping the Sharesheet's own Copy button produced no confirmation modal and left
the gift card unfunded, so the link the user pasted could not be collected.
Picking a copy-to-clipboard app target works, which is why this looked
unreproducible: the two are different controls in the same sheet.

They also report differently. An app selection reaches ShareBroadcastReceiver
with EXTRA_CHOSEN_COMPONENT; the Copy button reaches it with EXTRA_CHOOSER_RESULT
and no component on API 35+, and goes unreported below that. The receiver read
only the component, which left ShareResult.CopiedToClipboard unreachable.

Parse the chooser result for CHOOSER_RESULT_COPY. The clipboard check meant to
cover older releases could not run either: checkForShare() sat behind the
foregroundRefreshed gate, which resets on ON_STOP, and the Sharesheet is
translucent, so it pauses the activity without ever stopping it. Move the check
ahead of that gate and have it wait for a clipboard the window has focus to read,
since Android denies an unfocused read by reporting no clip at all and that is
indistinguishable from an empty one.
@bmc08gt bmc08gt self-assigned this Sep 21, 2026
@github-actions github-actions Bot added type: fix Bug fix area: session and removed type: fix Bug fix labels Sep 21, 2026
@bmc08gt
bmc08gt merged commit a88e292 into code/cash Sep 21, 2026
3 checks passed
@bmc08gt
bmc08gt deleted the fix/cash-link-copy-confirmation branch September 21, 2026 21:49
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant