Cómo obtener el Android ID o IMEI con Kotlin: un análisis profundo de la identificación de dispositivos

En el vertiginoso mundo del desarrollo de aplicaciones móviles, la identificación única de un dispositivo ha sido, durante mucho tiempo, una pieza angular para una multitud de funcionalidades. Desde la personalización de la experiencia del usuario y el seguimiento de conversiones, hasta la prevención del fraude y la gestión de licencias, la capacidad de reconocer un dispositivo ha demostrado ser valiosa. Sin embargo, lo que antes era una práctica relativamente sencilla, hoy se ha convertido en un campo minado de consideraciones de privacidad, cambios en las políticas de la plataforma y evoluciones constantes de las APIs.

Como desarrolladores, nos encontramos en una encrucijada: la necesidad de identificar dispositivos para ciertas lógicas de negocio choca directamente con la creciente demanda de los usuarios por proteger su privacidad y el control sobre sus datos. Android, en particular, ha implementado una serie de cambios significativos en las últimas versiones para limitar el acceso a identificadores persistentes y únicos, como el IMEI, empujando a los desarrolladores a buscar alternativas más respetuosas con la privacidad. Este post explorará en detalle cómo se puede, o se podía, obtener el Android ID y el IMEI utilizando Kotlin, desglosando las implicaciones, restricciones y, lo que es más importante, sugiriendo las mejores prácticas actuales.

El Android ID: un identificador persistente, pero no absoluto

person holding black iphone 5

El Android ID es uno de los identificadores de dispositivo más utilizados y, a primera vista, parece una solución sencilla. Sin embargo, su comportamiento ha evolucionado considerablemente con cada nueva versión de Android, lo que requiere una comprensión clara de sus particularidades para evitar malentendidos o implementaciones erróneas.

¿Qué es el Android ID?

El Android ID es un número hexadecimal de 64 bits que se genera cuando el dispositivo arranca por primera vez y permanece constante durante la vida útil del mismo, a menos que se realice un restablecimiento de fábrica. En versiones de Android anteriores a 8.0 (Oreo), este identificador era relativamente estable y el mismo para todas las aplicaciones instaladas en un dispositivo. Esto lo hacía atractivo para el seguimiento de instalaciones y la vinculación de datos de usuario con el dispositivo.

Sin embargo, a partir de Android 8.0 (API nivel 26), el Android ID se volvió específico para cada aplicación (por usuario y por dispositivo). Esto significa que cada aplicación en un mismo dispositivo recibirá un Android ID diferente. Además, si el usuario desinstala y reinstala la aplicación, o si la aplicación se ejecuta en diferentes perfiles de usuario, el Android ID puede cambiar. Esta evolución fue un paso significativo hacia la protección de la privacidad del usuario, impidiendo que una aplicación rastree a un usuario a través de múltiples aplicaciones o reinstalaciones sin su consentimiento explícito.

Cómo obtener el Android ID en Kotlin

Obtener el Android ID es bastante sencillo utilizando la clase Settings.Secure. A diferencia del IMEI, no requiere permisos de tiempo de ejecución específicos en el manifiesto para la mayoría de los casos de uso (aunque es importante siempre revisar la documentación oficial para la versión de Android a la que se apunta).

Aquí hay un ejemplo de cómo obtenerlo:

import android.content.Context
import android.provider.Settings

fun getAndroidId(context: Context): String? {
    return Settings.Secure.getString(context.contentResolver, Settings.Secure.ANDROID_ID)
}

// Ejemplo de uso dentro de una Activity o Fragment:
// val androidId = getAndroidId(applicationContext)
// Log.d("DeviceID", "Android ID: $androidId")

En este fragmento de código, Settings.Secure.ANDROID_ID es la clave para acceder a este valor. El método getString() del ContentResolver se encarga de recuperar el identificador. Es importante notar que el resultado puede ser null en ciertos escenarios, especialmente en emuladores o dispositivos sin los servicios de Google Play instalados, por lo que siempre es buena práctica manejar esta posibilidad.

Para más detalles sobre Settings.Secure.ANDROID_ID, puedes consultar la documentación oficial de Android Developers.

Limitaciones y consideraciones sobre el Android ID

