Skip to content

fix(android): record the launched intent owner from the device - #3600

Merged
thymikee merged 4 commits into
mainfrom
fix/android-observed-intent-owner
Oct 14, 2026
Merged

thymikee merged 4 commits into
mainfrom
fix/android-observed-intent-owner

Conversation

@thymikee

@thymikee thymikee commented Oct 14, 2026 •

Copy link
Copy Markdown
Member

Summary

On ADB-backed Android (local adb and ADB providers), open settings and other intent targets left appBundleId undefined, and --relaunch closed the raw target.

  • Owner, in order: the requested package, the package am start -W reports on its Activity: line, then the foreground package when no activity is named. The framework android chooser/forwarder and a permission prompt over the app are never owners.
  • --relaunch closes only a requested package. Otherwise the launch stops the handler: -S for intents, force-stop then start for package and app-bound targets. Bare-URL relaunch stays rejected before dispatch. An unresolved app-bound package previously closed the raw target name.

WebDriver/Appium opens only by package, so its owner is the requested package; Limrun is unchanged.

Supersedes #3555
Related to #3077

Validation

Head d9a726882, rebased on main ba7730af0: pnpm check:affected --base origin/main --run passed (464 files / 3,235 tests). The owner test fails if the lifecycle reads the follow-up open's report.

Live at 19d113ba7 (owner route unchanged since), API 35 emulator, built CLI: open settings reports appBundleId: com.android.settings; open settings --relaunch restarts Settings (PID 981 → 3249).

