Add five independent Android incoming call preferences

This commit is contained in:
evgeny committed 2026-10-19 15:20:25 +02:00
1 parent 97b537c005
commit 7d5399cd62
3 files changed
+42 -20

No files matched your search

@@ -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<Boolean> = 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<List<Boolean>> = 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")
@@ -25,6 +25,7 @@ object PhoneStateMonitor {
private val watches = mutableListOf<Watch>()
private val states = mutableMapOf<Int, Int>()
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)
}
}
@@ -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()) }