Skip to content

Commit 8eb72af

Browse files
committed
feat(screening): ring India's 1600 and 1601 series through by default
TRAI's 2026 amendment (regulation 34A of the Customer Preference regulations, 05 of 2026) stops call apps tagging, blocking or filtering calls from the series it designates for service and government callers. The allow rule stays below a person's own blocks, which the regulation's proviso leaves alone, and each series can be turned off under Advanced. The default follows the phone's home region, so a phone outside India keeps the old behavior. A preference nobody has set stays unwritten, and a backup records only an explicit off, so restoring on another phone can't pin a default that belongs to a different SIM.
1 parent 8dd89b7 commit 8eb72af

16 files changed

Lines changed: 139 additions & 19 deletions

File tree

‎README.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -144,7 +144,7 @@ All detection layers implement a shared `IChecker` interface and run in priority
144144
| 5320 | **Prefix Rules** | Block | Downloaded wangiri country codes, US premium rate (+1900), international premium |
145145
| 5310 | **Regulatory Prefix** | Block | Opt-in (Settings > Telemarketing ranges) blocks for ranges regulators set aside for sales calls: Spain 400 (from 17 October 2026), India 140 (TRAI), Brazil 0303 (ANATEL). Matches the number with its country code, and without it on a phone from that country |
146146
| 5300 | **STIR/SHAKEN Authenticated** | Allow | Carrier-authenticated caller ID allows through heuristic/ML suspicion, and through a database match only when that row's newest evidence, community reports included, is over a year old and the number isn't trending right now. Explicit blocks still win first |
147-
| 5250 | **Regulatory Allow** | Allow | Opt-in protected series that rings through past the database and statistics. India 1600 (banks, insurers and government offices, per TRAI) |
147+
| 5250 | **Regulatory Allow** | Allow | Protected series that rings through past the database and statistics. India 1600 (banks, insurers and government offices) and 1601 (utilities, logistics and courier companies), per TRAI. On by default with an Indian SIM, and you can turn each one off |
148148
| 5200 | **Spam Database** | Block | 51,407 imported spam numbers plus the trending-numbers feed |
149149
| 5150 | **Database Prefix Expansion** | Block | Auto-blocks last-two-digit siblings of confirmed database entries |
150150
| 5000 | **Recently Dialed** | Allow | Numbers you called in the last 24h. They're probably calling back |

