From 73677f2707e1478859ee838f2352b84bbecfc94b Mon Sep 17 00:00:00 2001 From: Ayaan Zaidi Date: Wed, 25 Feb 2026 17:52:42 +0530 Subject: [PATCH] refactor(android): remove legacy voice wake controls from settings --- .../ai/openclaw/android/ui/OnboardingFlow.kt | 2 +- .../ai/openclaw/android/ui/SettingsSheet.kt | 183 +++++------------- 2 files changed, 48 insertions(+), 137 deletions(-) diff --git a/apps/android/app/src/main/java/ai/openclaw/android/ui/OnboardingFlow.kt b/apps/android/app/src/main/java/ai/openclaw/android/ui/OnboardingFlow.kt index d9f3ec44fa6..4c9e064e6af 100644 --- a/apps/android/app/src/main/java/ai/openclaw/android/ui/OnboardingFlow.kt +++ b/apps/android/app/src/main/java/ai/openclaw/android/ui/OnboardingFlow.kt @@ -1053,7 +1053,7 @@ private fun PermissionsStep( } PermissionToggleRow( title = "Microphone", - subtitle = "Talk mode + voice features", + subtitle = "Voice tab transcription", checked = enableMicrophone, granted = isPermissionGranted(context, Manifest.permission.RECORD_AUDIO), onCheckedChange = onMicrophoneChange, diff --git a/apps/android/app/src/main/java/ai/openclaw/android/ui/SettingsSheet.kt b/apps/android/app/src/main/java/ai/openclaw/android/ui/SettingsSheet.kt index 2a6219578c7..00d55b7c74c 100644 --- a/apps/android/app/src/main/java/ai/openclaw/android/ui/SettingsSheet.kt +++ b/apps/android/app/src/main/java/ai/openclaw/android/ui/SettingsSheet.kt @@ -9,7 +9,6 @@ import android.os.Build import android.provider.Settings import androidx.activity.compose.rememberLauncherForActivityResult import androidx.activity.result.contract.ActivityResultContracts -import androidx.compose.animation.AnimatedVisibility import androidx.compose.foundation.background import androidx.compose.foundation.border import androidx.compose.foundation.layout.Box @@ -32,8 +31,6 @@ import androidx.compose.foundation.layout.windowInsetsPadding import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.items import androidx.compose.foundation.lazy.rememberLazyListState -import androidx.compose.foundation.text.KeyboardActions -import androidx.compose.foundation.text.KeyboardOptions import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material.icons.Icons import androidx.compose.material3.Button @@ -48,7 +45,6 @@ import androidx.compose.material3.RadioButton import androidx.compose.material3.Switch import androidx.compose.material3.Text import androidx.compose.runtime.Composable -import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf @@ -56,11 +52,8 @@ import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier import androidx.compose.ui.draw.alpha -import androidx.compose.ui.focus.onFocusChanged import androidx.compose.ui.platform.LocalContext -import androidx.compose.ui.platform.LocalFocusManager import androidx.compose.ui.graphics.Color -import androidx.compose.ui.text.input.ImeAction import androidx.compose.ui.text.font.FontFamily import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.unit.sp @@ -69,8 +62,6 @@ import androidx.core.content.ContextCompat import ai.openclaw.android.BuildConfig import ai.openclaw.android.LocationMode import ai.openclaw.android.MainViewModel -import ai.openclaw.android.VoiceWakeMode -import ai.openclaw.android.WakeWords @Composable fun SettingsSheet(viewModel: MainViewModel) { @@ -81,16 +72,9 @@ fun SettingsSheet(viewModel: MainViewModel) { val locationMode by viewModel.locationMode.collectAsState() val locationPreciseEnabled by viewModel.locationPreciseEnabled.collectAsState() val preventSleep by viewModel.preventSleep.collectAsState() - val wakeWords by viewModel.wakeWords.collectAsState() - val voiceWakeMode by viewModel.voiceWakeMode.collectAsState() - val voiceWakeStatusText by viewModel.voiceWakeStatusText.collectAsState() - val isConnected by viewModel.isConnected.collectAsState() val canvasDebugStatusEnabled by viewModel.canvasDebugStatusEnabled.collectAsState() val listState = rememberLazyListState() - val (wakeWordsText, setWakeWordsText) = remember { mutableStateOf("") } - val focusManager = LocalFocusManager.current - var wakeWordsHadFocus by remember { mutableStateOf(false) } val deviceModel = remember { listOfNotNull(Build.MANUFACTURER, Build.MODEL) @@ -116,14 +100,6 @@ fun SettingsSheet(viewModel: MainViewModel) { leadingIconColor = mobileTextSecondary, ) - LaunchedEffect(wakeWords) { setWakeWordsText(wakeWords.joinToString(", ")) } - val commitWakeWords = { - val parsed = WakeWords.parseIfChanged(wakeWordsText, wakeWords) - if (parsed != null) { - viewModel.setWakeWords(parsed) - } - } - val permissionLauncher = rememberLauncherForActivityResult(ActivityResultContracts.RequestMultiplePermissions()) { perms -> val cameraOk = perms[Manifest.permission.CAMERA] == true @@ -165,9 +141,16 @@ fun SettingsSheet(viewModel: MainViewModel) { } } + var micPermissionGranted by + remember { + mutableStateOf( + ContextCompat.checkSelfPermission(context, Manifest.permission.RECORD_AUDIO) == + PackageManager.PERMISSION_GRANTED, + ) + } val audioPermissionLauncher = - rememberLauncherForActivityResult(ActivityResultContracts.RequestPermission()) { _ -> - // Status text is handled by NodeRuntime. + rememberLauncherForActivityResult(ActivityResultContracts.RequestPermission()) { granted -> + micPermissionGranted = granted } val smsPermissionAvailable = @@ -302,7 +285,7 @@ fun SettingsSheet(viewModel: MainViewModel) { item { HorizontalDivider(color = mobileBorder) } - // Voice + // Voice item { Text( "VOICE", @@ -310,120 +293,48 @@ fun SettingsSheet(viewModel: MainViewModel) { color = mobileAccent, ) } - item { - val enabled = voiceWakeMode != VoiceWakeMode.Off - ListItem( - modifier = settingsRowModifier(), - colors = listItemColors, - headlineContent = { Text("Voice Wake", style = mobileHeadline) }, - supportingContent = { Text(voiceWakeStatusText, style = mobileCallout) }, - trailingContent = { - Switch( - checked = enabled, - onCheckedChange = { on -> - if (on) { - val micOk = - ContextCompat.checkSelfPermission(context, Manifest.permission.RECORD_AUDIO) == - PackageManager.PERMISSION_GRANTED - if (!micOk) audioPermissionLauncher.launch(Manifest.permission.RECORD_AUDIO) - viewModel.setVoiceWakeMode(VoiceWakeMode.Foreground) + item { + ListItem( + modifier = settingsRowModifier(), + colors = listItemColors, + headlineContent = { Text("Microphone permission", style = mobileHeadline) }, + supportingContent = { + Text( + if (micPermissionGranted) { + "Granted. Use the Voice tab mic button to capture transcript." } else { - viewModel.setVoiceWakeMode(VoiceWakeMode.Off) - } - }, - ) - }, - ) - } - item { - AnimatedVisibility(visible = voiceWakeMode != VoiceWakeMode.Off) { - Column(verticalArrangement = Arrangement.spacedBy(6.dp), modifier = Modifier.fillMaxWidth()) { - ListItem( - modifier = settingsRowModifier(), - colors = listItemColors, - headlineContent = { Text("Foreground Only", style = mobileHeadline) }, - supportingContent = { Text("Listens only while OpenClaw is open.", style = mobileCallout) }, - trailingContent = { - RadioButton( - selected = voiceWakeMode == VoiceWakeMode.Foreground, - onClick = { - val micOk = - ContextCompat.checkSelfPermission(context, Manifest.permission.RECORD_AUDIO) == - PackageManager.PERMISSION_GRANTED - if (!micOk) audioPermissionLauncher.launch(Manifest.permission.RECORD_AUDIO) - viewModel.setVoiceWakeMode(VoiceWakeMode.Foreground) - }, + "Required for Voice tab transcription." + }, + style = mobileCallout, + ) + }, + trailingContent = { + Button( + onClick = { + if (micPermissionGranted) { + openAppSettings(context) + } else { + audioPermissionLauncher.launch(Manifest.permission.RECORD_AUDIO) + } + }, + colors = settingsPrimaryButtonColors(), + shape = RoundedCornerShape(14.dp), + ) { + Text( + if (micPermissionGranted) "Manage" else "Grant", + style = mobileCallout.copy(fontWeight = FontWeight.Bold), ) - }, - ) - ListItem( - modifier = settingsRowModifier(), - colors = listItemColors, - headlineContent = { Text("Always", style = mobileHeadline) }, - supportingContent = { Text("Keeps listening in the background (shows a persistent notification).", style = mobileCallout) }, - trailingContent = { - RadioButton( - selected = voiceWakeMode == VoiceWakeMode.Always, - onClick = { - val micOk = - ContextCompat.checkSelfPermission(context, Manifest.permission.RECORD_AUDIO) == - PackageManager.PERMISSION_GRANTED - if (!micOk) audioPermissionLauncher.launch(Manifest.permission.RECORD_AUDIO) - viewModel.setVoiceWakeMode(VoiceWakeMode.Always) - }, - ) - }, - ) - } - } - } - item { - OutlinedTextField( - value = wakeWordsText, - onValueChange = setWakeWordsText, - label = { Text("Wake Words (comma-separated)", style = mobileCaption1, color = mobileTextSecondary) }, - modifier = - Modifier.fillMaxWidth().onFocusChanged { focusState -> - if (focusState.isFocused) { - wakeWordsHadFocus = true - } else if (wakeWordsHadFocus) { - wakeWordsHadFocus = false - commitWakeWords() } }, - singleLine = true, - keyboardOptions = KeyboardOptions(imeAction = ImeAction.Done), - keyboardActions = - KeyboardActions( - onDone = { - commitWakeWords() - focusManager.clearFocus() - }, - ), - textStyle = mobileBody.copy(color = mobileText), - colors = settingsTextFieldColors(), - ) - } - item { - Button( - onClick = viewModel::resetWakeWordsDefaults, - colors = settingsPrimaryButtonColors(), - shape = RoundedCornerShape(14.dp), - ) { - Text("Reset defaults", style = mobileCallout.copy(fontWeight = FontWeight.Bold)) - } + ) + } + item { + Text( + "Voice wake and talk modes were removed. Voice now uses one mic on/off flow in the Voice tab.", + style = mobileCallout, + color = mobileTextSecondary, + ) } - item { - Text( - if (isConnected) { - "Any node can edit wake words. Changes sync via the gateway." - } else { - "Connect to a gateway to sync wake words globally." - }, - style = mobileCallout, - color = mobileTextSecondary, - ) - } item { HorizontalDivider(color = mobileBorder) }