diff --git a/apps/android/app/src/main/AndroidManifest.xml b/apps/android/app/src/main/AndroidManifest.xml index 31a9fb6e1e7..801623083d2 100644 --- a/apps/android/app/src/main/AndroidManifest.xml +++ b/apps/android/app/src/main/AndroidManifest.xml @@ -80,6 +80,10 @@ + + + + diff --git a/apps/android/app/src/main/java/ai/openclaw/app/ui/SettingsSheet.kt b/apps/android/app/src/main/java/ai/openclaw/app/ui/SettingsSheet.kt index 7a503c1ff41..7b85b9bcb01 100644 --- a/apps/android/app/src/main/java/ai/openclaw/app/ui/SettingsSheet.kt +++ b/apps/android/app/src/main/java/ai/openclaw/app/ui/SettingsSheet.kt @@ -9,6 +9,7 @@ import android.hardware.SensorManager import android.net.Uri import android.os.Build import android.provider.Settings +import android.app.role.RoleManager import androidx.activity.compose.rememberLauncherForActivityResult import androidx.activity.result.contract.ActivityResultContracts import androidx.compose.foundation.background @@ -150,6 +151,8 @@ fun SettingsSheet(viewModel: MainViewModel) { versionName } } + var assistantRoleAvailable by remember(context) { mutableStateOf(isAssistantRoleAvailable(context)) } + var assistantRoleHeld by remember(context) { mutableStateOf(isAssistantRoleHeld(context)) } val listItemColors = ListItemDefaults.colors( containerColor = Color.Transparent, @@ -326,6 +329,12 @@ fun SettingsSheet(viewModel: MainViewModel) { viewModel.refreshGatewayConnection() } + val assistantRoleLauncher = + rememberLauncherForActivityResult(ActivityResultContracts.StartActivityForResult()) { + assistantRoleAvailable = isAssistantRoleAvailable(context) + assistantRoleHeld = isAssistantRoleHeld(context) + } + DisposableEffect(lifecycleOwner, context) { val observer = LifecycleEventObserver { _, event -> @@ -362,6 +371,8 @@ fun SettingsSheet(viewModel: MainViewModel) { || ContextCompat.checkSelfPermission(context, Manifest.permission.READ_SMS) == PackageManager.PERMISSION_GRANTED + assistantRoleAvailable = isAssistantRoleAvailable(context) + assistantRoleHeld = isAssistantRoleHeld(context) } } lifecycleOwner.lifecycle.addObserver(observer) @@ -478,6 +489,42 @@ fun SettingsSheet(viewModel: MainViewModel) { color = mobileTextTertiary, ) } + if (assistantRoleAvailable) { + HorizontalDivider(color = mobileBorder) + ListItem( + modifier = Modifier.fillMaxWidth(), + colors = listItemColors, + headlineContent = { Text("Default Assistant", style = mobileHeadline) }, + supportingContent = { + Text( + if (assistantRoleHeld) { + "OpenClaw is registered as the device assistant." + } else { + "Let Android launch OpenClaw from the assistant gesture. Google Assistant App Actions still work separately." + }, + style = mobileCallout, + ) + }, + trailingContent = { + Button( + onClick = { + assistantRoleLauncher.launch( + context + .getSystemService(RoleManager::class.java) + .createRequestRoleIntent(RoleManager.ROLE_ASSISTANT), + ) + }, + colors = settingsPrimaryButtonColors(), + shape = RoundedCornerShape(14.dp), + ) { + Text( + if (assistantRoleHeld) "Manage" else "Enable", + style = mobileCallout.copy(fontWeight = FontWeight.Bold), + ) + } + }, + ) + } } } @@ -1294,3 +1341,11 @@ private fun hasMotionCapabilities(context: Context): Boolean { return sensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER) != null || sensorManager.getDefaultSensor(Sensor.TYPE_STEP_COUNTER) != null } + +private fun isAssistantRoleAvailable(context: Context): Boolean { + return context.getSystemService(RoleManager::class.java).isRoleAvailable(RoleManager.ROLE_ASSISTANT) +} + +private fun isAssistantRoleHeld(context: Context): Boolean { + return context.getSystemService(RoleManager::class.java).isRoleHeld(RoleManager.ROLE_ASSISTANT) +}