Contexto
Detectar las SIMs activas en un dispositivo Android es necesario si quieres permitir bloqueo independiente por slot (caso clasico: SIM personal sin bloqueo, SIM de trabajo con blocklist agresiva). La API base es SubscriptionManager.activeSubscriptionInfoList, disponible desde API 22 (Android 5.1).
La trampa: la API tecnicamente existe desde 22, pero los permisos requeridos cambiaron 2 veces despues. Asumir "una API publica" como universal es un crash garantizado.
Lo que aprendi
Hay 3 tiers de comportamiento segun version:
- API 22-30 (Android 5.1 - 11): solo
READ_PHONE_STATE - API 31-32 (Android 12): requiere ADEMAS
READ_PHONE_NUMBERSparagetActiveSubscriptionInfoList() - API 33+ (Android 13+): el numero de telefono ya no esta en
SubscriptionInfo.number(deprecado), usasubscriptionManager.getPhoneNumber(subId)que requiereREAD_PHONE_NUMBERS
fun getActiveSimCards(context: Context): List<SimConfig> {
if (ContextCompat.checkSelfPermission(
context, Manifest.permission.READ_PHONE_STATE
) != PackageManager.PERMISSION_GRANTED) {
return emptyList()
}
// Android 12+: tambien READ_PHONE_NUMBERS
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
if (ContextCompat.checkSelfPermission(
context, Manifest.permission.READ_PHONE_NUMBERS
) != PackageManager.PERMISSION_GRANTED) {
return emptyList()
}
}
val subscriptionManager = context.getSystemService(Context.TELEPHONY_SUBSCRIPTION_SERVICE)
as? SubscriptionManager ?: return emptyList()
return subscriptionManager.activeSubscriptionInfoList?.map { subInfo ->
SimConfig(
subscriptionId = subInfo.subscriptionId,
simSlot = subInfo.simSlotIndex,
carrierName = subInfo.carrierName?.toString() ?: "SIM ${subInfo.simSlotIndex + 1}",
phoneNumber = getPhoneNumber(context, subInfo.subscriptionId)
)
} ?: emptyList()
}
private fun getPhoneNumber(context: Context, subscriptionId: Int): String? {
val subManager = context.getSystemService(Context.TELEPHONY_SUBSCRIPTION_SERVICE)
as? SubscriptionManager ?: return null
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
// API 33+: API moderna
subManager.getPhoneNumber(subscriptionId).takeIf { it.isNotBlank() }
} else {
// API 22-32: legacy field con @Suppress("DEPRECATION")
@Suppress("DEPRECATION")
subManager.activeSubscriptionInfoList
?.find { it.subscriptionId == subscriptionId }
?.number
?.takeIf { it.isNotBlank() }
}
}
Por que importa
La API de telefonia en Android es el ejemplo canonico de "publica pero con quirks por version". Cualquier feature relacionado con SIM, IMEI, numero, MCC/MNC o subscription va a necesitar ramificacion por SDK.
Lecciones:
- No uses solo
try/catchpara manejar permisos faltantes.checkSelfPermissionantes evita logs de error y comportamientos inconsistentes. - Maneja el carrier name vacio (
carrierName?.toString() ?: "SIM ${slot + 1}"). Operadores chicos a veces no lo reportan. - El phone number puede ser null aunque tengas permisos. El operador puede no proveerlo. UI debe permitir editar manualmente.
@Suppress("DEPRECATION")es legitimo aqui, no es deuda tecnica: es soporte explicito de versiones viejas. Comenta el porque.
Bonus: en el call screening, usar subscription_id del CallLog (campo no documentado pero estable) permite saber que SIM recibio una llamada bloqueada despues del hecho. Util si el usuario quiere stats por SIM.