A pesar de su facilidad de acceso, las limitaciones del Android ID son significativas:

  1. Persistencia limitada: Como se mencionó, a partir de Android 8.0, el Android ID cambia con la desinstalación y reinstalación de la aplicación. Esto lo hace poco útil para el seguimiento de usuarios a largo plazo o para escenarios donde se requiere persistencia a través de la vida útil del dispositivo.
  2. Multiusuario: Si un dispositivo Android tiene múltiples perfiles de usuario, cada perfil tendrá su propio Android ID, lo que significa que una aplicación verá un ID diferente para cada usuario en el mismo dispositivo.
  3. Restablecimiento de fábrica: Un restablecimiento de fábrica siempre generará un nuevo Android ID.
  4. No garantiza la unicidad global: Aunque está diseñado para ser único por instalación (o por app/usuario en versiones recientes), no hay una garantía matemática de que no puedan existir colisiones, aunque son extremadamente raras.

En mi opinión, el Android ID sigue siendo útil para ciertas métricas de uso y personalización dentro de una misma instalación de la aplicación. Sin embargo, si tu objetivo es un seguimiento persistente y único del dispositivo o del usuario a través de reinstalaciones, el Android ID ya no es la herramienta adecuada. Los cambios en las políticas de Android demuestran una clara tendencia a disuadir el uso de identificadores persistentes y no reseteables.

El IMEI: un identificador único de hardware con grandes restricciones

El IMEI (International Mobile Equipment Identity) es, por naturaleza, el identificador más persistente y único de un dispositivo móvil. Es un número de 15 a 17 dígitos que identifica de forma global y única a un dispositivo móvil GSM, WCDMA y iDEN. Es comparable al número de serie de un equipo y se utiliza principalmente para identificar teléfonos robados (blacklist), para propósitos de garantía y para ciertas funciones de red. Precisamente por su unicidad y persistencia, se ha convertido en el foco de estrictas regulaciones de privacidad.

¿Qué es el IMEI y por qué es tan sensible?

El IMEI identifica el hardware del dispositivo. No cambia con restablecimientos de fábrica, ni con la instalación de nuevas ROMs, ni con la desinstalación de aplicaciones. Esta característica, que lo hace invaluable para operadores de red y fabricantes, lo convierte en una herramienta extremadamente potente para el seguimiento de usuarios a lo largo del tiempo, a través de diferentes aplicaciones y reinstalaciones.

Esta persistencia es precisamente la razón por la que el acceso al IMEI se ha restringido tan severamente. Un identificador tan único y permanente puede vincularse fácilmente a la actividad de un usuario, creando un perfil detallado de su comportamiento, ubicación y preferencias sin su consentimiento explícito o la capacidad de resetear dicho identificador. La creciente preocupación por la privacidad de los datos ha llevado a las empresas como Google a limitar el acceso a estos identificadores tan "poderosos".

Cómo intentar obtener el IMEI en Kotlin (y por qué no deberías)

Históricamente, el IMEI se obtenía utilizando la clase TelephonyManager. Sin embargo, es crucial entender que el acceso a este identificador está severamente restringido en las versiones modernas de Android, y para la mayoría de las aplicaciones de terceros, simplemente ya no es posible obtenerlo.

Para versiones de Android anteriores a 10 (API nivel 29), y solo si la aplicación tenía el permiso READ_PHONE_STATE, se podía intentar obtener el IMEI de la siguiente manera:

Primero, se debe declarar el permiso en el AndroidManifest.xml:

<uses-permission android:name="android.permission.READ_PHONE_STATE"/>

Luego, en Kotlin, el código para obtenerlo sería (con las advertencias correspondientes):

import android.Manifest
import android.content.Context
import android.content.pm.PackageManager
import android.os.Build
import android.telephony.TelephonyManager
import android.util.Log
import androidx.core.content.ContextCompat

