Skip to content

feat(android): support android node sms.search#48299

Merged
obviyus merged 4 commits intoopenclaw:mainfrom
lixuankai:main
Mar 19, 2026
Merged

feat(android): support android node sms.search#48299
obviyus merged 4 commits intoopenclaw:mainfrom
lixuankai:main

Conversation

@lixuankai
Copy link
Copy Markdown
Contributor

Summary

When I say,

Generate a weekly summary, view the schedule, call history, and SMS messages of the past week

It not support:
image

So I plan to enhance the tools for Android nodes to support more high-value scenarios
Upgrade the Android node function:

  • support calllog.search tool (already merged) fix(android): support android node calllog.search #44073
  • support sms.search tool(this commit
  • support device.control tool
  • support ADB tool
  • support Dynamic program execution tool (This is what I think is the most important, but I am not sure if the actual effect is good)

Scope (select all touched areas)

  • Gateway / orchestration
  • Skills / tool execution
  • Auth / tokens
  • Memory / storage
  • Integrations
  • API / contracts
  • UI / DX
  • CI/CD / infra

Linked Issue/PR

User-visible / Behavior Changes

  • Add relevant explanations on the first launch page of the Android app
  • Add a switch for sms read permissions on the settings page of the Android app

Security Impact (required)

  • New permissions/capabilities? (Yes)
  • Secrets/tokens handling changed? (No)
  • New/changed network calls? (No)
  • Command/tool execution surface changed? (No)
  • Data access scope changed? (Yes)
  • If any Yes, explain risk + mitigation:
    Support applying for sms read access permission on Android applications and providing sms.search to OpenClaw

Repro + Verification

Environment

  • OS: Windows host
  • Runtime/container: Android Gradle app module + repo TypeScript checks
  • Model/provider: n/a
  • Integration/channel (if any): Android Node
  • Relevant config (redacted): none

Steps

  1. Build Android Kotlin sources and targeted Android unit tests.
  2. Install Android apk and call sms.search through OpenClaw

Expected

  • Android builds cleanly
  • Targeted unit tests pass
  • Call sms.search success

Actual

  • Matches expected.

Evidence

Attach at least one:

  • Failing test/log before + passing after
  • Trace/log snippets
  • Screenshot/recording
  • Perf numbers (if relevant)
image image

Human Verification (required)

What you personally verified (not just CI), and how:

  • Verified scenarios: Install Android apk and call sms.search through OpenClaw, refer to the above image for the effect
  • Edge cases checked: no sms read permission
  • What you did not verify: NA

Compatibility / Migration

  • Backward compatible? (No)
  • Config/env changes? (No)
  • Migration needed? (No)
  • If yes, exact upgrade steps:

Failure Recovery (if this breaks)

  • How to disable/revert this change quickly: na
  • Files/config to restore: na
  • Known bad symptoms reviewers should watch for: na

Risks and Mitigations

None

@openclaw-barnacle openclaw-barnacle bot added docs Improvements or additions to documentation app: android App: android gateway Gateway runtime size: L labels Mar 16, 2026
@greptile-apps
Copy link
Copy Markdown
Contributor

greptile-apps bot commented Mar 16, 2026

Greptile Summary

This PR adds sms.search to the Android node, enabling the gateway to query a device's SMS inbox with filters (time range, contact name, phone number, keyword, type, read status, and pagination). It follows the pattern established by the recently merged callLog.search PR and wires in the required READ_SMS Android permission across the manifest, onboarding flow, and settings sheet.

Key changes:

  • SmsManager: new search() suspend function with full query-param parsing, contact-name → phone-number resolution via the Contacts provider, SQL filter building, and structured error codes
  • SmsHandler: new handleSmsSearch() that pre-checks READ_SMS before delegating
  • InvokeCommandRegistry / InvokeDispatcher: route sms.search to the new handler under SmsAvailable availability
  • node-command-policy.ts: sms.search placed in non-dangerous SMS_COMMANDS (included in Android defaults), separate from sms.send which remains in SMS_DANGEROUS_COMMANDS
  • OnboardingFlow / SettingsSheet: SMS permission toggle updated to request and track both SEND_SMS and READ_SMS together

Issues found:

  • Logic (InvokeCommandRegistry.kt): sms.search is registered with SmsAvailable, which resolves to sms.canSendSms() — requiring SEND_SMS even though search only needs READ_SMS. A user who grants READ_SMS but not SEND_SMS cannot use sms.search.
  • Performance (SmsManager.kt): querySmsMessages issues a contentResolver.query() with no SQL LIMIT/OFFSET, loading all matching rows into the cursor and applying pagination in Kotlin. ContactsHandler and CalendarHandler in the same codebase append LIMIT x to the sortOrder string — the same approach would work here.
  • Style (SmsManager.kt): Telephony.Sms.PERSON is deprecated since API 29 and returns an opaque contact ID, not a display name; API callers are likely to misread this field.

Confidence Score: 3/5

  • Functional and well-tested, but sms.search availability is incorrectly gated on SEND_SMS and the query has a memory-efficiency problem for large SMS histories.
  • The implementation is clean and follows established codebase patterns. The logic issue with SmsAvailable requiring SEND_SMS for a read-only command is a real correctness problem (wrong permission gate), and the missing SQL LIMIT is a performance gap that mirrors a pre-existing issue in CallLogHandler but is worth fixing before this ships. The deprecated PERSON field is minor. No security vulnerabilities were found — OS-level READ_SMS still gates all access.
  • InvokeCommandRegistry.kt (wrong availability gate) and SmsManager.kt (missing SQL LIMIT/OFFSET, deprecated PERSON field)

Comments Outside Diff (1)

  1. apps/android/app/src/main/java/ai/openclaw/app/node/SmsManager.kt, line 131-134 (link)

    Telephony.Sms.PERSON is deprecated and rarely populated

    Telephony.Sms.PERSON was deprecated in API 29. On modern Android it typically returns null or an opaque internal contact ID (not a human-readable name). Exposing it in the API response may confuse callers who expect a display name there.

    Options:

    • Remove the person field entirely (callers can resolve the name from address via the Contacts provider if needed).
    • Rename it to personId with documentation noting its deprecated/internal nature.
    • Resolve the contact display name at query time using ContactsContract (though that adds latency and an extra permission dependency).

    At minimum, the docstring for search() should clarify that person is an internal ID, not a display name.

    Prompt To Fix With AI
    This is a comment left during a code review.
    Path: apps/android/app/src/main/java/ai/openclaw/app/node/SmsManager.kt
    Line: 131-134
    
    Comment:
    **`Telephony.Sms.PERSON` is deprecated and rarely populated**
    
    `Telephony.Sms.PERSON` was deprecated in API 29. On modern Android it typically returns `null` or an opaque internal contact ID (not a human-readable name). Exposing it in the API response may confuse callers who expect a display name there.
    
    Options:
    - Remove the `person` field entirely (callers can resolve the name from `address` via the Contacts provider if needed).
    - Rename it to `personId` with documentation noting its deprecated/internal nature.
    - Resolve the contact display name at query time using `ContactsContract` (though that adds latency and an extra permission dependency).
    
    At minimum, the docstring for `search()` should clarify that `person` is an internal ID, not a display name.
    
    How can I resolve this? If you propose a fix, please make it concise.
Prompt To Fix All With AI
This is a comment left during a code review.
Path: apps/android/app/src/main/java/ai/openclaw/app/node/SmsManager.kt
Line: 484-502

Comment:
**No SQL-level LIMIT/OFFSET — all matching rows loaded into cursor**

The `contentResolver.query()` call fetches _all_ SMS messages matching the filter with no row-count cap, and the Kotlin loop then manually applies `limit`/`offset` after the fact. On a device with thousands of SMS messages this causes the cursor to load every matching row into memory before the first message is returned.

Other handlers in this codebase already append `LIMIT` to the `sortOrder` string (e.g. `ContactsHandler` uses `"… COLLATE NOCASE ASC LIMIT ${request.limit}"` and `CalendarHandler` uses `"… ASC LIMIT ${request.limit}"`). The same technique works for the Telephony SMS provider:

```kotlin
val sortOrder = "${Telephony.Sms.DATE} DESC LIMIT ${params.limit} OFFSET ${params.offset}"

val cursor = context.contentResolver.query(
    Telephony.Sms.CONTENT_URI,
    arrayOf( /* … columns … */ ),
    selection,
    selectionArgsArray,
    sortOrder
)

cursor?.use {
    // index columns as before
    var count = 0
    while (it.moveToNext() && count < params.limit) {
        // map row → SmsMessage
        count++
    }
}
```

With this change the `moveToPosition(offset - 1)` dance can also be removed, keeping the cursor traversal simpler.

How can I resolve this? If you propose a fix, please make it concise.

---

This is a comment left during a code review.
Path: apps/android/app/src/main/java/ai/openclaw/app/node/InvokeCommandRegistry.kt
Line: 192-195

Comment:
**`sms.search` gated on `SmsAvailable` which requires SEND_SMS**

`InvokeCommandAvailability.SmsAvailable` resolves to `flags.smsAvailable`, which is set from `sms.canSendSms()` in `NodeRuntime`. `canSendSms()` checks `hasSmsPermission()` (i.e. `SEND_SMS`) **and** `hasTelephonyFeature()`.

`sms.search` only needs `READ_SMS`, not `SEND_SMS`. If a user declines `SEND_SMS` but grants `READ_SMS` (which is possible through system settings), `sms.search` will not be advertised or dispatched by the node even though it could succeed at runtime.

Consider either:
- Introducing a dedicated `ReadSmsAvailable` availability variant that calls `sms.hasReadSmsPermission() && sms.hasTelephonyFeature()`, or
- Leaving `availability` unset (defaulting to `Always`) and relying solely on the runtime permission check in `SmsHandler.handleSmsSearch`.

The UI currently requests both permissions together, but the availability logic should reflect the actual requirement of the command to stay correct independent of the UI flow.

How can I resolve this? If you propose a fix, please make it concise.

---

This is a comment left during a code review.
Path: apps/android/app/src/main/java/ai/openclaw/app/node/SmsManager.kt
Line: 131-134

Comment:
**`Telephony.Sms.PERSON` is deprecated and rarely populated**

`Telephony.Sms.PERSON` was deprecated in API 29. On modern Android it typically returns `null` or an opaque internal contact ID (not a human-readable name). Exposing it in the API response may confuse callers who expect a display name there.

Options:
- Remove the `person` field entirely (callers can resolve the name from `address` via the Contacts provider if needed).
- Rename it to `personId` with documentation noting its deprecated/internal nature.
- Resolve the contact display name at query time using `ContactsContract` (though that adds latency and an extra permission dependency).

At minimum, the docstring for `search()` should clarify that `person` is an internal ID, not a display name.

How can I resolve this? If you propose a fix, please make it concise.

Last reviewed commit: 3285a7c

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 3285a7c3fd

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: fdedc83ba3

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

emptyList()
}

