Separate capture, routing and execution
Begin with editable text entry. Add a microphone button only after the text path works. Use a short, user-initiated speech session, review the transcript, then request a decision. Recognition failure should return the user to text entry with their existing text intact.
Android's SpeechRecognizer reference covers RECORD_AUDIO permission, main-thread calls, recognizer availability and releasing the recognizer with destroy(). Recognition can use remote services; do not describe it as offline unless you have selected and verified an available on-device recognizer. The app must remain usable when permission is denied or recognition is unavailable.
Route an intent, then resolve its arguments
For this feature use a separate vocabulary: CREATE_NOTE_DRAFT, OPEN_DIALER, ASK_USER and UNSUPPORTED. The notification vocabulary from module 1 should not silently gain unrelated capabilities.
| Transcript | Candidate route | Work still owned by the app |
|---|---|---|
| “Make a note” | CREATE_NOTE_DRAFT | Ask for or review the note body |
| “Call Rahul” | OPEN_DIALER | Resolve the intended contact and confirm the number |
| “Remind me later” | ASK_USER | Clarify the time and supported workflow |
| “Buy this now” | UNSUPPORTED | Explain the unsupported action |
Choosing OPEN_DIALER does not produce a validated telephone number. Let the user select a contact or enter a number. Preview the resolved argument and use an appropriate Android intent only after confirmation. Opening a dialer is distinct from placing a call. Do not show “Called” when you only launched another activity.
If you add a generative model for extraction, validate its arguments through the same path as manually entered values. Unknown or ambiguous arguments should stop at clarification. A high routing score does not resolve ambiguous contact names.
Make the state machine visible
Idle → Listening → Transcript review → Deciding → Action preview
↓ ↓
Error / Ask user Executing → Result
Permissions, supported handlers and request freshness are checked again immediately before execution. Keep the action out of composable rendering code: recomposition must not dispatch an intent. Deliver an explicit event from the user gesture to the executor and record completion separately from the predicted route.
A quick request to “Cancel” should cancel the current pending action locally. It does not require another cloud decision. Provide a visible Cancel button too.
Practice and checkpoint
Implement a note-draft flow with speech or typed input. A draft is an editable screen; saving requires a separate action. Try an empty transcript, two rapid submissions, ambiguous input, permission denial and a request to cancel during inference.
Expected: no draft is saved automatically, old results cannot reopen a cancelled preview, and denied microphone permission preserves text entry. Record the transcript, proposed route, validation result and actual outcome for five fictional examples.
Pass when: your demo distinguishes what the recognizer heard, what Jev selected and what Android actually did. This distinction becomes the core of the Decision Inspector.