Skip to content

Commit 2dbd12f

Browse files
committed
fix(android): keep one-shot PTT retries idempotent
1 parent fa2dba6 commit 2dbd12f

3 files changed

Lines changed: 15 additions & 12 deletions

File tree

apps/.i18n/native-source.json

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -203,23 +203,23 @@
203203
},
204204
{
205205
"kind": "conditional-branch",
206-
"line": 2440,
206+
"line": 2444,
207207
"path": "apps/android/app/src/main/java/ai/openclaw/app/NodeRuntime.kt",
208208
"source": "Failed: this host requires wss:// or Tailscale Serve. No TLS endpoint detected.",
209209
"surface": "android",
210210
"id": "native.android.81009c40eed2216d"
211211
},
212212
{
213213
"kind": "conditional-branch",
214-
"line": 2442,
214+
"line": 2446,
215215
"path": "apps/android/app/src/main/java/ai/openclaw/app/NodeRuntime.kt",
216216
"source": "Failed: secure endpoint reached, but TLS fingerprint verification timed out. Check Tailscale Serve or gateway TLS and retry.",
217217
"surface": "android",
218218
"id": "native.android.467899bb510b8e34"
219219
},
220220
{
221221
"kind": "conditional-branch",
222-
"line": 2444,
222+
"line": 2448,
223223
"path": "apps/android/app/src/main/java/ai/openclaw/app/NodeRuntime.kt",
224224
"source": "Failed: couldn't reach the secure gateway endpoint for this host.",
225225
"surface": "android",

apps/android/app/src/main/java/ai/openclaw/app/NodeRuntime.kt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1759,6 +1759,10 @@ class NodeRuntime private constructor(
17591759

17601760
private suspend fun handleTalkPttOnce(): GatewaySession.InvokeResult =
17611761
runTalkPttCommand {
1762+
talkMode.activePushToTalkCaptureId?.let { captureId ->
1763+
val payload = TalkPttStopPayload(captureId = captureId, transcript = null, status = "busy")
1764+
return@runTalkPttCommand GatewaySession.InvokeResult.ok(payload.toJson())
1765+
}
17621766
val lifecycleEpoch = voiceLifecycleEpoch.get()
17631767
val commandEpoch = talkPttCommandEpoch.get()
17641768
val start =

apps/android/app/src/test/java/ai/openclaw/app/GatewayBootstrapAuthTest.kt

Lines changed: 8 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -993,26 +993,25 @@ class GatewayBootstrapAuthTest {
993993
}
994994

995995
@Test
996-
@OptIn(ExperimentalCoroutinesApi::class)
997-
fun talkPttOnceRetryPreservesCaptureCleanupOwnership() =
996+
fun talkPttOnceRetryReturnsBusyWithoutPreparingCapture() =
998997
runBlocking {
999998
val app = RuntimeEnvironment.getApplication()
1000999
shadowOf(app).grantPermissions(Manifest.permission.RECORD_AUDIO)
10011000
val runtime = createTestRuntime(app)
10021001
val talkMode = readField<Lazy<TalkModeManager>>(runtime, "talkMode\$delegate").value
10031002
writeField(talkMode, "activePttCaptureId", "capture-1")
10041003
val dispatcher = readField<InvokeDispatcher>(runtime, "invokeDispatcher")
1005-
Dispatchers.setMain(Dispatchers.Unconfined)
1004+
val preparationMutex = readField<Mutex>(runtime, "voiceCapturePreparationMutex")
1005+
preparationMutex.lock()
10061006
try {
1007-
val retry = dispatcher.handleInvoke(OpenClawTalkCommand.PttOnce.rawValue, null)
1007+
val retry =
1008+
withTimeout(1_000) { dispatcher.handleInvoke(OpenClawTalkCommand.PttOnce.rawValue, null) }
10081009
assertNull(retry.error)
1009-
assertTrue(readField<MutableStateFlow<Boolean>>(runtime, "externalAudioCaptureActive").value)
1010-
1011-
val cancel = dispatcher.handleInvoke(OpenClawTalkCommand.PttCancel.rawValue, null)
1012-
assertNull(cancel.error)
1010+
assertEquals("""{"captureId":"capture-1","status":"busy"}""", retry.payloadJson)
1011+
assertEquals("capture-1", talkMode.activePushToTalkCaptureId)
10131012
assertFalse(readField<MutableStateFlow<Boolean>>(runtime, "externalAudioCaptureActive").value)
10141013
} finally {
1015-
Dispatchers.resetMain()
1014+
preparationMutex.unlock()
10161015
}
10171016
}
10181017

0 commit comments

Comments
 (0)