Android: centralize canvas protocol strings
This commit is contained in:
@@ -16,6 +16,10 @@ import com.steipete.clawdis.node.bridge.BridgePairingClient
|
||||
import com.steipete.clawdis.node.bridge.BridgeSession
|
||||
import com.steipete.clawdis.node.node.CameraCaptureManager
|
||||
import com.steipete.clawdis.node.node.CanvasController
|
||||
import com.steipete.clawdis.node.protocol.ClawdisCapability
|
||||
import com.steipete.clawdis.node.protocol.ClawdisCameraCommand
|
||||
import com.steipete.clawdis.node.protocol.ClawdisCanvasCommand
|
||||
import com.steipete.clawdis.node.protocol.ClawdisInvokeCommandAliases
|
||||
import com.steipete.clawdis.node.voice.VoiceWakeManager
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
@@ -264,15 +268,17 @@ class NodeRuntime(context: Context) {
|
||||
.ifEmpty { null }
|
||||
val resolved =
|
||||
if (storedToken.isNullOrBlank()) {
|
||||
_statusText.value = "Pairing…"
|
||||
val caps = buildList {
|
||||
add("canvas")
|
||||
if (cameraEnabled.value) add("camera")
|
||||
if (voiceWakeMode.value != VoiceWakeMode.Off && hasRecordAudioPermission()) add("voiceWake")
|
||||
}
|
||||
BridgePairingClient().pairAndHello(
|
||||
endpoint = endpoint,
|
||||
hello =
|
||||
_statusText.value = "Pairing…"
|
||||
val caps = buildList {
|
||||
add(ClawdisCapability.Canvas.rawValue)
|
||||
if (cameraEnabled.value) add(ClawdisCapability.Camera.rawValue)
|
||||
if (voiceWakeMode.value != VoiceWakeMode.Off && hasRecordAudioPermission()) {
|
||||
add(ClawdisCapability.VoiceWake.rawValue)
|
||||
}
|
||||
}
|
||||
BridgePairingClient().pairAndHello(
|
||||
endpoint = endpoint,
|
||||
hello =
|
||||
BridgePairingClient.Hello(
|
||||
nodeId = instanceId.value,
|
||||
displayName = displayName.value,
|
||||
@@ -305,17 +311,19 @@ class NodeRuntime(context: Context) {
|
||||
platform = "Android",
|
||||
version = "dev",
|
||||
deviceFamily = "Android",
|
||||
modelIdentifier = modelIdentifier,
|
||||
caps =
|
||||
buildList {
|
||||
add("canvas")
|
||||
if (cameraEnabled.value) add("camera")
|
||||
if (voiceWakeMode.value != VoiceWakeMode.Off && hasRecordAudioPermission()) add("voiceWake")
|
||||
},
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
modelIdentifier = modelIdentifier,
|
||||
caps =
|
||||
buildList {
|
||||
add(ClawdisCapability.Canvas.rawValue)
|
||||
if (cameraEnabled.value) add(ClawdisCapability.Camera.rawValue)
|
||||
if (voiceWakeMode.value != VoiceWakeMode.Off && hasRecordAudioPermission()) {
|
||||
add(ClawdisCapability.VoiceWake.rawValue)
|
||||
}
|
||||
},
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun hasRecordAudioPermission(): Boolean {
|
||||
return (
|
||||
@@ -422,7 +430,13 @@ class NodeRuntime(context: Context) {
|
||||
}
|
||||
|
||||
private suspend fun handleInvoke(command: String, paramsJson: String?): BridgeSession.InvokeResult {
|
||||
if (command.startsWith("canvas.") || command.startsWith("camera.")) {
|
||||
// Back-compat: accept screen.* commands and map them to canvas.*.
|
||||
val canonicalCommand = ClawdisInvokeCommandAliases.canonicalizeScreenToCanvas(command)
|
||||
|
||||
if (
|
||||
canonicalCommand.startsWith(ClawdisCanvasCommand.NamespacePrefix) ||
|
||||
canonicalCommand.startsWith(ClawdisCameraCommand.NamespacePrefix)
|
||||
) {
|
||||
if (!isForeground.value) {
|
||||
return BridgeSession.InvokeResult.error(
|
||||
code = "NODE_BACKGROUND_UNAVAILABLE",
|
||||
@@ -430,27 +444,27 @@ class NodeRuntime(context: Context) {
|
||||
)
|
||||
}
|
||||
}
|
||||
if (command.startsWith("camera.") && !cameraEnabled.value) {
|
||||
if (canonicalCommand.startsWith(ClawdisCameraCommand.NamespacePrefix) && !cameraEnabled.value) {
|
||||
return BridgeSession.InvokeResult.error(
|
||||
code = "CAMERA_DISABLED",
|
||||
message = "CAMERA_DISABLED: enable Camera in Settings",
|
||||
)
|
||||
}
|
||||
|
||||
return when (command) {
|
||||
"canvas.show" -> BridgeSession.InvokeResult.ok(null)
|
||||
"canvas.hide" -> BridgeSession.InvokeResult.ok(null)
|
||||
"canvas.setMode" -> {
|
||||
return when (canonicalCommand) {
|
||||
ClawdisCanvasCommand.Show.rawValue -> BridgeSession.InvokeResult.ok(null)
|
||||
ClawdisCanvasCommand.Hide.rawValue -> BridgeSession.InvokeResult.ok(null)
|
||||
ClawdisCanvasCommand.SetMode.rawValue -> {
|
||||
val mode = CanvasController.parseMode(paramsJson)
|
||||
canvas.setMode(mode)
|
||||
BridgeSession.InvokeResult.ok(null)
|
||||
}
|
||||
"canvas.navigate" -> {
|
||||
ClawdisCanvasCommand.Navigate.rawValue -> {
|
||||
val url = CanvasController.parseNavigateUrl(paramsJson)
|
||||
if (url != null) canvas.navigate(url)
|
||||
BridgeSession.InvokeResult.ok(null)
|
||||
}
|
||||
"canvas.eval" -> {
|
||||
ClawdisCanvasCommand.Eval.rawValue -> {
|
||||
val js =
|
||||
CanvasController.parseEvalJs(paramsJson)
|
||||
?: return BridgeSession.InvokeResult.error(
|
||||
@@ -468,7 +482,7 @@ class NodeRuntime(context: Context) {
|
||||
}
|
||||
BridgeSession.InvokeResult.ok("""{"result":${result.toJsonString()}}""")
|
||||
}
|
||||
"canvas.snapshot" -> {
|
||||
ClawdisCanvasCommand.Snapshot.rawValue -> {
|
||||
val maxWidth = CanvasController.parseSnapshotMaxWidth(paramsJson)
|
||||
val base64 =
|
||||
try {
|
||||
@@ -481,11 +495,11 @@ class NodeRuntime(context: Context) {
|
||||
}
|
||||
BridgeSession.InvokeResult.ok("""{"format":"png","base64":"$base64"}""")
|
||||
}
|
||||
"camera.snap" -> {
|
||||
ClawdisCameraCommand.Snap.rawValue -> {
|
||||
val res = camera.snap(paramsJson)
|
||||
BridgeSession.InvokeResult.ok(res.payloadJson)
|
||||
}
|
||||
"camera.clip" -> {
|
||||
ClawdisCameraCommand.Clip.rawValue -> {
|
||||
val includeAudio = paramsJson?.contains("\"includeAudio\":true") != false
|
||||
if (includeAudio) externalAudioCaptureActive.value = true
|
||||
try {
|
||||
|
||||
@@ -0,0 +1,55 @@
|
||||
package com.steipete.clawdis.node.protocol
|
||||
|
||||
enum class ClawdisCapability(val rawValue: String) {
|
||||
Canvas("canvas"),
|
||||
Camera("camera"),
|
||||
VoiceWake("voiceWake"),
|
||||
}
|
||||
|
||||
enum class ClawdisScreenCommand(val rawValue: String) {
|
||||
Show("screen.show"),
|
||||
Hide("screen.hide"),
|
||||
SetMode("screen.setMode"),
|
||||
Navigate("screen.navigate"),
|
||||
Eval("screen.eval"),
|
||||
Snapshot("screen.snapshot"),
|
||||
;
|
||||
|
||||
companion object {
|
||||
const val NamespacePrefix: String = "screen."
|
||||
}
|
||||
}
|
||||
|
||||
enum class ClawdisCanvasCommand(val rawValue: String) {
|
||||
Show("canvas.show"),
|
||||
Hide("canvas.hide"),
|
||||
SetMode("canvas.setMode"),
|
||||
Navigate("canvas.navigate"),
|
||||
Eval("canvas.eval"),
|
||||
Snapshot("canvas.snapshot"),
|
||||
;
|
||||
|
||||
companion object {
|
||||
const val NamespacePrefix: String = "canvas."
|
||||
}
|
||||
}
|
||||
|
||||
enum class ClawdisCameraCommand(val rawValue: String) {
|
||||
Snap("camera.snap"),
|
||||
Clip("camera.clip"),
|
||||
;
|
||||
|
||||
companion object {
|
||||
const val NamespacePrefix: String = "camera."
|
||||
}
|
||||
}
|
||||
|
||||
object ClawdisInvokeCommandAliases {
|
||||
fun canonicalizeScreenToCanvas(command: String): String {
|
||||
if (command.startsWith(ClawdisScreenCommand.NamespacePrefix)) {
|
||||
return ClawdisCanvasCommand.NamespacePrefix +
|
||||
command.removePrefix(ClawdisScreenCommand.NamespacePrefix)
|
||||
}
|
||||
return command
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
package com.steipete.clawdis.node.protocol
|
||||
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Test
|
||||
|
||||
class ClawdisProtocolConstantsTest {
|
||||
@Test
|
||||
fun mapsKnownScreenCommandsToCanvas() {
|
||||
val mappings =
|
||||
listOf(
|
||||
Pair(ClawdisScreenCommand.Show, ClawdisCanvasCommand.Show),
|
||||
Pair(ClawdisScreenCommand.Hide, ClawdisCanvasCommand.Hide),
|
||||
Pair(ClawdisScreenCommand.SetMode, ClawdisCanvasCommand.SetMode),
|
||||
Pair(ClawdisScreenCommand.Navigate, ClawdisCanvasCommand.Navigate),
|
||||
Pair(ClawdisScreenCommand.Eval, ClawdisCanvasCommand.Eval),
|
||||
Pair(ClawdisScreenCommand.Snapshot, ClawdisCanvasCommand.Snapshot),
|
||||
)
|
||||
|
||||
for ((screen, canvas) in mappings) {
|
||||
assertEquals(
|
||||
canvas.rawValue,
|
||||
ClawdisInvokeCommandAliases.canonicalizeScreenToCanvas(screen.rawValue),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun mapsUnknownScreenNamespaceToCanvas() {
|
||||
assertEquals("canvas.foo", ClawdisInvokeCommandAliases.canonicalizeScreenToCanvas("screen.foo"))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun leavesNonScreenCommandsUnchanged() {
|
||||
assertEquals(
|
||||
ClawdisCameraCommand.Snap.rawValue,
|
||||
ClawdisInvokeCommandAliases.canonicalizeScreenToCanvas(ClawdisCameraCommand.Snap.rawValue),
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun capabilitiesUseStableStrings() {
|
||||
assertEquals("canvas", ClawdisCapability.Canvas.rawValue)
|
||||
assertEquals("camera", ClawdisCapability.Camera.rawValue)
|
||||
assertEquals("voiceWake", ClawdisCapability.VoiceWake.rawValue)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user