Skip to content

Update android mobile with more action handlers - #2872

Open
jebrans wants to merge 1 commit into
microsoft:mainfrom
jebrans:dev/jebransyed/android-demo-v1
Open

Update android mobile with more action handlers#2872
jebrans wants to merge 1 commit into
microsoft:mainfrom
jebrans:dev/jebransyed/android-demo-v1

Conversation

@jebrans

@jebrans jebrans commented Aug 13, 2026

Copy link
Copy Markdown
Contributor

Problem

Separately, the androidDevice agent exposes only setAlarm, setTimer, and
searchNearby. Common device requests have no handler, and setAlarm cannot
express a recurring alarm.

Changes

Device actions. Seven additions to the androidDevice schema, each dispatched
through the existing client-agent path and launched through the shared intent
funnel that reports a structured failure when no app can handle the request:

  • showLocation opens the maps app on a named place.
  • dialPhoneNumber opens the dialer with a number pre-filled.
  • composeSms opens the messaging app with a recipient and body pre-filled.
  • webSearch runs a query in the default search app.
  • openWebPage opens a URL in the browser.
  • showAlarms and showTimers open the clock app's respective tabs.
  • setAlarm gains an optional days parameter for recurring alarms.

dialPhoneNumber uses ACTION_DIAL rather than ACTION_CALL, and composeSms
uses ACTION_SENDTO rather than SEND_SMS, so neither action places a call or
sends a message on its own and no new permissions are required. The user
confirms in the target app. Each new intent has a matching <queries> entry in
the manifest; without one, package visibility filtering on API 30 and above makes
resolveActivity return null and the action wrongly reports that no app is
available.

Action payloads are parsed by dedicated parsers sharing a common validation
layer that caps text length, rejects control characters, and reads strings via
opt(...) as? String so a JSON null does not arrive as the literal string
"null". URLs containing whitespace are rejected rather than repaired, so a
malformed address cannot be silently turned into a valid one for a host the model
never named. Web URLs have their scheme lowercased before dispatch, because
intent filter scheme matching is case-sensitive.

Testing

  • Added JVM tests for each new parser covering valid payloads, missing and
    null fields, oversized and control-character input, malformed URLs and phone
    numbers, and unsupported schemes. Extended the alarm and agent tests for
    repeat days and the new dispatch branches.
  • testDebugUnitTest assembleDebug lintDebug succeeds; 148 tests pass with no
    failures.
  • Verified ./gradlew runs from a fresh checkout of the branch, confirming the
    wrapper jar survives checkout under the new attribute.
  • Verified on an emulator that all six new intents resolve to real handlers
    (clock, dialer, messaging, browser, and the activity resolver).
  • Verified the connection, transcript, and conversation-resume behaviour
    introduced by Preserve chat state still works end to end after the merge fix.

Follow-up

  • The build fixes repair main for anyone building this sample and are
    independent of the device actions. They can be split into their own PR if that
    is easier to land quickly.
  • setAlarm and setTimer pass EXTRA_SKIP_UI, so they complete without user
    confirmation, unlike the actions added here. That is existing behaviour and is
    left unchanged, but it is worth revisiting.
  • Further device actions remain unimplemented and can follow the same parser and
    dispatch pattern.

The mobile-2 sample did not build at all on main, for two independent
reasons, so both are fixed here before anything is added on top.

The committed gradle-wrapper.jar was corrupt. .gitattributes sets
`* text eol=lf` globally and did not list *.jar as binary, so git
rewrote the CRLF bytes inside the jar's compressed entries, shifting
its central directory. `./gradlew` failed with "Invalid or corrupt
jarfile" before it could download anything. Marking *.jar as binary
stops it happening again; the jar itself is restored.

A merge-conflict resolution had also taken main's side wholesale in
MainActivity, reinstating a webSocketManager field that had since moved
into ChatViewModel and leaving five compile errors. onCreate goes back
to viewModel.connectIfNeeded, which drops the duplicated handler
registration, and searchNearby's completion is threaded through
ClientAction and failWith so the server's executeAction RPC is answered
instead of being left open.

On top of that, the androidDevice agent grows from three actions to
ten: showAlarms, showTimers, showLocation, dialPhoneNumber, composeSms,
webSearch and openWebPage, plus optional repeat days on setAlarm.

Each one is deliberately confirm-first, so the app needs no new
permissions: dialPhoneNumber uses ACTION_DIAL rather than ACTION_CALL,
and composeSms uses ACTION_SENDTO rather than SEND_SMS, leaving the
user to press call or send. Values arriving from the model are
validated rather than repaired, because a silently altered phone number
or URL is worse than a refused one - an over-length number, a number
outside the dialable charset and a URL containing whitespace are all
rejected outright. openWebPage additionally normalizes the scheme,
since intent filter matching is case-sensitive and "HTTPS://" would
otherwise resolve to nothing and be reported as a missing browser.

The <queries> entries are not optional: since Android 11 resolveActivity
returns null for any intent not declared there, which would make every
one of these actions claim no app was available.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
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.

2 participants