Skip to content

feat: forward --launch-args to adb shell am start on Android#599

Merged
thymikee merged 2 commits into
callstackincubator:mainfrom
mikegarfinkle:feat/open-launch-args-android
May 30, 2026
Merged

feat: forward --launch-args to adb shell am start on Android#599
thymikee merged 2 commits into
callstackincubator:mainfrom
mikegarfinkle:feat/open-launch-args-android

Conversation

@mikegarfinkle
Copy link
Copy Markdown
Contributor

@mikegarfinkle mikegarfinkle commented May 27, 2026

🚧 Draft, stacked on top of #598. The branch is based on #598's branch, so until #598 merges this diff includes its changes too. Will rebase onto main once #598 lands and the diff will narrow to the Android-only changes summarised below.

Summary

Extends the --launch-args flag added in #598 to Android, removing the UNSUPPORTED_OPERATION guard that landed with the Maestro work and forwarding launch arguments verbatim to adb shell am start.

Opening this as a separate PR so the Android-specific design decisions (shell-quoting model, threading across the five Android open helpers, app-bound URL path) get their own review thread.

What this PR does

Layer Change
src/core/dispatch.ts Removes the device.platform === 'android' rejection for launchArgs.
src/core/interactors/android.ts Forwards options.launchArgs into openAndroidApp.
src/platforms/android/app-lifecycle.ts Adds launchArgs?: string[] to OpenAndroidAppOptions; threads the args through all five Android open helpers (openAndroidPackage, openAndroidPackageActivity, openAndroidIntent, openAndroidDeepLink, openAndroidAppBoundDeepLink); adds a quoteAndroidShellArg helper applied to launch arg values.
src/utils/command-schema.ts Updates the --launch-args help text to describe the Android shape (--es key value for typed Intent extras); macOS remains the only rejected platform.

Why shell-quoting

adb shell joins its argv with spaces and sends the result as a single string to a shell on the device, which then re-tokenises before invoking am start. The well-known am start flags (-a, -c, -n, -p, -d, etc.) and the values we generate for them never contain shell-significant characters, so they round-trip untouched. Launch arguments are user-supplied and routinely contain JSON, spaces, #, ;, &, etc., which the device shell would otherwise re-interpret as comments, separators, or backgrounding operators.

Helper:

function quoteAndroidShellArg(arg: string): string {
  if (/^[A-Za-z0-9_@%+=:,./-]+$/.test(arg)) return arg;
  return `'${arg.replace(/'/g, `'\\''`)}'`;
}

Safe-ASCII args pass through unquoted (keeps adb and am start logs readable); anything else is single-quoted with the standard '\'' escape for embedded single quotes. The same shape long used by ADB-driven tooling for the same reason.

Per-path argv shapes

# Package launch
adb shell am start -W -a MAIN -c DEFAULT -c LAUNCHER -p <package> <launchArgs...>

# Activity override
adb shell am start -W -a MAIN -c DEFAULT -c LAUNCHER -n <package/component> <launchArgs...>

# Bare intent
adb shell am start -W -a <intent> <launchArgs...>

# Deep-link URL (with optional appBundleId)
adb shell am start -W -a VIEW -d <url> [-p <package>] <launchArgs...>

# App-bound deep-link URL
adb shell am start -W -a VIEW -d <url> -p <resolved-package> <launchArgs...>

Tests

  • src/platforms/android/__tests__/index.test.ts — five new tests cover:
    • Package launch with typed extras (--es, --ez)
    • Activity-override launch with extras
    • Deep-link URL launch with extras
    • App-bound URL launch with extras
    • Shell-quoting: a JSON value containing :, /, # is single-quoted on the way to the device
  • src/core/__tests__/dispatch-open.test.ts — the previous "rejects Android launch arguments" test is inverted into a forwarding test that asserts openAndroidApp receives the args.

Manual validation

Verified end-to-end on a Pixel emulator running a debug build whose launcher activity reads an Intent extra to bootstrap test configuration:

agent-device open com.example.app --platform android \
  --activity com.example.app/com.example.app.bootstrap.SyncActivity \
  --launch-args --es \
  --launch-args EXTRA_CONFIG \
  --launch-args '{"mode":"debug","path":"a/b","note":"x #y"}'

The JSON value (containing #, /, : — all shell-significant on the device side) survived single-quoted transit through adb shell, arrived at the activity as an Intent extra, and was consumed by the app's bootstrap code unchanged.

pnpm check passes locally on top of #598's branch.

@mikegarfinkle mikegarfinkle force-pushed the feat/open-launch-args-android branch 2 times, most recently from b0219ec to 8933dc3 Compare May 27, 2026 20:25
@mikegarfinkle mikegarfinkle force-pushed the feat/open-launch-args-android branch 2 times, most recently from 6eedf59 to 0922430 Compare May 27, 2026 20:38
Stacked on top of the iOS-only --launch-args PR (callstackincubator#598). Removes the
Android UNSUPPORTED_OPERATION guard added with the Maestro work and
threads launchArgs through all five Android open paths.

Per-path threading:

- openAndroidPackage           (-p package launch + activity-fallback)
- openAndroidPackageActivity   (-n component override)
- openAndroidIntent            (named intent action)
- openAndroidDeepLink          (-a VIEW -d <url>, with optional -p)
- openAndroidAppBoundDeepLink  (-a VIEW -d <url> -p <resolved>)

`adb shell` joins its argv with spaces and feeds the result to a
device shell, which re-tokenises. The other am-start arguments are
well-known and never contain shell-significant characters, so they
round-trip untouched. Launch arguments are user-supplied and may
contain JSON, spaces, `#`, etc.; each is single-quoted unless it
consists entirely of safe shell characters (the same approach long
used in adb-driven tooling for the same reason).

Help text on --launch-args is updated to describe the Android shape
(`adb shell am start args, e.g. --es key value` for typed Intent
extras) and macOS remains the only rejected platform.

Tests:

- src/platforms/android/__tests__/index.test.ts: five new tests
  cover package, activity-override, deep-link URL, app-bound URL,
  and JSON-with-shell-metacharacters quoting paths.
- src/core/__tests__/dispatch-open.test.ts: the previous
  "rejects Android launch arguments" test is inverted into a
  forwarding test that asserts openAndroidApp receives the args.

Validated end-to-end on a Pixel emulator running a debug build whose
launcher activity reads an Intent extra to bootstrap test
configuration: a JSON value containing `#`, `/`, `:` survived
single-quoted transit through `adb shell` and arrived at the
activity unchanged.
@thymikee thymikee force-pushed the feat/open-launch-args-android branch from 9743a04 to 5cb02a5 Compare May 30, 2026 14:17
@thymikee thymikee marked this pull request as ready for review May 30, 2026 14:18
Copilot AI review requested due to automatic review settings May 30, 2026 14:21
@thymikee thymikee force-pushed the feat/open-launch-args-android branch from 5cb02a5 to 5b758f2 Compare May 30, 2026 14:21
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Copilot was unable to review this pull request because the user who requested the review is ineligible. To be eligible to request a review, you need a paid Copilot license, or your organization must enable Copilot code review.

@thymikee thymikee merged commit b7ca4fb into callstackincubator:main May 30, 2026
11 checks passed
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.

3 participants