Files
openclaw-assistant/app/src/main/java/com/openclaw/assistant/node/SystemHandler.kt
T
Yu-gaClaude Sonnet 4.6google-labs-jules[bot] <161369871+google-labs-jules[bot]@users.noreply.github.com>
f8eb868e17 Add missing Android node command families (#189)
* Add missing Android node command families (parity gap)

- Implement Notifications, System, Photos, Contacts, Calendar, and Motion command families.
- Add OpenClawNotificationListenerService and NotificationManager for notification support.
- Implement robust runtime permission checks and structured error handling in all handlers.
- Wire new handlers into NodeRuntime, InvokeDispatcher, and ConnectionManager.
- Conditionally advertise capabilities to the gateway based on granted permissions.
- Add unit tests for command routing and handler logic.
- Update AndroidManifest.xml with necessary permissions and service declarations.

Co-authored-by: yuga-hashimoto <74749461+yuga-hashimoto@users.noreply.github.com>

* Refine Android node command families based on PR feedback

- Implement actual Notification actions (dismiss, open) using NotificationListenerService.
- Correct permission gating for Notifications (use Listener Service check instead of POST_NOTIFICATIONS).
- Add runtime permission check for system.notify on Android 13+.
- Improve Calendar event insertion by querying for a writable calendar instead of hardcoding ID 1.
- Update capability advertisement in ConnectionManager to reflect true availability.
- Enhance PhotosHandler and MotionHandler with more complete implementations.
- Update unit tests to match refined permission and action logic.

Co-authored-by: yuga-hashimoto <74749461+yuga-hashimoto@users.noreply.github.com>

* Finalize Android node command families with refined notification support

- Implement dismiss/open notification actions using NotificationListenerService.
- Return NOT_IMPLEMENTED for reply notification action as requested.
- Fix permission gating for notifications (use listener service status).
- Add runtime permission check for system notifications on Android 13+.
- Improve calendar event creation with dynamic writable calendar selection.
- Refine capability advertising in ConnectionManager.
- Clean up unused imports and duplicated logic.
- Update unit tests to match refined behavior.

Co-authored-by: yuga-hashimoto <74749461+yuga-hashimoto@users.noreply.github.com>

* fix: address review issues in node command families PR

- MotionHandler: add close() to unregister SensorEventListener on disconnect
- NodeRuntime: call motionHandler.close() in disconnect() for proper cleanup
- OpenClawNotificationListenerService: add @Volatile to companion manager field
- SystemHandlerTest: replace unconditional SDK_INT branch with asserting tests

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* feat: add on-demand permission requests to new node command handlers

Contacts, Calendar, Photos, and Motion handlers now use PermissionRequester
to show the system permission dialog when a command is first invoked,
matching the existing SMS/Camera handler pattern.

- ContactsHandler, CalendarHandler, PhotosHandler, MotionHandler:
  add attachPermissionRequester() + ensureXPermission() suspend helpers,
  convert handle methods to suspend fun
- PermissionRequester: add human-readable labels for new permissions
- NodeRuntime: expose attachPermissionRequester() delegating to all handlers
- MainActivity: wire permissionRequester to new handlers on startup
- InvokeDispatcherTest: update every -> coEvery for suspend handler mocks

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* feat: guide user to notification listener settings via dialog

When notifications.list or notifications.actions is invoked without
notification access, show an AlertDialog explaining the requirement
and open ACTION_NOTIFICATION_LISTENER_SETTINGS on confirmation.

- PermissionRequester: add requestNotificationAccess() suspend fun
- NotificationsHandler: add PermissionRequester wiring, make handle
  methods suspend, call requestNotificationAccess() before error return
- NodeRuntime.attachPermissionRequester: include notificationsHandler

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* add funding

---------

Co-authored-by: google-labs-jules[bot] <161369871+google-labs-jules[bot]@users.noreply.github.com>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-01 00:28:27 -08:00

63 lines
2.5 KiB
Kotlin

package com.openclaw.assistant.node
import android.Manifest
import android.app.NotificationChannel
import android.app.NotificationManager
import android.content.Context
import android.content.pm.PackageManager
import android.os.Build
import androidx.core.app.NotificationCompat
import androidx.core.content.ContextCompat
import com.openclaw.assistant.R
import com.openclaw.assistant.gateway.GatewaySession
import kotlinx.serialization.json.Json
import kotlinx.serialization.json.JsonPrimitive
import kotlinx.serialization.json.jsonObject
class SystemHandler(private val appContext: Context) {
private val json = Json { ignoreUnknownKeys = true }
fun handleNotify(paramsJson: String?): GatewaySession.InvokeResult {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
if (ContextCompat.checkSelfPermission(appContext, Manifest.permission.POST_NOTIFICATIONS) != PackageManager.PERMISSION_GRANTED) {
return GatewaySession.InvokeResult.error("PERMISSION_REQUIRED", "POST_NOTIFICATIONS permission required")
}
}
val params = paramsJson?.let {
try {
json.parseToJsonElement(it).jsonObject
} catch (e: Exception) {
null
}
} ?: return GatewaySession.InvokeResult.error("INVALID_REQUEST", "Expected JSON object")
val title = (params["title"] as? JsonPrimitive)?.content ?: "OpenClaw Assistant"
val message = (params["message"] as? JsonPrimitive)?.content ?: ""
if (message.isEmpty()) {
return GatewaySession.InvokeResult.error("INVALID_REQUEST", "Message is required")
}
val notificationManager = appContext.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
val channelId = "openclaw_system"
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
val channel = NotificationChannel(channelId, "OpenClaw System Notifications", NotificationManager.IMPORTANCE_DEFAULT)
notificationManager.createNotificationChannel(channel)
}
val notification = NotificationCompat.Builder(appContext, channelId)
.setSmallIcon(R.mipmap.ic_launcher)
.setContentTitle(title)
.setContentText(message)
.setAutoCancel(true)
.build()
notificationManager.notify(System.currentTimeMillis().toInt(), notification)
return GatewaySession.InvokeResult.ok("""{"ok":true}""")
}
}