From 8ab36e4308df022aeb1c295b2603c58b15dfd5f3 Mon Sep 17 00:00:00 2001 From: joshavant <830519+joshavant@users.noreply.github.com> Date: Thu, 18 Jun 2026 17:48:59 +0200 Subject: [PATCH] android: mark manual mic as microphone foreground service --- .../ai/openclaw/app/NodeForegroundService.kt | 9 +++++---- .../main/java/ai/openclaw/app/NodeRuntime.kt | 6 +++++- .../openclaw/app/GatewayBootstrapAuthTest.kt | 19 +++++++++++++++++++ .../openclaw/app/NodeForegroundServiceTest.kt | 4 ++-- 4 files changed, 31 insertions(+), 7 deletions(-) diff --git a/apps/android/app/src/main/java/ai/openclaw/app/NodeForegroundService.kt b/apps/android/app/src/main/java/ai/openclaw/app/NodeForegroundService.kt index 66b95023a581..16e3d4212c5b 100644 --- a/apps/android/app/src/main/java/ai/openclaw/app/NodeForegroundService.kt +++ b/apps/android/app/src/main/java/ai/openclaw/app/NodeForegroundService.kt @@ -223,10 +223,11 @@ class NodeForegroundService : Service() { internal fun foregroundServiceTypesForVoiceMode(mode: VoiceCaptureMode): Int { val base = ServiceInfo.FOREGROUND_SERVICE_TYPE_CONNECTED_DEVICE - return if (mode == VoiceCaptureMode.TalkMode) { - base or ServiceInfo.FOREGROUND_SERVICE_TYPE_MICROPHONE - } else { - base + return when (mode) { + VoiceCaptureMode.Off -> base + VoiceCaptureMode.ManualMic, + VoiceCaptureMode.TalkMode, + -> base or ServiceInfo.FOREGROUND_SERVICE_TYPE_MICROPHONE } } diff --git a/apps/android/app/src/main/java/ai/openclaw/app/NodeRuntime.kt b/apps/android/app/src/main/java/ai/openclaw/app/NodeRuntime.kt index 2cb0c4849ccd..f900b1b0b3ee 100644 --- a/apps/android/app/src/main/java/ai/openclaw/app/NodeRuntime.kt +++ b/apps/android/app/src/main/java/ai/openclaw/app/NodeRuntime.kt @@ -1396,8 +1396,9 @@ class NodeRuntime( mode: VoiceCaptureMode, persistManualMic: Boolean = true, ) { - if (mode == VoiceCaptureMode.TalkMode && !hasRecordAudioPermission()) { + if (mode.requiresMicrophonePermission && !hasRecordAudioPermission()) { _voiceCaptureMode.value = VoiceCaptureMode.Off + prefs.setVoiceMicEnabled(false) externalAudioCaptureActive.value = false return } @@ -1468,6 +1469,9 @@ class NodeRuntime( } } + private val VoiceCaptureMode.requiresMicrophonePermission: Boolean + get() = this == VoiceCaptureMode.ManualMic || this == VoiceCaptureMode.TalkMode + fun refreshGatewayConnection() { val endpoint = connectedEndpoint if (endpoint == null) { diff --git a/apps/android/app/src/test/java/ai/openclaw/app/GatewayBootstrapAuthTest.kt b/apps/android/app/src/test/java/ai/openclaw/app/GatewayBootstrapAuthTest.kt index 2316d8ddfda7..27bc494dbe8f 100644 --- a/apps/android/app/src/test/java/ai/openclaw/app/GatewayBootstrapAuthTest.kt +++ b/apps/android/app/src/test/java/ai/openclaw/app/GatewayBootstrapAuthTest.kt @@ -376,6 +376,25 @@ class GatewayBootstrapAuthTest { assertNull(authStore.loadToken(deviceId, "operator")) } + @Test + fun restoredManualMicWithoutRecordAudioClearsStalePreference() { + val app = RuntimeEnvironment.getApplication() + shadowOf(app).denyPermissions(Manifest.permission.RECORD_AUDIO) + val securePrefs = + app.getSharedPreferences( + "openclaw.node.secure.test.${UUID.randomUUID()}", + android.content.Context.MODE_PRIVATE, + ) + val prefs = SecurePrefs(app, securePrefsOverride = securePrefs) + prefs.setVoiceMicEnabled(true) + + val runtime = NodeRuntime(app, prefs) + + assertEquals(VoiceCaptureMode.Off, runtime.voiceCaptureMode.value) + assertFalse(prefs.voiceMicEnabled.value) + assertFalse(readField>(runtime, "externalAudioCaptureActive").value) + } + @Test fun talkPttStart_cleansPreparedCaptureWhenBeginFails() = runBlocking { diff --git a/apps/android/app/src/test/java/ai/openclaw/app/NodeForegroundServiceTest.kt b/apps/android/app/src/test/java/ai/openclaw/app/NodeForegroundServiceTest.kt index 2c982de07e12..4cb74be06a4a 100644 --- a/apps/android/app/src/test/java/ai/openclaw/app/NodeForegroundServiceTest.kt +++ b/apps/android/app/src/test/java/ai/openclaw/app/NodeForegroundServiceTest.kt @@ -32,13 +32,13 @@ class NodeForegroundServiceTest { } @Test - fun foregroundServiceTypesForVoiceMode_addsMicrophoneOnlyForTalkMode() { + fun foregroundServiceTypesForVoiceMode_addsMicrophoneForActiveCaptureModes() { assertEquals( ServiceInfo.FOREGROUND_SERVICE_TYPE_CONNECTED_DEVICE, foregroundServiceTypesForVoiceMode(VoiceCaptureMode.Off), ) assertEquals( - ServiceInfo.FOREGROUND_SERVICE_TYPE_CONNECTED_DEVICE, + ServiceInfo.FOREGROUND_SERVICE_TYPE_CONNECTED_DEVICE or ServiceInfo.FOREGROUND_SERVICE_TYPE_MICROPHONE, foregroundServiceTypesForVoiceMode(VoiceCaptureMode.ManualMic), ) assertEquals(