From 7d5399cd621745742832a908a0a9c4ee6ed543c4 Mon Sep 17 00:00:00 2001 From: evgeny Date: Mon, 19 Oct 2026 15:20:25 +0200 Subject: [PATCH] Add five independent Android incoming call preferences --- .../java/com/utun/chat/data/ConfigProvider.kt | 18 ++++++++--- .../com/utun/chat/data/PhoneStateMonitor.kt | 12 +++---- .../utun/chat/ui/screens/SettingsScreen.kt | 32 +++++++++++++------ 3 files changed, 42 insertions(+), 20 deletions(-) diff --git a/tools/chatgui-android/app/src/main/java/com/utun/chat/data/ConfigProvider.kt b/tools/chatgui-android/app/src/main/java/com/utun/chat/data/ConfigProvider.kt index 2b0c6210..bb675b92 100644 --- a/tools/chatgui-android/app/src/main/java/com/utun/chat/data/ConfigProvider.kt +++ b/tools/chatgui-android/app/src/main/java/com/utun/chat/data/ConfigProvider.kt @@ -85,12 +85,17 @@ class ConfigProvider(private val context: Context) { private val serviceEnabledKey = booleanPreferencesKey("service_enabled") private val startOnBootKey = booleanPreferencesKey("start_on_boot") private val soundEnabledKey = booleanPreferencesKey("sound_enabled") - private val phoneStatusEnabledKey = booleanPreferencesKey("phone_status_enabled") - val phoneStatusEnabled: Flow = context.dataStore.data.map { it[phoneStatusEnabledKey] ?: false }.distinctUntilChanged() + // Порядок совпадает с CALL_POLICY_* ядра: показ, предупреждение, отказ при занятости, запрет звонков, запрет рации. + private val incomingCallKeys = listOf("phone_status_enabled", "phone_notify_enabled", "phone_reject_enabled", + "block_incoming_calls", "block_incoming_radio").map(::booleanPreferencesKey) + val incomingCallOptions: Flow> = context.dataStore.data.map { prefs -> + incomingCallKeys.map { prefs[it] ?: false } + }.distinctUntilChanged() - suspend fun setPhoneStatusEnabled(enabled: Boolean) { - context.dataStore.edit { it[phoneStatusEnabledKey] = enabled } - LogManager.addLog("INFO", "PhoneState", "Phone status monitoring enabled=$enabled") + suspend fun setIncomingCallOption(option: Int, enabled: Boolean) { + require(option in incomingCallKeys.indices) + context.dataStore.edit { it[incomingCallKeys[option]] = enabled } + LogManager.addLog("INFO", "PhoneState", "${incomingCallKeys[option].name}=$enabled") } private val mutedChannelsKey = stringSetPreferencesKey("muted_channels") private val callAudioRouteKey = stringPreferencesKey("call_audio_route") @@ -706,6 +711,9 @@ class ConfigProvider(private val context: Context) { appendLine("enabled=no") appendLine() appendLine("[chat]") + val incoming = runBlocking { incomingCallOptions.first() } + val policy = incoming.withIndex().fold(0) { mask, (index, value) -> mask or if (value) (1 shl index) else 0 } + appendLine("incoming_call_policy=$policy") val chatAutoload = getInt("chat.storage_autoload") val chatAutoloadMaxMb = getInt("chat.storage_autoload_maxsize_mb") val chatMaxGb = getInt("chat.storage_maxsize_gb") diff --git a/tools/chatgui-android/app/src/main/java/com/utun/chat/data/PhoneStateMonitor.kt b/tools/chatgui-android/app/src/main/java/com/utun/chat/data/PhoneStateMonitor.kt index 12dfa2b4..f0072d80 100644 --- a/tools/chatgui-android/app/src/main/java/com/utun/chat/data/PhoneStateMonitor.kt +++ b/tools/chatgui-android/app/src/main/java/com/utun/chat/data/PhoneStateMonitor.kt @@ -25,6 +25,7 @@ object PhoneStateMonitor { private val watches = mutableListOf() private val states = mutableMapOf() private var enabled = false + private var policy = 0 private var generation = 0 private var lastState = -1 private var started = false @@ -46,12 +47,10 @@ object PhoneStateMonitor { if (started) return started = true val app = ChatApplication.instance - app.packageManager.addOnPermissionsChangeListener { uid -> - if (uid == app.applicationInfo.uid) main.post { refresh() } - } scope.launch { - app.configProvider.phoneStatusEnabled.collect { value -> - enabled = value + app.configProvider.incomingCallOptions.collect { options -> + policy = options.withIndex().fold(0) { mask, (index, value) -> mask or if (value) (1 shl index) else 0 } + enabled = policy and 7 != 0 refresh() } } @@ -110,6 +109,7 @@ object PhoneStateMonitor { private fun changed(token: Int, id: Int, state: Int) { if (token != generation || !enabled || !states.containsKey(id)) return + if (!granted()) { refresh(); return } states[id] = state publishAggregate() } @@ -122,6 +122,6 @@ object PhoneStateMonitor { private fun publish(state: Int) { if (lastState != state) LogManager.addLog("INFO", "PhoneState", "State=$state enabled=$enabled subscriptions=${states.size}") lastState = state - if (NativeLib.isReady()) NativeLib.setPhoneState(state) + if (NativeLib.isReady()) NativeLib.setPhoneState(state, policy) } } diff --git a/tools/chatgui-android/app/src/main/java/com/utun/chat/ui/screens/SettingsScreen.kt b/tools/chatgui-android/app/src/main/java/com/utun/chat/ui/screens/SettingsScreen.kt index e7bf1a98..17bf9cfd 100644 --- a/tools/chatgui-android/app/src/main/java/com/utun/chat/ui/screens/SettingsScreen.kt +++ b/tools/chatgui-android/app/src/main/java/com/utun/chat/ui/screens/SettingsScreen.kt @@ -1046,26 +1046,40 @@ private fun AudioSettingsTab(vm: ChatViewModel) { /* ── Входящие звонки поверх блокировки ── */ Text(stringResource(R.string.incoming_calls), style = MaterialTheme.typography.titleMedium) - val phoneStatusEnabled by provider.phoneStatusEnabled.collectAsState(initial = false) + val phoneOptions by provider.incomingCallOptions.collectAsState(initial = List(5) { false }) + var requestedPhoneOption by rememberSaveable { mutableStateOf(0) } var phonePermission by remember { mutableStateOf(PhoneStateMonitor.granted()) } val phonePermissionLauncher = rememberLauncherForActivityResult(ActivityResultContracts.RequestPermission()) { granted -> phonePermission = granted LogManager.addLog(if (granted) "INFO" else "WARN", "PhoneState", "Phone permission result granted=$granted") - scope.launch { provider.setPhoneStatusEnabled(granted) } + scope.launch { + provider.setIncomingCallOption(requestedPhoneOption, granted) + } } LifecycleResumeEffect(Unit) { phonePermission = PhoneStateMonitor.granted() onPauseOrDispose { } } - Row(verticalAlignment = Alignment.CenterVertically) { + val phoneTitles = listOf(R.string.phone_status_setting, R.string.phone_notify_setting, R.string.phone_reject_setting, + R.string.block_incoming_calls, R.string.block_incoming_radio) + val phoneDescriptions = listOf(R.string.phone_status_description, R.string.phone_notify_description, + R.string.phone_reject_description, R.string.block_incoming_calls_description, R.string.block_incoming_radio_description) + for (option in phoneTitles.indices) Row(verticalAlignment = Alignment.CenterVertically) { + val selected = phoneOptions[option] + val needsPermission = option < 3 Column(Modifier.weight(1f)) { - Text(stringResource(R.string.phone_status_setting)) - Text(stringResource(if (phoneStatusEnabled && !phonePermission) R.string.phone_status_permission_missing - else R.string.phone_status_description), style = MaterialTheme.typography.bodySmall) + Text(stringResource(phoneTitles[option])) + Text(stringResource(if (selected && needsPermission && !phonePermission) R.string.phone_status_permission_missing + else phoneDescriptions[option]), + style = MaterialTheme.typography.bodySmall) } - Switch(checked = phoneStatusEnabled && phonePermission, onCheckedChange = { value -> - if (value && !PhoneStateMonitor.granted()) phonePermissionLauncher.launch(Manifest.permission.READ_PHONE_STATE) - else scope.launch { provider.setPhoneStatusEnabled(value) } + Switch(checked = selected, onCheckedChange = { value -> + if (value && needsPermission && !PhoneStateMonitor.granted()) { + requestedPhoneOption = option + phonePermissionLauncher.launch(Manifest.permission.READ_PHONE_STATE) + } else scope.launch { + provider.setIncomingCallOption(option, value) + } }) } var fsiEnabled by remember { mutableStateOf(IncomingCallManager.canFullScreenIntent()) }