Add more androidDevice actions to the mobile-2 sample - #2964
Draft
jebrans wants to merge 1 commit into
Draft
Conversation
Extends the `androidDevice` client agent with the five medium-risk actions from the Android feature plan. Every one stays a compose/open intent that the user confirms, so no new Android permissions are needed and a hallucinated action cannot complete on its own. ## Actions - `composeEmail` - `ACTION_SENDTO` with a bare `mailto:` URI. `SENDTO` rather than `SEND` so only mail apps resolve, not every share target. Recipients ride as `EXTRA_EMAIL`/`EXTRA_CC`/`EXTRA_BCC` arrays rather than being spliced into the URI. One unusable address fails the whole action: a draft addressed to fewer people than asked for would look like success. - `shareText` - `ACTION_SEND` (`text/plain`) inside `createChooser`, so the user picks the destination. The inner intent is resolved before wrapping, because the chooser is a system activity that always resolves and would otherwise report a false success on a device with no text handler. - `openSettings` - the screen comes from a closed `AndroidSettingsScreen` enum mapped exhaustively to `Settings.ACTION_*`; the model never supplies a raw action string, and `appInfo` is pinned to this app's own package. Screens only display settings; nothing is toggled. - `createCalendarEvent` - `ACTION_INSERT` on `CalendarContract.Events`, which opens the calendar app's pre-filled new-event editor, so no `WRITE_CALENDAR` permission is involved. ISO-8601 times are resolved without `java.time` (minSdk 24, no core-library desugaring). - `playMusicFromSearch` - `MEDIA_PLAY_FROM_SEARCH`. What actually plays is up to the installed app, so the action reports what it dispatched. ## Invariants - Every action reaches the OS through `launchExternalIntent`, keeping the existing resolveActivity / foreground-lifecycle / exception funnel, and completes its callback exactly once. - Each new implicit intent has a matching `<queries>` entry; without one Android 11+ package visibility makes `resolveActivity` return null and the action falsely reports that no app is available. - Parsers read strings via `opt(name) as? String`, never `optString`, because Android's `org.json` renders a JSON null as the string "null". - Ambiguity fails the action rather than being guessed at: an unknown music `focus`, an unknown settings screen, an all-day event that also carries a time of day, and a span over 366 days are all rejected. - Calendar offsets are built from a raw integer offset via `SimpleTimeZone`, not by formatting a `GMT+hh:mm` string. `String.format` is locale-sensitive, and under a locale with non-Latin digits `TimeZone.getTimeZone` cannot read the result and silently falls back to GMT - turning `+05:30` into a five-and-a-half-hour shift with no error. - All-day events are anchored at UTC midnight as `CalendarContract` requires, so a user in UTC+10 does not see them land a day early. - `AndroidDeviceSchemaAssetTest` now checks that the settings-screen and music-focus unions in `androidDeviceSchema.ts` match the Kotlin enums, so the two cannot drift apart silently. ## Validation - `gradlew testDebugUnitTest` - 234 tests, 0 failures (was 179). - `gradlew assembleDebug lintDebug` - clean; lint reports only pre-existing informational items. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What this changes
composeEmail,shareText,openSettings,createCalendarEventandplayMusicFromSearchtoandroidDeviceSchema.tsand wires each one throughAndroidDeviceAgent,WebSocketManager,ChatViewModelandMainActivity.ACTION_SENDTOwith a baremailto:URI forcomposeEmail, so onlymail apps resolve rather than every share target, and carries recipients as
EXTRA_EMAIL/EXTRA_CC/EXTRA_BCCarrays instead of splicing them into theURI. A single unusable address fails the whole action, because a draft
addressed to fewer people than the user asked for would look like success.
ACTION_SENDintent before wrapping it inIntent.createChooserforshareText. The chooser is a system activity thatalways resolves, so an unchecked wrap would report success on a device with
no text handler.
openSettingsto a closedAndroidSettingsScreenenum mappedexhaustively to
Settings.ACTION_*constants. The model never supplies a rawintent action string,
appInfois pinned to this app's own package, and thescreens only display settings rather than toggling them.
createCalendarEventasACTION_INSERTonCalendarContract.Events,which opens the calendar app's pre-filled new-event editor and so needs no
WRITE_CALENDARpermission. ISO-8601 times are resolved withoutjava.time,since the module targets
minSdk 24with no core-library desugaring.SimpleTimeZonerather than by formatting a
GMT+hh:mmstring.String.formatislocale-sensitive, and under a locale with non-Latin digits
TimeZone.getTimeZonecannot read the result and silently returns GMT,turning
+05:30into a five-and-a-half-hour shift with no error.CalendarContractrequires, so auser in UTC+10 does not see them land a day early.
focus, an unknown settingsscreen, an all-day event that also carries a time of day, and an event
spanning more than 366 days.
<queries>entry for every new implicit intent. Without one, Android11+ package visibility makes
resolveActivityreturn null and the actionfalsely reports that no app is available.
AndroidDeviceSchemaAssetTestto check that the settings-screen andmusic-focus unions in the schema match the Kotlin enums, so the two cannot
drift apart silently.
Every action reaches the OS through the existing
launchExternalIntentfunnel,keeping the current
resolveActivitycheck, foreground-lifecycle check andexception handling, and completing its callback exactly once. The ten existing
Tier-1 actions are unchanged.
Verification
Unit tests and the debug build completed successfully:
gradlew testDebugUnitTest— 234 tests, 0 failures (previously 179).gradlew assembleDebug— successful.gradlew lintDebug— clean; the only findings are pre-existinginformational items unrelated to this change.
Review confirmed:
finding (locale-sensitive offset formatting) and Low finding (fractional
seconds rejected) are both fixed here, each with a regression test. The
locale defect was reproduced on the JVM before and after the fix:
+05:30resolved to offset
0instead of19800000.+05are now accepted, with a test.AndroidManifest.xml.