fun getImei(context: Context): String? {
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
        // En Android 10 (API 29) y posteriores, getDeviceId() está obsoleto
        // y requiere permisos de nivel de sistema para la mayoría de las apps.
        // Las apps de terceros no pueden acceder al IMEI directamente.
        Log.w("DeviceID", "IMEI no disponible para apps de terceros en Android 10+.")
        return null
    }

    if (ContextCompat.checkSelfPermission(context, Manifest.permission.READ_PHONE_STATE) == PackageManager.PERMISSION_GRANTED) {
        val telephonyManager = context.getSystemService(Context.TELEPHONY_SERVICE) as TelephonyManager
        try {
            // getDeviceId() ha sido obsoleto para APIs > 26 y se comporta diferente
            // en Android 10+. Para dispositivos con múltiples SIMs, esto solo obtiene el ID de la primera SIM.
            @Suppress("Deprecation")
            return telephonyManager.deviceId
        } catch (e: SecurityException) {
            Log.e("DeviceID", "No se tienen los permisos necesarios para obtener el IMEI: ${e.message}")
            return null
        }
    } else {
        Log.w("DeviceID", "Permiso READ_PHONE_STATE no concedido.")
        // Deberías solicitar el permiso al usuario en tiempo de ejecución
        // si tu app realmente lo necesita para versiones anteriores a Android 10.
        return null
    }
}

La telephonyManager.getDeviceId() se marca como obsoleta para dispositivos con API 26 y superiores y su funcionalidad se ha modificado drásticamente a partir de Android 10 (API 29). Para más información sobre el TelephonyManager, puedes revisar su documentación oficial.

Restricciones de privacidad y la deprecación del IMEI para aplicaciones de terceros

Las restricciones sobre el acceso al IMEI han sido una de las evoluciones más significativas en la plataforma Android:

  • Android 6.0 (Marshmallow - API 23): Introdujo los permisos en tiempo de ejecución. Las aplicaciones tenían que solicitar el permiso READ_PHONE_STATE al usuario explícitamente, lo que ya era un gran paso.
  • Android 8.0 (Oreo - API 26): getDeviceId() comenzó a devolver un identificador nulo o un valor genérico para aplicaciones de terceros que no tuvieran permisos específicos o que no fueran aplicaciones de sistema.
  • Android 10 (Q - API 29): Marcó el final efectivo del acceso al IMEI para la mayoría de las aplicaciones de terceros. Las aplicaciones que intentan acceder a identificadores de dispositivos persistentes como el IMEI o el número de serie de hardware utilizando READ_PHONE_STATE recibirán un valor nulo o genérico. Solo las aplicaciones de sistema, las aplicaciones con privilegios especiales (como el operador telefónico predeterminado), o las aplicaciones que vienen preinstaladas como parte del firmware del dispositivo, pueden seguir accediendo a estos identificadores. Esto se detalla en las políticas de privacidad de Android 10.

En mi opinión, esta serie de restricciones y la eventual deprecación del acceso al IMEI para las aplicaciones de terceros es una medida absolutamente necesaria y bienvenida. Los usuarios deben tener el control sobre qué información se comparte y cómo se rastrea su dispositivo. Obligar a los desarrolladores a buscar alternativas más respetuosas con la privacidad fomenta un ecosistema de aplicaciones más ético y seguro.

Alternativas y mejores prácticas para la identificación de dispositivos

Dado el panorama actual y futuro de la privacidad en Android, es fundamental que los desarrolladores adopten enfoques alternativos para la identificación de dispositivos que sean más respetuosos y cumplan con las políticas de la plataforma. La clave es entender tu caso de uso: ¿Necesitas un identificador para fines publicitarios, analíticos, de autenticación o de prevención de fraude? La respuesta determinará la mejor alternativa.