‎app/src/main/java/com/sysadmindoc/callshield/data/BackupRestore.kt‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -208,6 +208,8 @@ object BackupRestore {
208208
val notificationScreeningPackages: List<String>? = null,
209209
/** Preference key names of the [RegulatoryPrefix] rules that are on. */
210210
val enabledRegulatoryPrefixes: List<String> = emptyList(),
211+
/** Key names of the on-by-default rules turned off; one in neither list follows the phone's region. */
212+
val disabledRegulatoryPrefixes: List<String> = emptyList(),
211213
val meetingModeEnabled: Boolean = false,
212214
val meetingModeApps: List<String> = emptyList(),
213215
// Null in a backup made before these were saved, which leaves the

‎app/src/main/java/com/sysadmindoc/callshield/data/BackupSettingsPreferences.kt‎

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,11 @@ internal fun Preferences.toBackupSettings(): BackupSettings =
119119
.filter { this[it.key] == true }
120120
.map { it.key.name }
121121
.sorted(),
122+
disabledRegulatoryPrefixes =
123+
RegulatoryPrefix.entries
124+
.filter { it.onByDefaultAtHome && this[it.key] == false }
125+
.map { it.key.name }
126+
.sorted(),
122127
meetingModeEnabled = this[SpamRepository.KEY_MEETING_MODE] ?: false,
123128
meetingModeApps = this[SpamRepository.KEY_MEETING_MODE_APPS].orEmpty().sorted(),
124129
outgoingCallHoldEnabled = this[SpamRepository.KEY_OUTGOING_CALL_HOLD] ?: false,
@@ -209,6 +214,12 @@ internal fun BackupSettings.sanitized(): BackupSettings =
209214
.filter { name -> RegulatoryPrefix.entries.any { it.key.name == name } }
210215
.distinct()
211216
.sorted(),
217+
disabledRegulatoryPrefixes =
218+
disabledRegulatoryPrefixes
219+
.map { it.trim() }
220+
.filter { name -> RegulatoryPrefix.entries.any { it.key.name == name && it.onByDefaultAtHome } }
221+
.distinct()
222+
.sorted(),
212223
meetingModeApps =
213224
meetingModeApps
214225
.map { it.trim() }
@@ -273,7 +284,13 @@ internal fun BackupSettings.writeTo(preferences: MutablePreferences) {
273284
preferences[SpamRepository.KEY_ALLOWED_REGIONS] = allowedRegions.toSet()
274285
}
275286
preferences[SpamRepository.KEY_REGION_BLOCK] = regionBlockEnabled && allowedRegions.isNotEmpty()
276-
RegulatoryPrefix.entries.forEach { preferences[it.key] = it.key.name in enabledRegulatoryPrefixes }
287+
RegulatoryPrefix.entries.forEach {
288+
when {
289+
it.key.name in enabledRegulatoryPrefixes -> preferences[it.key] = true
290+
!it.onByDefaultAtHome || it.key.name in disabledRegulatoryPrefixes -> preferences[it.key] = false
291+
else -> preferences.remove(it.key)
292+
}
293+
}
277294
preferences[SpamRepository.KEY_MEETING_MODE] = meetingModeEnabled
278295
if (meetingModeApps.isEmpty()) {
279296
preferences.remove(SpamRepository.KEY_MEETING_MODE_APPS)

‎app/src/main/java/com/sysadmindoc/callshield/data/RegulatoryPrefix.kt‎

Lines changed: 27 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,11 @@ import androidx.datastore.preferences.core.Preferences
44
import com.sysadmindoc.callshield.R
55

66
/**
7-
* A number series a telecom regulator set aside, each behind its own opt-in
8-
* setting. A range either blocks (sales-call ranges) or rings through (a
9-
* protected series).
7+
* A number series a telecom regulator set aside, each behind its own setting.
8+
* A range either blocks (sales-call ranges) or rings through (a protected
9+
* series). Each is opt-in, except that a series the home country's regulator
10+
* bars call apps from flagging starts on for a phone in that country, and a
11+
* person can still turn it off.
1012
*
1113
* Matched in E.164 form and in the national form a caller ID keeps when
1214
* Android can't put it in E.164, which happens for a range newer than the
@@ -28,6 +30,8 @@ enum class RegulatoryPrefix(
2830
val descriptionRes: Int,
2931
val titleRes: Int,
3032
val summaryRes: Int,
33+
/** On for a phone whose home region is [regionIso] until the person sets it. */
34+
val onByDefaultAtHome: Boolean = false,
3135
) {
3236
SPAIN_400(
3337
key = SpamRepository.KEY_REG_SPAIN_400,
@@ -72,9 +76,28 @@ enum class RegulatoryPrefix(
7276
descriptionRes = R.string.reg_prefix_india_1600_allow_match,
7377
titleRes = R.string.reg_prefix_india_1600_allow,
7478
summaryRes = R.string.reg_prefix_india_1600_allow_desc,
79+
onByDefaultAtHome = true,
80+
),
81+
INDIA_1601(
82+
key = SpamRepository.KEY_REG_INDIA_1601_ALLOW,
83+
allows = true,
84+
regionIso = "IN",
85+
e164Prefix = "+911601",
86+
nationalPrefix = "1601",
87+
nationalLength = 10,
88+
descriptionRes = R.string.reg_prefix_india_1601_allow_match,
89+
titleRes = R.string.reg_prefix_india_1601_allow,
90+
summaryRes = R.string.reg_prefix_india_1601_allow_desc,
91+
onByDefaultAtHome = true,
7592
),
7693
;
7794

95+
/** The person's own setting, or else the range's default for this phone. */
96+
fun isOn(
97+
prefs: Preferences,
98+
homeRegionIso: () -> String?,
99+
): Boolean = prefs[key] ?: (onByDefaultAtHome && homeRegionIso() == regionIso)
100+
78101
/** [homeRegionIso] is read only for a number in national form. */
79102
fun matches(
80103
number: String,
@@ -97,7 +120,7 @@ enum class RegulatoryPrefix(
97120
homeRegionIso: () -> String?,
98121
): RegulatoryPrefix? =
99122
entries.firstOrNull {
100-
it.allows == allows && prefs[it.key] == true && it.matches(number, homeRegionIso)
123+
it.allows == allows && it.isOn(prefs, homeRegionIso) && it.matches(number, homeRegionIso)
101124
}
102125
}
103126
}

‎app/src/main/java/com/sysadmindoc/callshield/data/SpamRepository.kt‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,7 @@ class SpamRepository(
109109
dataStore = settingsDataStore ?: appContext.dataStore,
110110
privateDataStore =
111111
privateSettingsDataStore ?: appContext.noBackupDataStore("callshield_private_prefs"),
112+
homeRegionIso = { canonicalizer().homeRegionIso },
112113
)
113114
private val spamRepositoryImpl =
114115
SpamRepositoryImpl(
@@ -224,6 +225,7 @@ class SpamRepository(
224225
val KEY_REG_INDIA_140 = booleanPreferencesKey("reg_india_140_enabled")
225226
val KEY_REG_BRAZIL_0303 = booleanPreferencesKey("reg_brazil_0303_enabled")
226227
val KEY_REG_INDIA_1600_ALLOW = booleanPreferencesKey("reg_india_1600_allow_enabled")
228+
val KEY_REG_INDIA_1601_ALLOW = booleanPreferencesKey("reg_india_1601_allow_enabled")
227229

228230
// Meeting mode: silence non-contacts while a picked meeting app holds an ongoing notification.
229231
val KEY_MEETING_MODE = booleanPreferencesKey("meeting_mode_enabled")

‎app/src/main/java/com/sysadmindoc/callshield/data/checker/Checkers.kt‎

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -533,22 +533,24 @@ internal class RegulatoryPrefixChecker(
533533
override val priority = CheckerPriority.REGULATORY_PREFIX
534534
override val name = "regulatory_prefix"
535535

536-
override suspend fun isEnabled(ctx: CheckContext): Boolean = RegulatoryPrefix.entries.any { !it.allows && ctx.prefs[it.key] == true }
536+
override suspend fun isEnabled(ctx: CheckContext): Boolean =
537+
RegulatoryPrefix.entries.any { !it.allows && it.isOn(ctx.prefs) { homeRegion(ctx.appContext) } }
537538

538539
override suspend fun check(ctx: CheckContext): BlockResult? =
539540
RegulatoryPrefix.enabledMatch(ctx.prefs, ctx.number, allows = false) { homeRegion(ctx.appContext) }?.let { range ->
540541
BlockResult.block(name, "telemarketer", ctx.appContext.getString(range.descriptionRes))
541542
}
542543
}
543544

544-
/** Opt-in allow for a protected series ([RegulatoryPrefix]), above the downloaded data and statistics. */
545+
/** Allow for a protected series ([RegulatoryPrefix]), above the downloaded data and statistics. */
545546
internal class RegulatoryAllowChecker(
546547
private val homeRegion: (Context) -> String? = phoneHomeRegion,
547548
) : IChecker {
548549
override val priority = CheckerPriority.REGULATORY_ALLOW
549550
override val name = "regulatory_allow"
550551

551-
override suspend fun isEnabled(ctx: CheckContext): Boolean = RegulatoryPrefix.entries.any { it.allows && ctx.prefs[it.key] == true }
552+
override suspend fun isEnabled(ctx: CheckContext): Boolean =
553+
RegulatoryPrefix.entries.any { it.allows && it.isOn(ctx.prefs) { homeRegion(ctx.appContext) } }
552554

553555
override suspend fun check(ctx: CheckContext): BlockResult? =
554556
RegulatoryPrefix.enabledMatch(ctx.prefs, ctx.number, allows = true) { homeRegion(ctx.appContext) }?.let { series ->

‎app/src/main/java/com/sysadmindoc/callshield/data/checker/IChecker.kt‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -297,7 +297,7 @@ object CheckerPriority {
297297
const val PREFIX_MATCH = 5_320 // downloaded prefix reputation rows (country/NPA ranges)
298298
const val REGULATORY_PREFIX = 5_310 // opt-in country telemarketing prefix blocks
299299
const val STIR_SHAKEN_TRUSTED = 5_300
300-
const val REGULATORY_ALLOW = 5_250 // protected series (India 1600)
300+
const val REGULATORY_ALLOW = 5_250 // protected series (India 1600 and 1601)
301301
const val GITHUB_DATABASE = 5_200 // downloaded exact reputation rows
302302
const val DB_PREFIX_EXPANSION = 5_150 // auto-block last-2-digit siblings of DB entries
303303
const val RECENTLY_DIALED = 5_000 // user just called this number

‎app/src/main/java/com/sysadmindoc/callshield/data/repository/SettingsRepository.kt‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,7 @@ internal fun feedTrustFailureFor(
8484
class SettingsRepository(
8585
private val dataStore: DataStore<Preferences>,
8686
private val privateDataStore: DataStore<Preferences>,
87+
private val homeRegionIso: () -> String? = { null },
8788
) {
8889
private companion object {
8990
const val DEFAULT_TIME_BLOCK_START = 22
@@ -162,7 +163,7 @@ class SettingsRepository(
162163
val regionBlockEnabled: Flow<Boolean> =
163164
dataStore.data.map { it[SpamRepository.KEY_REGION_BLOCK] ?: false }
164165
val enabledRegulatoryPrefixes: Flow<Set<RegulatoryPrefix>> =
165-
dataStore.data.map { prefs -> RegulatoryPrefix.entries.filterTo(mutableSetOf()) { prefs[it.key] == true } }
166+
dataStore.data.map { prefs -> RegulatoryPrefix.entries.filterTo(mutableSetOf()) { it.isOn(prefs, homeRegionIso) } }
166167
val meetingModeEnabled: Flow<Boolean> = dataStore.data.map { it[SpamRepository.KEY_MEETING_MODE] ?: false }
167168
val expectingCallUntil: Flow<Long> = dataStore.data.map { it[SpamRepository.KEY_EXPECTING_CALL_UNTIL] ?: 0L }
168169
val meetingModeApps: Flow<Set<String>> =

‎app/src/main/res/values-es/strings.xml‎

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1769,11 +1769,14 @@
17691769
<string name="reg_prefix_brazil_0303">Bloquear telemarketing 0303 de Brasil</string>
17701770
<string name="reg_prefix_brazil_0303_desc">Los telemarketers de Brasil llaman desde el código 0303 que ANATEL reservó para ellos.</string>
17711771
<string name="reg_prefix_india_1600_allow">Proteger la serie 1600 de India</string>
1772-
<string name="reg_prefix_india_1600_allow_desc">La serie 1600 de India pertenece a bancos, aseguradoras y oficinas de gobierno, así que sus llamadas suenan sin pasar por la base de datos de spam.</string>
1772+
<string name="reg_prefix_india_1600_allow_desc">La serie 1600 de India pertenece a bancos, aseguradoras y oficinas de gobierno, así que sus llamadas suenan sin pasar por la base de datos de spam. Activado de forma predeterminada con una SIM de India.</string>
1773+
<string name="reg_prefix_india_1601_allow">Proteger la serie 1601 de India</string>
1774+
<string name="reg_prefix_india_1601_allow_desc">La serie 1601 de India pertenece a empresas de servicios públicos, logística y mensajería, así que sus llamadas suenan sin pasar por la base de datos de spam. Activado de forma predeterminada con una SIM de India.</string>
17731775
<string name="reg_prefix_spain_400_match">Rango de llamadas comerciales 400 de España</string>
17741776
<string name="reg_prefix_india_140_match">Serie de llamadas promocionales 140 de India</string>
17751777
<string name="reg_prefix_brazil_0303_match">Código de telemarketing 0303 de Brasil</string>
17761778
<string name="reg_prefix_india_1600_allow_match">Serie 1600 de India para bancos, aseguradoras y oficinas de gobierno</string>
1779+
<string name="reg_prefix_india_1601_allow_match">Serie 1601 de India para servicios públicos, logística y mensajería</string>
17771780
<string name="region_rules_title">Reglas de región y nombre de quien llama</string>
17781781
<string name="region_rules_disclosure">Estas reglas se ejecutan por completo en tu dispositivo. El bloqueo por región lee el código de área de un número norteamericano y el código de país de cualquier otro. La confianza por nombre usa solo un nombre presentado por la operadora que Android entrega al servicio de filtrado.</string>
17791782
<string name="region_rules_block_outside">Bloquear llamadas fuera de las regiones permitidas</string>
@@ -1858,7 +1861,7 @@
18581861
<string name="reasoning_temporary_allow_headline">Permitiste este número por un tiempo.</string>
18591862
<string name="reasoning_temporary_allow_detail">Suena hasta que termine el permiso temporal.</string>
18601863
<string name="reasoning_regulatory_allow_headline">Este número está en una serie que protege el regulador.</string>
1861-
<string name="reasoning_regulatory_allow_detail">Bancos, aseguradoras y oficinas de gobierno llaman desde ahí, así que suena sin pasar por la base de datos de spam, la heurística, las horas de silencio ni las reglas de región.</string>
1864+
<string name="reasoning_regulatory_allow_detail">Está reservada para llamadas de servicio y de gobierno, así que suena sin pasar por la base de datos de spam, la heurística, las horas de silencio ni las reglas de región.</string>
18621865
<string name="reasoning_recently_dialed_headline">Llamaste a este número hace poco, así que la devolución de llamada sonó.</string>
18631866
<string name="reasoning_recently_dialed_detail">Cualquier número al que llamaste en las últimas 24 horas pasa las reglas de región, las horas de silencio y la heurística de spam.</string>
18641867
<string name="reasoning_emergency_callback_headline">El margen para devoluciones de llamada de emergencia está activo.</string>

‎app/src/main/res/values-pt-rBR/strings.xml‎

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1769,11 +1769,14 @@
17691769
<string name="reg_prefix_brazil_0303">Bloquear telemarketing 0303 do Brasil</string>
17701770
<string name="reg_prefix_brazil_0303_desc">As empresas de telemarketing no Brasil ligam do código 0303 que a ANATEL reservou para elas.</string>
17711771
<string name="reg_prefix_india_1600_allow">Proteger a série 1600 da Índia</string>
1772-
<string name="reg_prefix_india_1600_allow_desc">A série 1600 da Índia pertence a bancos, seguradoras e órgãos do governo, então as chamadas dela tocam mesmo que estejam no banco de dados de spam.</string>
1772+
<string name="reg_prefix_india_1600_allow_desc">A série 1600 da Índia pertence a bancos, seguradoras e órgãos do governo, então as chamadas dela tocam mesmo que estejam no banco de dados de spam. Ligado por padrão com um chip da Índia.</string>
1773+
<string name="reg_prefix_india_1601_allow">Proteger a série 1601 da Índia</string>
1774+
<string name="reg_prefix_india_1601_allow_desc">A série 1601 da Índia pertence a empresas de serviços públicos, logística e entregas, então as chamadas dela tocam mesmo que estejam no banco de dados de spam. Ligado por padrão com um chip da Índia.</string>
17731775
<string name="reg_prefix_spain_400_match">Faixa de chamadas comerciais 400 da Espanha</string>
17741776
<string name="reg_prefix_india_140_match">Série de chamadas promocionais 140 da Índia</string>
17751777
<string name="reg_prefix_brazil_0303_match">Código de telemarketing 0303 do Brasil</string>
17761778
<string name="reg_prefix_india_1600_allow_match">Série 1600 da Índia para bancos, seguradoras e órgãos do governo</string>
1779+
<string name="reg_prefix_india_1601_allow_match">Série 1601 da Índia para serviços públicos, logística e entregas</string>
17771780
<string name="region_rules_title">Regras de região e nome do chamador</string>
17781781
<string name="region_rules_disclosure">Estas regras funcionam inteiramente no seu aparelho. O bloqueio por região lê o DDD de um número norte-americano e o código do país de qualquer outro. A confiança no nome do chamador usa apenas um nome apresentado pela operadora que o Android repassa ao serviço de filtragem.</string>
17791782
<string name="region_rules_block_outside">Bloquear chamadas fora das regiões permitidas</string>
@@ -1858,7 +1861,7 @@
18581861
<string name="reasoning_temporary_allow_headline">Você permitiu este número por um tempo.</string>
18591862
<string name="reasoning_temporary_allow_detail">Ele toca até a permissão temporária acabar.</string>
18601863
<string name="reasoning_regulatory_allow_headline">Este número está em uma série protegida pelo órgão regulador.</string>
1861-
<string name="reasoning_regulatory_allow_detail">Bancos, seguradoras e órgãos do governo ligam dela, então toca mesmo que esteja no banco de dados de spam, nas heurísticas, no horário silencioso e nas regras de região.</string>
1864+
<string name="reasoning_regulatory_allow_detail">Ela é reservada para chamadas de serviço e de governo, então toca mesmo que esteja no banco de dados de spam, nas heurísticas, no horário silencioso e nas regras de região.</string>
18621865
<string name="reasoning_recently_dialed_headline">Você ligou para este número recentemente, então o retorno tocou.</string>
18631866
<string name="reasoning_recently_dialed_detail">Qualquer número para o qual você ligou nas últimas 24 horas passa pelas regras de região, pelo horário silencioso e pelas heurísticas de spam.</string>
18641867
<string name="reasoning_emergency_callback_headline">A tolerância para retorno de emergência está ativa.</string>

0 commit comments

Comments
 (0)