


























@@ -8,6 +8,7 @@ import ai.openclaw.app.LocationMode
88import ai.openclaw.app.MainViewModel
99import ai.openclaw.app.NotificationPackageFilterMode
1010import ai.openclaw.app.chat.ChatPendingToolCall
11+import ai.openclaw.app.node.DeviceNotificationListenerService
1112import ai.openclaw.app.ui.design.ClawDetailRow
1213import ai.openclaw.app.ui.design.ClawIconBadge
1314import ai.openclaw.app.ui.design.ClawListPanel
@@ -21,8 +22,16 @@ import ai.openclaw.app.ui.design.ClawStatusPill
2122import ai.openclaw.app.ui.design.ClawTextBadge
2223import ai.openclaw.app.ui.design.ClawTextField
2324import ai.openclaw.app.ui.design.ClawTheme
25+import android.Manifest
26+import android.content.Context
27+import android.content.Intent
28+import android.content.pm.PackageManager
2429import android.media.AudioManager
2530import android.media.ToneGenerator
31+import android.os.Build
32+import android.provider.Settings
33+import androidx.activity.compose.rememberLauncherForActivityResult
34+import androidx.activity.result.contract.ActivityResultContracts
2635import androidx.compose.foundation.BorderStroke
2736import androidx.compose.foundation.background
2837import androidx.compose.foundation.layout.Arrangement
@@ -73,8 +82,10 @@ import androidx.compose.ui.Alignment
7382import androidx.compose.ui.Modifier
7483import androidx.compose.ui.graphics.Color
7584import androidx.compose.ui.graphics.vector.ImageVector
85+import androidx.compose.ui.platform.LocalContext
7686import androidx.compose.ui.text.style.TextOverflow
7787import androidx.compose.ui.unit.dp
88+import androidx.core.content.ContextCompat
78897990internal enum class V2SettingsRoute {
8091Home,
@@ -544,6 +555,7 @@ private fun V2NotificationSettingsScreen(
544555viewModel: MainViewModel,
545556onBack: () -> Unit,
546557) {
558+val context = LocalContext.current
547559val enabled by viewModel.notificationForwardingEnabled.collectAsState()
548560val mode by viewModel.notificationForwardingMode.collectAsState()
549561val packages by viewModel.notificationForwardingPackages.collectAsState()
@@ -552,12 +564,30 @@ private fun V2NotificationSettingsScreen(
552564val quietEnd by viewModel.notificationForwardingQuietEnd.collectAsState()
553565val maxEventsPerMinute by viewModel.notificationForwardingMaxEventsPerMinute.collectAsState()
554566val modeLabel = if (mode == NotificationPackageFilterMode.Blocklist) "Blocklist" else "Allowlist"
567+var listenerEnabled by remember { mutableStateOf(DeviceNotificationListenerService.isAccessEnabled(context)) }
568+val notificationPermissionLauncher =
569+ rememberLauncherForActivityResult(ActivityResultContracts.RequestPermission()) { granted ->
570+ viewModel.setNotificationForwardingEnabled(granted)
571+ }
572+573+fun setForwarding(checked: Boolean) {
574+if (!checked) {
575+ viewModel.setNotificationForwardingEnabled(false)
576+return
577+ }
578+if (Build.VERSION.SDK_INT >= 33 && !hasV2Permission(context, Manifest.permission.POST_NOTIFICATIONS)) {
579+ notificationPermissionLauncher.launch(Manifest.permission.POST_NOTIFICATIONS)
580+ } else {
581+ viewModel.setNotificationForwardingEnabled(true)
582+ }
583+ listenerEnabled = DeviceNotificationListenerService.isAccessEnabled(context)
584+ }
555585556586V2SettingsDetailFrame(title = "Notifications", subtitle = "Choose what reaches OpenClaw.", icon = Icons.Default.Notifications, onBack = onBack) {
557587V2SettingsTogglePanel(
558588 rows =
559589listOf(
560-V2SettingsToggleRow("Forward Notifications", if (enabled) "OpenClaw can receive selected alerts." else "Alerts stay on this phone.", Icons.Default.Notifications, enabled, viewModel::setNotificationForwardingEnabled),
590+V2SettingsToggleRow("Forward Notifications", if (enabled) "OpenClaw can receive selected alerts." else "Alerts stay on this phone.", Icons.Default.Notifications, enabled, ::setForwarding),
561591V2SettingsToggleRow("Quiet Hours", "$quietStart to $quietEnd", Icons.Default.Bolt, quietEnabled) { checked ->
562592 viewModel.setNotificationForwardingQuietHours(enabled = checked, start = quietStart, end = quietEnd)
563593 },
@@ -569,8 +599,19 @@ private fun V2NotificationSettingsScreen(
569599V2SettingsMetric("Policy", modeLabel),
570600V2SettingsMetric("Selected Apps", packages.size.toString()),
571601V2SettingsMetric("Rate Limit", "$maxEventsPerMinute/min"),
602+V2SettingsMetric("Access", if (listenerEnabled) "Granted" else "Setup"),
572603 ),
573604 )
605+Row(modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.spacedBy(8.dp)) {
606+ClawSecondaryButton(
607+ text = if (listenerEnabled) "Check Access" else "Open System Access",
608+ onClick = {
609+ openV2NotificationListenerSettings(context)
610+ listenerEnabled = DeviceNotificationListenerService.isAccessEnabled(context)
611+ },
612+ modifier = Modifier.weight(1f),
613+ )
614+ }
574615ClawPanel {
575616Column(verticalArrangement = Arrangement.spacedBy(8.dp)) {
576617Text(text = "Forwarding Mode", style = ClawTheme.type.section, color = ClawTheme.colors.text)
@@ -591,18 +632,66 @@ private fun V2PhoneCapabilitiesScreen(
591632viewModel: MainViewModel,
592633onBack: () -> Unit,
593634) {
635+val context = LocalContext.current
594636val cameraEnabled by viewModel.cameraEnabled.collectAsState()
595637val locationMode by viewModel.locationMode.collectAsState()
596638val locationPreciseEnabled by viewModel.locationPreciseEnabled.collectAsState()
597639val preventSleep by viewModel.preventSleep.collectAsState()
598640val canvasDebugStatusEnabled by viewModel.canvasDebugStatusEnabled.collectAsState()
641+val cameraPermissionLauncher =
642+ rememberLauncherForActivityResult(ActivityResultContracts.RequestPermission()) { granted ->
643+ viewModel.setCameraEnabled(granted)
644+ }
645+val locationPermissionLauncher =
646+ rememberLauncherForActivityResult(ActivityResultContracts.RequestMultiplePermissions()) { grants ->
647+val granted = grants[Manifest.permission.ACCESS_FINE_LOCATION] == true || grants[Manifest.permission.ACCESS_COARSE_LOCATION] == true
648+ viewModel.setLocationMode(if (granted) LocationMode.WhileUsing else LocationMode.Off)
649+ viewModel.setLocationPreciseEnabled(grants[Manifest.permission.ACCESS_FINE_LOCATION] == true)
650+ }
651+652+fun setCameraAccess(checked: Boolean) {
653+if (!checked) {
654+ viewModel.setCameraEnabled(false)
655+return
656+ }
657+if (hasV2Permission(context, Manifest.permission.CAMERA)) {
658+ viewModel.setCameraEnabled(true)
659+ } else {
660+ cameraPermissionLauncher.launch(Manifest.permission.CAMERA)
661+ }
662+ }
663+664+fun setLocationAccess(mode: LocationMode) {
665+if (mode == LocationMode.Off) {
666+ viewModel.setLocationMode(LocationMode.Off)
667+return
668+ }
669+if (hasV2LocationPermission(context)) {
670+ viewModel.setLocationMode(LocationMode.WhileUsing)
671+ } else {
672+ locationPermissionLauncher.launch(arrayOf(Manifest.permission.ACCESS_FINE_LOCATION, Manifest.permission.ACCESS_COARSE_LOCATION))
673+ }
674+ }
675+676+fun setPreciseLocation(checked: Boolean) {
677+if (!checked) {
678+ viewModel.setLocationPreciseEnabled(false)
679+return
680+ }
681+if (hasV2Permission(context, Manifest.permission.ACCESS_FINE_LOCATION)) {
682+ viewModel.setLocationPreciseEnabled(true)
683+ viewModel.setLocationMode(LocationMode.WhileUsing)
684+ } else {
685+ locationPermissionLauncher.launch(arrayOf(Manifest.permission.ACCESS_FINE_LOCATION, Manifest.permission.ACCESS_COARSE_LOCATION))
686+ }
687+ }
599688600689V2SettingsDetailFrame(title = "Phone Capabilities", subtitle = "Choose what this phone can share.", icon = Icons.AutoMirrored.Filled.ScreenShare, onBack = onBack) {
601690V2SettingsTogglePanel(
602691 rows =
603692listOf(
604-V2SettingsToggleRow("Camera", "Allow camera tools when requested.", Icons.Default.CameraAlt, cameraEnabled, viewModel::setCameraEnabled),
605-V2SettingsToggleRow("Precise Location", "Share precise location while location is enabled.", Icons.Default.LocationOn, locationPreciseEnabled, viewModel::setLocationPreciseEnabled),
693+V2SettingsToggleRow("Camera", "Allow camera tools when requested.", Icons.Default.CameraAlt, cameraEnabled, ::setCameraAccess),
694+V2SettingsToggleRow("Precise Location", "Share precise location while location is enabled.", Icons.Default.LocationOn, locationPreciseEnabled, ::setPreciseLocation),
606695V2SettingsToggleRow("Keep Awake", "Keep the node available during active work.", Icons.Default.Bolt, preventSleep, viewModel::setPreventSleep),
607696V2SettingsToggleRow("Canvas Status", "Show screen-sharing debug state.", Icons.AutoMirrored.Filled.ScreenShare, canvasDebugStatusEnabled, viewModel::setCanvasDebugStatusEnabled),
608697 ),
@@ -613,7 +702,7 @@ private fun V2PhoneCapabilitiesScreen(
613702ClawSegmentedControl(
614703 options = listOf("Off", "While Using"),
615704 selected = if (locationMode == LocationMode.WhileUsing) "While Using" else "Off",
616- onSelect = { selected -> viewModel.setLocationMode(if (selected == "While Using") LocationMode.WhileUsing else LocationMode.Off) },
705+ onSelect = { selected -> setLocationAccess(if (selected == "While Using") LocationMode.WhileUsing else LocationMode.Off) },
617706 )
618707 }
619708 }
@@ -1026,3 +1115,17 @@ private fun V2SettingsIconMark(icon: ImageVector) {
10261115 }
10271116 }
10281117}
1118+1119+private fun hasV2Permission(
1120+context: Context,
1121+permission: String,
1122+): Boolean = ContextCompat.checkSelfPermission(context, permission) == PackageManager.PERMISSION_GRANTED
1123+1124+private fun hasV2LocationPermission(context: Context): Boolean =
1125+ hasV2Permission(context, Manifest.permission.ACCESS_FINE_LOCATION) ||
1126+ hasV2Permission(context, Manifest.permission.ACCESS_COARSE_LOCATION)
1127+1128+private fun openV2NotificationListenerSettings(context: Context) {
1129+val intent = Intent(Settings.ACTION_NOTIFICATION_LISTENER_SETTINGS).addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
1130+ context.startActivity(intent)
1131+}
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。