Identificadores específicos de la instalación (Instance ID, Advertising ID)

  1. ID de Publicidad (Advertising ID): Este es el identificador recomendado por Google para fines publicitarios. A diferencia del Android ID (en versiones modernas), el Advertising ID es el mismo para todas las aplicaciones instaladas en un dispositivo y, lo más importante, el usuario puede restablecerlo o desactivar la personalización de anuncios en cualquier momento desde la configuración de Google de su dispositivo. Es pseudónimo y está diseñado específicamente para publicidad.

    Para obtenerlo, tu aplicación necesita incluir la biblioteca de Servicios de Google Play para publicidad:

    implementation 'com.google.android.gms:play-services-ads-identifier:18.0.1' // Verifica la última versión
    

    Y luego en tu código Kotlin:

    import com.google.android.gms.ads.identifier.AdvertisingIdClient
    import kotlinx.coroutines.Dispatchers
    import kotlinx.coroutines.withContext
    
    suspend fun getAdvertisingId(context: Context): String? = withContext(Dispatchers.IO) {
        try {
            val adInfo = AdvertisingIdClient.getAdvertisingIdInfo(context)
            if (adInfo != null && !adInfo.isLimitAdTrackingEnabled) {
                return@withContext adInfo.id
            } else {
                Log.d("DeviceID", "Limit Ad Tracking está habilitado o adInfo es nulo.")
                return@withContext null
            }
        } catch (e: Exception) {
            Log.e("DeviceID", "Error al obtener el Advertising ID: ${e.message}")
            return@withContext null
        }
    }
    

    Este método debe ejecutarse en un hilo de fondo (como Dispatchers.IO) ya que es una llamada de red/IPC. Para más información, consulta la documentación de AdvertisingIdClient.

  2. ID de Instancia de Firebase (Firebase Installation ID - FID): Si utilizas Firebase, el FID es un identificador único para cada instalación de tu aplicación. Es estable mientras la aplicación esté instalada y se asocia con tu proyecto de Firebase. Es ideal para servicios de Firebase como Cloud Messaging o Analytics.

    import com.google.firebase.installations.FirebaseInstallations
    import com.google.android.gms.tasks.Tasks
    import java.util.concurrent.TimeUnit
    
    fun getFirebaseInstallationId(): String? {
        return try {
            Tasks.await(FirebaseInstallations.getInstance().id, 10, TimeUnit.SECONDS)
        } catch (e: Exception) {
            Log.e("FirebaseID", "Error al obtener Firebase Installation ID: ${e.message}")
            null
        }
    }
    

UUID generados por la aplicación

Esta es a menudo la solución más sencilla y respetuosa con la privacidad cuando necesitas un identificador único para tu propia aplicación, que persista a través de las sesiones, pero que se pueda restablecer fácilmente si el usuario desinstala la aplicación.

La idea es generar un UUID (Universally Unique Identifier) aleatorio la primera vez que la aplicación se ejecuta y luego almacenarlo localmente (por ejemplo, en SharedPreferences).

import android.content.Context
import android.content.SharedPreferences
import java.util.UUID

class DeviceIdManager(context: Context) {

    private val PREFS_NAME = "app_prefs"
    private val UNIQUE_ID_KEY = "unique_device_id"
    private val sharedPrefs: SharedPreferences = context.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE)

    fun getAppSpecificDeviceId(): String {
        var uuid = sharedPrefs.getString(UNIQUE_ID_KEY, null)
        if (uuid == null) {
            uuid = UUID.randomUUID().toString()
            sharedPrefs.edit().putString(UNIQUE_ID_KEY, uuid).apply()
        }
        return uuid
    }
}

// Ejemplo de uso:
// val deviceIdManager = DeviceIdManager(applicationContext)
// val appDeviceId = deviceIdManager.getAppSpecificDeviceId()
// Log.d("DeviceID", "App Specific Device ID: $appDeviceId")

Ventajas:

  • Control total sobre el identificador.
  • No requiere permisos especiales.
  • Es único para cada instalación de la aplicación.
  • Se borra automáticamente cuando el usuario desinstala la aplicación (lo cual es bueno para la privacidad).

Desventajas:

  • No es persistente entre reinstalaciones de la aplicación. Si el usuario desinstala y vuelve a instalar, se generará un nuevo ID.
  • No es un identificador a nivel de dispositivo (no se puede usar para correlacionar diferentes aplicaciones o identificar el hardware).

Consideraciones sobre permisos y la evolución de la privacidad

El panorama actual y la dirección futura de Android son claros: los identificadores de dispositivos persistentes y de amplio alcance son cada vez más difíciles de obtener. La privacidad del usuario es una prioridad. Como desarrolladores, debemos internalizar esto y diseñar nuestras aplicaciones con la privacidad en mente desde el principio.

Algunas pautas clave incluyen:

  • Minimizar la recolección de datos: Recopila solo la información estrictamente necesaria para que tu aplicación funcione.
  • Solicitar permisos de forma inteligente: Pide permisos solo cuando sean absolutamente necesarios y explica al usuario por qué los necesitas.
  • Utilizar identificadores reseteables: Siempre que sea posible, opta por identificadores que el usuario pueda controlar o restablecer, como el Advertising ID.
  • Cumplir con las regulaciones: Asegúrate de que tus prácticas de reco
Diario Tecnología