Skip to content

Commit 5e342c7

Browse files
improve: refresh Android overview control surface (#95557)
* improve android overview control surface * fix android lint gates * fix android voice e2e debug broadcast * harden android voice e2e receiver * fix(android): clarify Talk entry copy --------- Co-authored-by: joshavant <[email protected]>
1 parent 321e58c commit 5e342c7

15 files changed

Lines changed: 1031 additions & 262 deletions

File tree

apps/android/app/src/debug/AndroidManifest.xml

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,16 @@
11
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
2+
3+
<permission
4+
android:name="${applicationId}.permission.RUN_VOICE_E2E"
5+
android:protectionLevel="signature" />
6+
7+
<uses-permission android:name="${applicationId}.permission.RUN_VOICE_E2E" />
8+
29
<application>
310
<receiver
411
android:name=".VoiceE2eReceiver"
5-
android:exported="true">
12+
android:permission="${applicationId}.permission.RUN_VOICE_E2E"
13+
android:exported="false">
614
<intent-filter>
715
<action android:name="ai.openclaw.app.debug.RUN_VOICE_E2E" />
816
</intent-filter>

apps/android/app/src/main/java/ai/openclaw/app/chat/ChatController.kt

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -538,8 +538,7 @@ class ChatController internal constructor(
538538
}
539539
}
540540

541-
private fun parseEventSessionEntry(payload: JsonObject): ChatSessionEntry? =
542-
payload["session"].asObjectOrNull()?.let(::parseSessionEntry) ?: parseSessionEntry(payload)
541+
private fun parseEventSessionEntry(payload: JsonObject): ChatSessionEntry? = payload["session"].asObjectOrNull()?.let(::parseSessionEntry) ?: parseSessionEntry(payload)
543542

544543
private fun handleAgentEvent(payloadJson: String) {
545544
val payload = json.parseToJsonElement(payloadJson).asObjectOrNull() ?: return
@@ -747,9 +746,16 @@ class ChatController internal constructor(
747746
): ChatSessionEntry? {
748747
if (obj == null) return null
749748
val key =
750-
obj["key"].asStringOrNull()?.trim().orEmpty()
751-
.ifEmpty { obj["sessionKey"].asStringOrNull()?.trim().orEmpty() }
752-
.ifEmpty { fallbackKey?.trim().orEmpty() }
749+
obj["key"]
750+
.asStringOrNull()
751+
?.trim()
752+
.orEmpty()
753+
.ifEmpty {
754+
obj["sessionKey"]
755+
.asStringOrNull()
756+
?.trim()
757+
.orEmpty()
758+
}.ifEmpty { fallbackKey?.trim().orEmpty() }
753759
if (key.isEmpty()) return null
754760
return ChatSessionEntry(
755761
key = key,

apps/android/app/src/main/java/ai/openclaw/app/gateway/GatewayDiscovery.kt

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
package ai.openclaw.app.gateway
22

3-
import android.annotation.TargetApi
43
import android.content.Context
54
import android.net.ConnectivityManager
65
import android.net.DnsResolver
@@ -12,6 +11,7 @@ import android.net.nsd.NsdServiceInfo
1211
import android.os.Build
1312
import android.os.CancellationSignal
1413
import android.util.Log
14+
import androidx.annotation.RequiresApi
1515
import kotlinx.coroutines.CoroutineScope
1616
import kotlinx.coroutines.Dispatchers
1717
import kotlinx.coroutines.Job
@@ -56,7 +56,7 @@ private fun createDnsResolver(context: Context): DnsResolver =
5656
createLegacyDnsResolver()
5757
}
5858

59-
@TargetApi(Build.VERSION_CODES.CINNAMON_BUN)
59+
@RequiresApi(Build.VERSION_CODES.CINNAMON_BUN)
6060
private fun createContextDnsResolver(context: Context): DnsResolver = DnsResolver(context, null)
6161

6262
@Suppress("DEPRECATION")
@@ -189,7 +189,7 @@ class GatewayDiscovery(
189189
}
190190
}
191191

192-
@TargetApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
192+
@RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
193193
private fun resolveWithServiceInfoCallback(serviceInfo: NsdServiceInfo) {
194194
val serviceName = BonjourEscapes.decode(serviceInfo.serviceName)
195195
val id = stableId(serviceName, "local.")

apps/android/app/src/main/java/ai/openclaw/app/node/DeviceHandler.kt

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import ai.openclaw.app.BuildConfig
44
import ai.openclaw.app.SensitiveFeatureConfig
55
import ai.openclaw.app.gateway.GatewaySession
66
import android.Manifest
7+
import android.annotation.SuppressLint
78
import android.app.ActivityManager
89
import android.content.Context
910
import android.content.Intent
@@ -63,7 +64,7 @@ private class AndroidDeviceAppSource(
6364

6465
val appInfos =
6566
if (includeNonLaunchable) {
66-
packageManager.getInstalledApplications(PackageManager.MATCH_ALL)
67+
visibleInstalledApplications(packageManager)
6768
} else {
6869
launchablePackages.mapNotNull { packageName ->
6970
runCatching { packageManager.getApplicationInfo(packageName, 0) }.getOrNull()
@@ -90,6 +91,13 @@ private class AndroidDeviceAppSource(
9091
.sortedWith(compareBy<DeviceAppEntry> { it.label.lowercase() }.thenBy { it.packageName })
9192
.toList()
9293
}
94+
95+
@SuppressLint("QueryPermissionsNeeded")
96+
private fun visibleInstalledApplications(packageManager: PackageManager): List<ApplicationInfo> {
97+
// Android package visibility intentionally bounds this result to packages the app can see.
98+
// OpenClaw should not request QUERY_ALL_PACKAGES for this optional device-context surface.
99+
return packageManager.getInstalledApplications(PackageManager.MATCH_ALL)
100+
}
93101
}
94102

95103
private data class DeviceAppsRequest(

apps/android/app/src/main/java/ai/openclaw/app/ui/OnboardingFlow.kt

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1378,7 +1378,12 @@ private fun rememberPermissionState(
13781378
photosGranted = permissions[photosPermission] ?: photosGranted
13791379
contactsGranted = permissions[Manifest.permission.READ_CONTACTS] ?: contactsGranted
13801380
calendarGranted = permissions[Manifest.permission.READ_CALENDAR] ?: calendarGranted
1381-
notificationsGranted = permissions[Manifest.permission.POST_NOTIFICATIONS] ?: notificationsGranted
1381+
notificationsGranted =
1382+
if (Build.VERSION.SDK_INT >= 33) {
1383+
permissions[Manifest.permission.POST_NOTIFICATIONS] ?: notificationsGranted
1384+
} else {
1385+
true
1386+
}
13821387
motionGranted = permissions[Manifest.permission.ACTIVITY_RECOGNITION] ?: motionGranted
13831388
smsGranted =
13841389
(permissions[Manifest.permission.SEND_SMS] ?: smsGranted) &&

apps/android/app/src/main/java/ai/openclaw/app/ui/SessionsScreen.kt

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ import androidx.compose.ui.text.style.TextOverflow
5555
import androidx.compose.ui.unit.dp
5656
import androidx.compose.ui.unit.sp
5757

58-
/** Session browser for recent and currently-live chat sessions. */
58+
/** Session browser for recent and current chat sessions. */
5959
@Composable
6060
internal fun SessionsScreen(
6161
viewModel: MainViewModel,
@@ -73,7 +73,7 @@ internal fun SessionsScreen(
7373
.let { rows ->
7474
when (filter) {
7575
SessionFilter.Recent -> rows
76-
SessionFilter.Live -> rows.filter { it.key == chatSessionKey }
76+
SessionFilter.Current -> rows.filter { it.key == chatSessionKey }
7777
}
7878
}.let { rows ->
7979
if (recentFirst) {
@@ -92,12 +92,12 @@ internal fun SessionsScreen(
9292
}
9393

9494
ClawScaffold(
95-
contentPadding = PaddingValues(start = 20.dp, top = 14.dp, end = 20.dp, bottom = 6.dp),
95+
contentPadding = PaddingValues(start = 16.dp, top = 10.dp, end = 16.dp, bottom = 4.dp),
9696
contentWindowInsets = WindowInsets.safeDrawing.only(WindowInsetsSides.Top + WindowInsetsSides.Horizontal),
9797
) {
9898
LazyColumn(
9999
modifier = Modifier.fillMaxSize(),
100-
verticalArrangement = Arrangement.spacedBy(7.dp),
100+
verticalArrangement = Arrangement.spacedBy(9.dp),
101101
contentPadding = PaddingValues(bottom = 4.dp),
102102
) {
103103
item {
@@ -106,7 +106,7 @@ internal fun SessionsScreen(
106106
verticalAlignment = Alignment.CenterVertically,
107107
horizontalArrangement = Arrangement.spacedBy(8.dp),
108108
) {
109-
Text(text = "Sessions", style = ClawTheme.type.display.copy(fontSize = 17.4.sp, lineHeight = 21.sp), color = ClawTheme.colors.text, modifier = Modifier.weight(1f))
109+
Text(text = "Sessions", style = ClawTheme.type.display.copy(fontSize = 24.sp, lineHeight = 28.sp), color = ClawTheme.colors.text, modifier = Modifier.weight(1f))
110110
SessionPlainIconButton(icon = Icons.Default.Search, contentDescription = "Search sessions", onClick = onOpenCommand)
111111
SessionPlainIconButton(icon = Icons.Default.SwapVert, contentDescription = "Reverse session sort", onClick = { recentFirst = !recentFirst })
112112
}
@@ -115,7 +115,7 @@ internal fun SessionsScreen(
115115
item {
116116
Row(horizontalArrangement = Arrangement.spacedBy(5.dp)) {
117117
FilterPill(text = "Recent", icon = Icons.Outlined.AccessTime, active = filter == SessionFilter.Recent, onClick = { filter = SessionFilter.Recent })
118-
FilterPill(text = "Live", icon = Icons.Outlined.MicNone, active = filter == SessionFilter.Live, live = sessions.any { it.key == chatSessionKey }, onClick = { filter = SessionFilter.Live })
118+
FilterPill(text = "Current", icon = Icons.Outlined.MicNone, active = filter == SessionFilter.Current, showDot = sessions.any { it.key == chatSessionKey }, onClick = { filter = SessionFilter.Current })
119119
}
120120
}
121121

@@ -179,7 +179,7 @@ private fun FilterPill(
179179
text: String,
180180
icon: ImageVector? = null,
181181
active: Boolean = false,
182-
live: Boolean = false,
182+
showDot: Boolean = false,
183183
dropdown: Boolean = false,
184184
onClick: (() -> Unit)? = null,
185185
) {
@@ -198,7 +198,7 @@ private fun FilterPill(
198198
) {
199199
icon?.let { Icon(imageVector = it, contentDescription = null, modifier = Modifier.size(12.dp), tint = ClawTheme.colors.text) }
200200
Text(text = text, style = ClawTheme.type.label, color = ClawTheme.colors.text, maxLines = 1)
201-
if (live) {
201+
if (showDot) {
202202
Box(modifier = Modifier.size(4.dp).clip(CircleShape).background(ClawTheme.colors.success))
203203
}
204204
if (dropdown) {
@@ -258,7 +258,7 @@ private fun SessionRow(
258258
Text(text = subtitle, style = ClawTheme.type.caption.copy(fontSize = 12.5.sp, lineHeight = 16.sp), color = ClawTheme.colors.textMuted, maxLines = 1)
259259
Row(horizontalArrangement = Arrangement.spacedBy(4.dp)) {
260260
SessionMiniTag(text = "Workspace")
261-
SessionMiniTag(text = if (active) "Active" else "OpenClaw")
261+
SessionMiniTag(text = if (active) "Current" else "OpenClaw")
262262
}
263263
}
264264
}
@@ -320,21 +320,21 @@ private fun SessionMiniTag(text: String) {
320320

321321
private enum class SessionFilter {
322322
Recent,
323-
Live,
323+
Current,
324324
}
325325

326326
/** Empty-state title selected by the active session browser filter. */
327327
private fun emptySessionTitle(filter: SessionFilter): String =
328328
when (filter) {
329329
SessionFilter.Recent -> "No sessions yet"
330-
SessionFilter.Live -> "No live session"
330+
SessionFilter.Current -> "No current session"
331331
}
332332

333333
/** Empty-state body selected by the active session browser filter. */
334334
private fun emptySessionBody(filter: SessionFilter): String =
335335
when (filter) {
336336
SessionFilter.Recent -> "Start a new conversation and it will show up here."
337-
SessionFilter.Live -> "Open Chat to start or resume the current session."
337+
SessionFilter.Current -> "Open Chat to start or resume the current session."
338338
}
339339

340340
/** Formats session timestamps for compact mobile metadata. */

apps/android/app/src/main/java/ai/openclaw/app/ui/SettingsScreens.kt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,7 @@ internal enum class SettingsRoute {
106106
Profile,
107107
Voice,
108108
Agents,
109+
ProvidersModels,
109110
Approvals,
110111
CronJobs,
111112
Usage,
@@ -136,6 +137,7 @@ internal fun SettingsDetailScreen(
136137
SettingsRoute.Profile -> ProfileSettingsScreen(viewModel = viewModel, onBack = onBack)
137138
SettingsRoute.Voice -> VoiceSettingsScreen(viewModel = viewModel, onBack = onBack)
138139
SettingsRoute.Agents -> AgentsSettingsScreen(viewModel = viewModel, onBack = onBack)
140+
SettingsRoute.ProvidersModels -> ProvidersModelsScreen(viewModel = viewModel, onBack = onBack)
139141
SettingsRoute.Approvals -> ApprovalsSettingsScreen(viewModel = viewModel, onBack = onBack)
140142
SettingsRoute.CronJobs -> CronJobsSettingsScreen(viewModel = viewModel, onBack = onBack)
141143
SettingsRoute.Usage -> UsageSettingsScreen(viewModel = viewModel, onBack = onBack)

0 commit comments

Comments
 (0)