Risk: am start -S refuses an implicit intent matching several activities (raw am start -W -S -a MAIN -c HOME: can't stop). settings has one handler here; OEM builds with several Settings handlers would fail a relaunch, where a plain open already shows the chooser.

🤖 Generated with Claude Code

@cubic-dev-ai cubic-dev-ai Bot left a comment •

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All reported issues were addressed across 10 files

Reply with feedback, questions, or to request a fix.

View guided diff | Turn on auto-fix | Re-trigger cubic

Comment thread packages/platform-android/src/open-target-resolution.ts Outdated
Comment thread packages/platform-android/src/app-lifecycle.ts
Comment thread packages/platform-android/src/open-target-resolution.test.ts
@github-actions

github-actions Bot commented Oct 14, 2026 •

Copy link
Copy Markdown
Contributor

Size Report

Metric Base Current Diff
Installed (including dependencies) 5.21 MB 5.21 MB +752 B
Package (unpacked) 5.21 MB 5.21 MB +752 B
Package (download) 1.57 MB 1.57 MB +194 B

Startup median (7 runs, lower is better):

Scenario Base Current Diff
CLI --version 26.0 ms 26.3 ms +0.3 ms
CLI --help 75.4 ms 75.4 ms -0.0 ms

@cubic-dev-ai cubic-dev-ai Bot left a comment •

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All reported issues were addressed across 12 files (changes from recent commits).

Reply with feedback, questions, or to request a fix.

View guided diff | Turn on auto-fix | Re-trigger cubic

Comment thread packages/platform-android/src/lifecycle.test.ts Outdated
Comment thread src/platform-runtime-android-application-tools.ts
@thymikee

Copy link
Copy Markdown
Member Author

The code in 19d113b looks good, and I found nothing that blocks it. All 19 checks pass at this head. The PR body places the live API 35 run in the head-19d113ba7 validation section but does not name the commit for the device run itself. I could not reproduce it. The live output shows appBundleId: com.android.settings, but it does not show whether that came from the Activity line or the foreground fallback. Only the fake-adb test at app-lifecycle-open.test.ts:124 proves the launch-report route. I did not check older API levels or each provider's own Android open path (WebDriver/Appium, Limrun). I only confirmed that those paths do not reach bindAndroidApplicationLifecycle.

Not blocking, and fine to take or leave: openAndroidDeepLink adds -S when terminateRunningApp is set (app-lifecycle.ts:265), but the daemon rejects every bare-URL relaunch before dispatch, so that branch cannot fire today. Also, open settings --relaunch now succeeds and intent opens now record an appBundleId. The multi-handler -S refusal is also new. One line in the Android notes in website/docs/docs/commands.md (near line 92) would cover all three.

The open inline thread on lifecycle.test.ts:44-47 still stands: #3600 (comment). The mock returns the same report for the follow-up open, so the test at :485-500 would stay green if lifecycle.ts:177 read the wrong report. Please key the mock's report on app so the follow-up returns a different package. Production is correct. This thread is the only step left before merge.

The catch-path thread does not apply, so please resolve it: #3600 (comment). The catch fires only if loadAndroidMechanics rejects, but that module is already loaded for the launch. Returning the raw report there would also skip the android framework filter in open-target-resolution.ts:38.

thymikee and others added 4 commits October 14, 2026 12:54
An ADB open of an intent or alias target (open settings) now records the
package the device resumed as the session appBundleId, so post-open
observation, frame-perf reset, and helper leasing use it. A framework
foreground (chooser, cross-profile forwarder) is never an owner.

open --relaunch closes only a requested package; an intent target instead
launches with am start -S so the handler the device resolves is
force-stopped and restarted, never close(<target>).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A package target whose requested package did not resolve before launch
now force-stops the resolved package before starting it, so open
--relaunch never degrades to a plain foreground open.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
am start -W names the activity the launch resumed. That component, not a
later foreground read, is the owner, so a permission prompt over the
launched app is never recorded. The foreground read remains only for a
launch that names no activity. Deep-link and app-bound launches honor
relaunch termination like intent and package launches.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Key the lifecycle fixture's launch report on the opened target so the
runtime-URL follow-up reports a different package. Drop the unreachable
deep-link -S branch, since a bare URL relaunch is rejected before
dispatch, and document Android intent ownership and relaunch.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@thymikee
thymikee force-pushed the fix/android-observed-intent-owner branch from 19d113b to d9a7268 Compare October 14, 2026 11:01
@thymikee

Copy link
Copy Markdown
Member Author

Addressed the review of 19d113b at d9a726882 (rebased on main ba7730af0):

  • Blocking mock: the fixture's launch report is keyed on the opened target, so the runtime-URL follow-up reports a different package. Reading the follow-up's report in lifecycle.ts now fails the owner inference receives the package the primary launch reported (verified by mutation).
  • Catch path: not applied, for the reason you gave: the mechanics module is already loaded, and the raw report would skip the android filter.
  • Docs: one Android note in commands.md covers intent owners, open settings --relaunch, and the multi-handler refusal.
  • The unreachable deep-link -S branch and its test are removed.
  • The body names the live run's commit (19d113ba7); the owner route is unchanged since.

Gate: pnpm check:affected --base origin/main --run passed (464 files / 3,235 tests).

@github-actions

github-actions Bot commented Oct 14, 2026 •

Copy link
Copy Markdown
Contributor
PR Preview Action v1.8.1
Preview removed because the pull request was closed.
2026-10-14 13:38 UTC

@thymikee

Copy link
Copy Markdown
Member Author

This PR is ready at d9a7268. The earlier findings from 19d113b are fixed, and I found nothing new in the delta.

Not blocking: the docs note at website/docs/docs/commands.md:93 says "Android (adb)", but Limrun Android is also adb-backed and opens through openDirectApplication (https://github.com/callstack/agent-device/blob/d9a7268/packages/contracts/src/application-lifecycle-interaction.ts#L303-L335), which drops the launch report, so you could scope the note to local Android (adb) or name the backends it covers, and you can take or leave this.

Of the open threads, the ones on #3600 (comment) (mock follow-up target), #3600 (comment) (focused-window owner), #3600 (comment) (report fallback catch), #3600 (comment) (relaunch with bare URL) and #3600 (comment) (launch-report test) no longer apply. The first and third are fixed at this head, and the other three are benign as written, so you can resolve them.

No conflicts. Smoke Tests is queued, not failed, and the delta only touches an unreachable deep-link -S argument, a unit fixture and one docs line. I did not run the mutation, which I reasoned from lifecycle.test.ts:51-58 and :292-307. I did not rerun live device validation, because the delta changes no reachable device route and the earlier review accepted the API 35 run. I did not check Appium's own Android open path, and the docs note excludes it by scoping the claim to adb. Merge once Smoke Tests finishes green.

@thymikee thymikee added the ready-for-human Valid work that needs human implementation, judgment, or maintainer merge label Oct 14, 2026
@thymikee
thymikee merged commit 1768cbe into main Oct 14, 2026
22 of 23 checks passed
@thymikee
thymikee deleted the fix/android-observed-intent-owner branch October 14, 2026 12:50
thymikee added a commit that referenced this pull request Oct 14, 2026
* origin/main:
  feat(leasing): proxy CLI names each new lease, daemon keeps a caller-chosen lease id (#3479)
  feat(remote): name the held lease on upload, artifact download and diagnostics requests (#3594)
  fix(android): record the launched intent owner from the device (#3600)
  fix(selectors): type rect-requiring targets as addressable (#3608)
  feat(snapshot): interpret iOS node facts once at producer seams (#3592)
  refactor(gates): own the client-only replay rule as daemon-process reachability (R82) (#3604)
thymikee added a commit that referenced this pull request Oct 14, 2026
* origin/main:
  feat(remote): name the held lease on upload, artifact download and diagnostics requests (#3594)
  fix(android): record the launched intent owner from the device (#3600)
  fix(selectors): type rect-requiring targets as addressable (#3608)
  feat(snapshot): interpret iOS node facts once at producer seams (#3592)
  refactor(gates): own the client-only replay rule as daemon-process reachability (R82) (#3604)
thymikee added a commit that referenced this pull request Oct 14, 2026
* origin/main:
  feat(remote): name the held lease on upload, artifact download and diagnostics requests (#3594)
  fix(android): record the launched intent owner from the device (#3600)
  fix(selectors): type rect-requiring targets as addressable (#3608)
  feat(snapshot): interpret iOS node facts once at producer seams (#3592)
  refactor(gates): own the client-only replay rule as daemon-process reachability (R82) (#3604)
thymikee added a commit that referenced this pull request Oct 14, 2026
#3600 routed package relaunch through the same stop helper as close, so the
fence refusal now also guards relaunch before any launch.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
thymikee added a commit that referenced this pull request Oct 15, 2026
#3600 routed package relaunch through the same stop helper as close, so the
fence refusal now also guards relaunch before any launch.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
thymikee added a commit that referenced this pull request Oct 15, 2026
#3600 routed package relaunch through the same stop helper as close, so the
fence refusal now also guards relaunch before any launch.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
thymikee added a commit that referenced this pull request Oct 15, 2026
* fix(android): refuse close when stop fences expire

* fix(android): reject close probes that finish after deadline

* refactor(android): bound close fences with Deadline

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* docs(close): state the Android ADB close fence timeout

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* test(android): refuse a package relaunch whose stop fence expires

#3600 routed package relaunch through the same stop helper as close, so the
fence refusal now also guards relaunch before any launch.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(android): let killApp classify a survivor after the shared stop wait

The stop wait now reports which fence expired instead of throwing. Close and
relaunch turn that into android-app-close-timeout; killApp keeps its own
android-kill-process-survived classification, as on main.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* test(android): move closeAndroidApp and stop-fence tests to their own file

app-lifecycle-open.test.ts crossed the test-file size tripwire; the close and
stop-fence cases move unchanged to app-lifecycle-close.test.ts.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(android): keep both stop waits on the killApp path

The stop wait returned at an expired foreground fence, so killApp skipped the
process wait and sampled pidof once. Kill now runs both waits as on main and
classifies a survivor itself.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ready-for-human Valid work that needs human implementation, judgment, or maintainer merge

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant