docs/platforms/android.md
System control (launchd/systemd) lives on the Gateway host. See Gateway.
Android node app ⇄ (mDNS/NSD + WebSocket) ⇄ Gateway
Android connects directly to the Gateway WebSocket and uses device pairing (role: node).
For Tailscale or public hosts, Android requires a secure endpoint:
https://<magicdns> / wss://<magicdns>wss:// Gateway URL with a real TLS endpointws:// remains supported on private LAN addresses / .local hosts, plus localhost, 127.0.0.1, and the Android emulator bridge (10.0.2.2)ws:// endpoints. Use Tailscale Serve or another wss:// URL instead.openclaw) on the gateway machine (or via SSH).openclaw gateway --port 18789 --verbose
Confirm in logs you see something like:
listening on ws://0.0.0.0:18789For remote Android access over Tailscale, prefer Serve/Funnel instead of a raw tailnet bind:
openclaw gateway --tailscale serve
This gives Android a secure wss:// / https:// endpoint. A plain gateway.bind: "tailnet" setup is not enough for first-time remote Android pairing unless you also terminate TLS separately.
From the gateway machine:
dns-sd -B _openclaw-gw._tcp local.
More debugging notes: Bonjour.
If you also configured a wide-area discovery domain, compare against:
openclaw gateway discover --json
That shows local. plus the configured wide-area domain in one pass and uses the resolved
service endpoint instead of TXT-only hints.
Android NSD/mDNS discovery won’t cross networks. If your Android node and the gateway are on different networks but connected via Tailscale, use Wide-Area Bonjour / unicast DNS-SD instead.
Discovery alone is not sufficient for tailnet/public Android pairing. The discovered route still needs a secure endpoint (wss:// or Tailscale Serve):
openclaw.internal.) on the gateway host and publish _openclaw-gw._tcp records.Details and example CoreDNS config: Bonjour.
In the Android app:
ws:// still works. For Tailscale/public hosts, turn on TLS and use a wss:// / Tailscale Serve endpoint.After the first successful pairing, Android auto-reconnects on launch:
After the authenticated node session connects, and when the app moves to the background while the
foreground service is still connected, Android calls node.event with
event: "node.presence.alive". The gateway records this as lastSeenAtMs/lastSeenReason on the
paired node/device metadata only after the authenticated node device identity is known.
The app counts the beacon as successfully recorded only when the gateway response includes
handled: true. Older gateways may acknowledge node.event with { "ok": true }; that response is
compatible but does not count as a durable last-seen update.
On the gateway machine:
openclaw devices list
openclaw devices approve <requestId>
openclaw devices reject <requestId>
Pairing details: Pairing.
Optional: if the Android node always connects from a tightly controlled subnet, you can opt in to first-time node auto-approval with explicit CIDRs or exact IPs:
{
gateway: {
nodes: {
pairing: {
autoApproveCidrs: ["192.168.1.0/24"],
},
},
},
}
This is disabled by default. It applies only to fresh role: node pairing with
no requested scopes. Operator/browser pairing and any role, scope, metadata, or
public-key change still require manual approval.
Via nodes status:
openclaw nodes status
Via Gateway:
openclaw gateway call node.list --params "{}"
The Android Chat tab supports session selection (default main, plus other existing sessions):
chat.history (display-normalized; inline directive tags are
stripped from visible text, plain-text tool-call XML payloads (including
<tool_call>...</tool_call>, <function_call>...</function_call>,
<tool_calls>...</tool_calls>, <function_calls>...</function_calls>, and
truncated tool-call blocks) and leaked ASCII/full-width model control tokens
are stripped, pure silent-token assistant rows such as exact NO_REPLY /
no_reply are omitted, and oversized rows can be replaced with placeholders)chat.sendchat.subscribe → event:"chat"If you want the node to show real HTML/CSS/JS that the agent can edit on disk, point the node at the Gateway canvas host.
<Note> Nodes load canvas from the Gateway HTTP server (same port as `gateway.port`, default `18789`). </Note>Create ~/.openclaw/workspace/canvas/index.html on the gateway host.
Navigate the node to it (LAN):
openclaw nodes invoke --node "<Android Node>" --command canvas.navigate --params '{"url":"http://<gateway-hostname>.local:18789/__openclaw__/canvas/"}'
Tailnet (optional): if both devices are on Tailscale, use a MagicDNS name or tailnet IP instead of .local, e.g. http://<gateway-magicdns>:18789/__openclaw__/canvas/.
This server injects a live-reload client into HTML and reloads on file changes.
The A2UI host lives at http://<gateway-host>:18789/__openclaw__/a2ui/.
Canvas commands (foreground only):
canvas.eval, canvas.snapshot, canvas.navigate (use {"url":""} or {"url":"/"} to return to the default scaffold). canvas.snapshot returns { format, base64 } (default format="jpeg").canvas.a2ui.push, canvas.a2ui.reset (canvas.a2ui.pushJSONL legacy alias)Camera commands (foreground only; permission-gated):
camera.snap (jpg)camera.clip (mp4)See Camera node for parameters and CLI helpers.
dataSync to dataSync|microphone before capture starts, then demotes it when Talk Mode stops. Android 14+ requires the FOREGROUND_SERVICE_MICROPHONE declaration, the RECORD_AUDIO runtime grant, and the microphone service type at runtime.talk.speak through the configured gateway Talk provider. Local system TTS is used only when talk.speak is unavailable.device.status, device.info, device.permissions, device.healthnotifications.list, notifications.actions (see Notification forwarding below)photos.latestcontacts.search, contacts.addcalendar.events, calendar.addcallLog.searchsms.searchmotion.activity, motion.pedometerAndroid supports launching OpenClaw from the system assistant trigger (Google Assistant). When configured, holding the home button or saying "Hey Google, ask OpenClaw..." opens the app and hands the prompt into the chat composer.
This uses Android App Actions metadata declared in the app manifest. No extra configuration is needed on the gateway side -- the assistant intent is handled entirely by the Android app and forwarded as a normal chat message.
<Note> App Actions availability depends on the device, Google Play Services version, and whether the user has set OpenClaw as the default assistant app. </Note>Android can forward device notifications to the gateway as events. Several controls let you scope which notifications are forwarded and when.
| Key | Type | Description |
|---|---|---|
notifications.allowPackages | string[] | Only forward notifications from these package names. If set, all other packages are ignored. |
notifications.denyPackages | string[] | Never forward notifications from these package names. Applied after allowPackages. |
notifications.quietHours.start | string (HH:mm) | Start of quiet hours window (local device time). Notifications are suppressed during this window. |
notifications.quietHours.end | string (HH:mm) | End of quiet hours window. |
notifications.rateLimit | number | Maximum forwarded notifications per package per minute. Excess notifications are dropped. |
The notification picker also uses safer behavior for forwarded notification events, preventing accidental forwarding of sensitive system notifications.
Example configuration:
{
notifications: {
allowPackages: ["com.slack", "com.whatsapp"],
denyPackages: ["com.android.systemui"],
quietHours: {
start: "22:00",
end: "07:00",
},
rateLimit: 5,
},
}