refactor(android): share node JSON param parsing helpers

This commit is contained in:
Peter Steinberger
2026-03-02 14:35:20 +00:00
parent e23b6fb2ba
commit b85facfb5d
3 changed files with 45 additions and 61 deletions

View File

@@ -33,10 +33,7 @@ import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.suspendCancellableCoroutine import kotlinx.coroutines.suspendCancellableCoroutine
import kotlinx.coroutines.withTimeout import kotlinx.coroutines.withTimeout
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import kotlinx.serialization.json.Json
import kotlinx.serialization.json.JsonObject import kotlinx.serialization.json.JsonObject
import kotlinx.serialization.json.JsonPrimitive
import kotlinx.serialization.json.contentOrNull
import java.io.ByteArrayOutputStream import java.io.ByteArrayOutputStream
import java.io.File import java.io.File
import java.util.concurrent.Executor import java.util.concurrent.Executor
@@ -101,7 +98,7 @@ class CameraCaptureManager(private val context: Context) {
withContext(Dispatchers.Main) { withContext(Dispatchers.Main) {
ensureCameraPermission() ensureCameraPermission()
val owner = lifecycleOwner ?: throw IllegalStateException("UNAVAILABLE: camera not ready") val owner = lifecycleOwner ?: throw IllegalStateException("UNAVAILABLE: camera not ready")
val params = parseParamsObject(paramsJson) val params = parseJsonParamsObject(paramsJson)
val facing = parseFacing(params) ?: "front" val facing = parseFacing(params) ?: "front"
val quality = (parseQuality(params) ?: 0.95).coerceIn(0.1, 1.0) val quality = (parseQuality(params) ?: 0.95).coerceIn(0.1, 1.0)
val maxWidth = parseMaxWidth(params) ?: 1600 val maxWidth = parseMaxWidth(params) ?: 1600
@@ -167,7 +164,7 @@ class CameraCaptureManager(private val context: Context) {
withContext(Dispatchers.Main) { withContext(Dispatchers.Main) {
ensureCameraPermission() ensureCameraPermission()
val owner = lifecycleOwner ?: throw IllegalStateException("UNAVAILABLE: camera not ready") val owner = lifecycleOwner ?: throw IllegalStateException("UNAVAILABLE: camera not ready")
val params = parseParamsObject(paramsJson) val params = parseJsonParamsObject(paramsJson)
val facing = parseFacing(params) ?: "front" val facing = parseFacing(params) ?: "front"
val durationMs = (parseDurationMs(params) ?: 3_000).coerceIn(200, 60_000) val durationMs = (parseDurationMs(params) ?: 3_000).coerceIn(200, 60_000)
val includeAudio = parseIncludeAudio(params) ?: true val includeAudio = parseIncludeAudio(params) ?: true
@@ -293,20 +290,8 @@ class CameraCaptureManager(private val context: Context) {
return rotated return rotated
} }
private fun parseParamsObject(paramsJson: String?): JsonObject? {
if (paramsJson.isNullOrBlank()) return null
return try {
Json.parseToJsonElement(paramsJson).asObjectOrNull()
} catch (_: Throwable) {
null
}
}
private fun readPrimitive(params: JsonObject?, key: String): JsonPrimitive? =
params?.get(key) as? JsonPrimitive
private fun parseFacing(params: JsonObject?): String? { private fun parseFacing(params: JsonObject?): String? {
val value = readPrimitive(params, "facing")?.contentOrNull?.trim()?.lowercase() ?: return null val value = parseJsonString(params, "facing")?.trim()?.lowercase() ?: return null
return when (value) { return when (value) {
"front", "back" -> value "front", "back" -> value
else -> null else -> null
@@ -314,31 +299,21 @@ class CameraCaptureManager(private val context: Context) {
} }
private fun parseQuality(params: JsonObject?): Double? = private fun parseQuality(params: JsonObject?): Double? =
readPrimitive(params, "quality")?.contentOrNull?.toDoubleOrNull() parseJsonDouble(params, "quality")
private fun parseMaxWidth(params: JsonObject?): Int? = private fun parseMaxWidth(params: JsonObject?): Int? =
readPrimitive(params, "maxWidth") parseJsonInt(params, "maxWidth")
?.contentOrNull
?.toIntOrNull()
?.takeIf { it > 0 } ?.takeIf { it > 0 }
private fun parseDurationMs(params: JsonObject?): Int? = private fun parseDurationMs(params: JsonObject?): Int? =
readPrimitive(params, "durationMs")?.contentOrNull?.toIntOrNull() parseJsonInt(params, "durationMs")
private fun parseDeviceId(params: JsonObject?): String? = private fun parseDeviceId(params: JsonObject?): String? =
readPrimitive(params, "deviceId") parseJsonString(params, "deviceId")
?.contentOrNull
?.trim() ?.trim()
?.takeIf { it.isNotEmpty() } ?.takeIf { it.isNotEmpty() }
private fun parseIncludeAudio(params: JsonObject?): Boolean? { private fun parseIncludeAudio(params: JsonObject?): Boolean? = parseJsonBooleanFlag(params, "includeAudio")
val value = readPrimitive(params, "includeAudio")?.contentOrNull?.trim()?.lowercase()
return when (value) {
"true" -> true
"false" -> false
else -> null
}
}
private fun Context.mainExecutor(): Executor = ContextCompat.getMainExecutor(this) private fun Context.mainExecutor(): Executor = ContextCompat.getMainExecutor(this)

View File

@@ -1,10 +1,12 @@
package ai.openclaw.android.node package ai.openclaw.android.node
import ai.openclaw.android.gateway.parseInvokeErrorFromThrowable import ai.openclaw.android.gateway.parseInvokeErrorFromThrowable
import kotlinx.serialization.json.Json
import kotlinx.serialization.json.JsonElement import kotlinx.serialization.json.JsonElement
import kotlinx.serialization.json.JsonNull import kotlinx.serialization.json.JsonNull
import kotlinx.serialization.json.JsonObject import kotlinx.serialization.json.JsonObject
import kotlinx.serialization.json.JsonPrimitive import kotlinx.serialization.json.JsonPrimitive
import kotlinx.serialization.json.contentOrNull
const val DEFAULT_SEAM_COLOR_ARGB: Long = 0xFF4F7A9A const val DEFAULT_SEAM_COLOR_ARGB: Long = 0xFF4F7A9A
@@ -21,6 +23,35 @@ fun String.toJsonString(): String {
fun JsonElement?.asObjectOrNull(): JsonObject? = this as? JsonObject fun JsonElement?.asObjectOrNull(): JsonObject? = this as? JsonObject
fun parseJsonParamsObject(paramsJson: String?): JsonObject? {
if (paramsJson.isNullOrBlank()) return null
return try {
Json.parseToJsonElement(paramsJson).asObjectOrNull()
} catch (_: Throwable) {
null
}
}
fun readJsonPrimitive(params: JsonObject?, key: String): JsonPrimitive? = params?.get(key) as? JsonPrimitive
fun parseJsonInt(params: JsonObject?, key: String): Int? =
readJsonPrimitive(params, key)?.contentOrNull?.toIntOrNull()
fun parseJsonDouble(params: JsonObject?, key: String): Double? =
readJsonPrimitive(params, key)?.contentOrNull?.toDoubleOrNull()
fun parseJsonString(params: JsonObject?, key: String): String? =
readJsonPrimitive(params, key)?.contentOrNull
fun parseJsonBooleanFlag(params: JsonObject?, key: String): Boolean? {
val value = readJsonPrimitive(params, key)?.contentOrNull?.trim()?.lowercase() ?: return null
return when (value) {
"true" -> true
"false" -> false
else -> null
}
}
fun JsonElement?.asStringOrNull(): String? = fun JsonElement?.asStringOrNull(): String? =
when (this) { when (this) {
is JsonNull -> null is JsonNull -> null

View File

@@ -10,10 +10,7 @@ import ai.openclaw.android.ScreenCaptureRequester
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.delay import kotlinx.coroutines.delay
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import kotlinx.serialization.json.Json
import kotlinx.serialization.json.JsonObject import kotlinx.serialization.json.JsonObject
import kotlinx.serialization.json.JsonPrimitive
import kotlinx.serialization.json.contentOrNull
import java.io.File import java.io.File
import kotlin.math.roundToInt import kotlin.math.roundToInt
@@ -39,7 +36,7 @@ class ScreenRecordManager(private val context: Context) {
"SCREEN_PERMISSION_REQUIRED: grant Screen Recording permission", "SCREEN_PERMISSION_REQUIRED: grant Screen Recording permission",
) )
val params = parseParamsObject(paramsJson) val params = parseJsonParamsObject(paramsJson)
val durationMs = (parseDurationMs(params) ?: 10_000).coerceIn(250, 60_000) val durationMs = (parseDurationMs(params) ?: 10_000).coerceIn(250, 60_000)
val fps = (parseFps(params) ?: 10.0).coerceIn(1.0, 60.0) val fps = (parseFps(params) ?: 10.0).coerceIn(1.0, 60.0)
val fpsInt = fps.roundToInt().coerceIn(1, 60) val fpsInt = fps.roundToInt().coerceIn(1, 60)
@@ -146,38 +143,19 @@ class ScreenRecordManager(private val context: Context) {
} }
} }
private fun parseParamsObject(paramsJson: String?): JsonObject? {
if (paramsJson.isNullOrBlank()) return null
return try {
Json.parseToJsonElement(paramsJson).asObjectOrNull()
} catch (_: Throwable) {
null
}
}
private fun readPrimitive(params: JsonObject?, key: String): JsonPrimitive? =
params?.get(key) as? JsonPrimitive
private fun parseDurationMs(params: JsonObject?): Int? = private fun parseDurationMs(params: JsonObject?): Int? =
readPrimitive(params, "durationMs")?.contentOrNull?.toIntOrNull() parseJsonInt(params, "durationMs")
private fun parseFps(params: JsonObject?): Double? = private fun parseFps(params: JsonObject?): Double? =
readPrimitive(params, "fps")?.contentOrNull?.toDoubleOrNull() parseJsonDouble(params, "fps")
private fun parseScreenIndex(params: JsonObject?): Int? = private fun parseScreenIndex(params: JsonObject?): Int? =
readPrimitive(params, "screenIndex")?.contentOrNull?.toIntOrNull() parseJsonInt(params, "screenIndex")
private fun parseIncludeAudio(params: JsonObject?): Boolean? { private fun parseIncludeAudio(params: JsonObject?): Boolean? = parseJsonBooleanFlag(params, "includeAudio")
val value = readPrimitive(params, "includeAudio")?.contentOrNull?.trim()?.lowercase()
return when (value) {
"true" -> true
"false" -> false
else -> null
}
}
private fun parseString(params: JsonObject?, key: String): String? = private fun parseString(params: JsonObject?, key: String): String? =
readPrimitive(params, key)?.contentOrNull parseJsonString(params, key)
private fun estimateBitrate(width: Int, height: Int, fps: Int): Int { private fun estimateBitrate(width: Int, height: Int, fps: Int): Int {
val pixels = width.toLong() * height.toLong() val pixels = width.toLong() * height.toLong()