val messages = querySmsMessages(params, phoneNumbers)
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Short-circuit contact-name searches with no matching numbers

If contactName is set but getPhoneNumbersFromContactName() returns an empty list, this still falls through to querySmsMessages() with no address filter. In the common case where the caller only supplies contactName, a typo or nonexistent contact now returns the device's recent SMS history instead of an empty result set, which leaks unrelated messages.

Useful? React with 👍 / 👎.

Comment on lines +195 to +196
name = OpenClawSmsCommand.Search.rawValue,
availability = InvokeCommandAvailability.ReadSmsAvailable,
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Add a live-test profile before advertising sms.search

Once READ_SMS is granted, advertisedCommands() will now include sms.search, but src/gateway/android-node.capabilities.live.test.ts:456-459 hard-fails whenever an advertised command is missing from COMMAND_PROFILES. Because this new command is registered here without a corresponding profile, live Android-node capability runs will start aborting with unmapped advertised commands: sms.search on devices that expose the permission.

Useful? React with 👍 / 👎.

@obviyus obviyus merged commit c86de67 into openclaw:main Mar 19, 2026
30 of 40 checks passed
@obviyus
Copy link
Copy Markdown
Contributor

obviyus commented Mar 19, 2026

Landed on main.

Thanks @lixuankai.

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 205498dcfb

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +425 to +430
val phoneNumbers = if (!params.contactName.isNullOrEmpty()) {
if (!ensureReadContactsPermission()) {
return@withContext SearchResult(
ok = false,
messages = emptyList(),
error = "CONTACTS_PERMISSION_REQUIRED: grant READ_CONTACTS permission",
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Fall back to explicit phoneNumber when contacts access is denied

If a caller supplies both contactName and phoneNumber, this branch still hard-fails on missing READ_CONTACTS before querySmsMessages() runs. querySmsMessages() can already filter by phoneNumber alone, so otherwise valid searches (for example, an agent sending both a display name and an extracted number) now return CONTACTS_PERMISSION_REQUIRED on devices that intentionally withhold contacts access. Only requiring contacts permission when contactName is the sole address filter would avoid blocking those searches.

Useful? React with 👍 / 👎.

Comment on lines +257 to +259
val sendOk = perms[Manifest.permission.SEND_SMS] == true
val readOk = perms[Manifest.permission.READ_SMS] == true
smsPermissionGranted = sendOk && readOk
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Let users enable sms.search without also granting SEND_SMS

This new Settings flow makes SMS permission all-or-nothing: smsPermissionGranted only becomes true when both SEND_SMS and READ_SMS are granted. That blocks the new read-only sms.search feature on devices or policies where sending SMS is intentionally disallowed, even though InvokeCommandRegistry and SmsManager.search() only need READ_SMS for search. As written, users cannot enable the new command from Settings unless they also hand over send-SMS access.

Useful? React with 👍 / 👎.

cxa pushed a commit to cxa/openclaw that referenced this pull request Mar 19, 2026
* feat(android): support android node sms.search

* feat(android): support android node sms.search

* fix(android): split sms search permissions

* fix: document android sms.search landing (openclaw#48299) (thanks @lixuankai)

---------

Co-authored-by: lixuankai <[email protected]>
Co-authored-by: Ayaan Zaidi <[email protected]>
udftd pushed a commit to udftd/openclaw that referenced this pull request Mar 20, 2026
* feat(android): support android node sms.search

* feat(android): support android node sms.search

* fix(android): split sms search permissions

* fix: document android sms.search landing (openclaw#48299) (thanks @lixuankai)

---------

Co-authored-by: lixuankai <[email protected]>
Co-authored-by: Ayaan Zaidi <[email protected]>
fuller-stack-dev pushed a commit to fuller-stack-dev/openclaw that referenced this pull request Mar 20, 2026
* feat(android): support android node sms.search

* feat(android): support android node sms.search

* fix(android): split sms search permissions

* fix: document android sms.search landing (openclaw#48299) (thanks @lixuankai)

---------

Co-authored-by: lixuankai <[email protected]>
Co-authored-by: Ayaan Zaidi <[email protected]>
fuller-stack-dev pushed a commit to fuller-stack-dev/openclaw that referenced this pull request Mar 20, 2026
* feat(android): support android node sms.search

* feat(android): support android node sms.search

* fix(android): split sms search permissions

* fix: document android sms.search landing (openclaw#48299) (thanks @lixuankai)

---------

Co-authored-by: lixuankai <[email protected]>
Co-authored-by: Ayaan Zaidi <[email protected]>
pholpaphankorn pushed a commit to pholpaphankorn/openclaw that referenced this pull request Mar 22, 2026
* feat(android): support android node sms.search

* feat(android): support android node sms.search

* fix(android): split sms search permissions

* fix: document android sms.search landing (openclaw#48299) (thanks @lixuankai)

---------

Co-authored-by: lixuankai <[email protected]>
Co-authored-by: Ayaan Zaidi <[email protected]>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

app: android App: android docs Improvements or additions to documentation gateway Gateway runtime size: L

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants