Desarrolladores: es muy fácil evitar las restricciones API ocultas de Android

click fraud protection

Android 9 Pie y Android 10 lanzan advertencias o bloquean directamente el acceso a API ocultas. Así es como los desarrolladores pueden sortear las restricciones ocultas de la API.

Retrocedamos a hace más de un año y todos estamos emocionados de ver lo que vendrá en las versiones beta de Android P. Los usuarios esperan nuevas funciones y los desarrolladores esperan algunas herramientas nuevas para mejorar sus aplicaciones. Desafortunadamente para algunos de esos desarrolladores, la primera versión beta de Android P vino con una sorpresa desagradable: restricciones de API ocultas. Antes de profundizar en lo que eso significa exactamente, permítanme explicarles un poco sobre su contexto.

¿De qué se trata todo esto?

Los desarrolladores de aplicaciones de Android no tienen que empezar desde cero cuando crean una aplicación. Google proporciona herramientas para hacer que el desarrollo de aplicaciones sea más fácil y menos repetitivo. Una de estas herramientas es el SDK de Android. El SDK es esencialmente una referencia a todas las funciones que los desarrolladores pueden utilizar de forma segura en sus aplicaciones. Estas funciones vienen de serie en todas las variantes de Android que aprueba Google. Sin embargo, el SDK no es exhaustivo. Hay bastantes partes "ocultas" del marco de Android que no forman parte del SDK.

Estas partes "ocultas" pueden ser increíblemente útiles para cosas más hacky o de bajo nivel. Por ejemplo, mi Aplicación Cajón de widgets hace uso de una función que no es SDK para detectar cuando un usuario inicia una aplicación desde un widget para que el cajón pueda cerrarse automáticamente. Quizás esté pensando: "¿Por qué no hacer que estas funciones que no son del SDK formen parte del SDK?" Bueno, el problema es que su funcionamiento no es del todo predecible. Google no puede asegurarse de que cada parte del marco funcione en todos los dispositivos que aprueba, por lo que selecciona métodos más importantes para verificarlos. Google no garantiza que el resto del marco sea coherente. Los fabricantes pueden cambiar o eliminar por completo estas funciones ocultas. Incluso en diferentes versiones de AOSP, nunca se sabe si una función oculta seguirá existiendo o funcionará como solía hacerlo.

Si se pregunta por qué he estado usando la palabra "oculto", es porque estas funciones ni siquiera forman parte del SDK. Normalmente, si intenta utilizar un método o clase oculto en una aplicación, no se podrá compilar. Usarlos requiere reflexión o un SDK modificado.

Con Android P, Google decidió que ocultarlos no era suficiente. Cuando se lanzó la primera versión beta, se anunció que la mayoría (no todas) las funciones ocultas ya no estaban disponibles para los desarrolladores de aplicaciones. La primera versión beta le avisaría cuando su aplicación utilizara una función de la lista negra. Las siguientes versiones beta simplemente bloquearon tu aplicación. Al menos para mí, esta lista negra era bastante molesta. No sólo rompió bastante Gestos de navegación, pero dado que las funciones ocultas están en la lista negra de forma predeterminada (Google tiene que incluir manualmente algunas en la lista blanca por versión), tuve muchos problemas para que Widget Drawer funcionara.

Ahora bien, había algunas formas de evitar la lista negra. La primera fue simplemente mantener su aplicación orientada a API 27 (Android 8.1), ya que la lista negra solo se aplicaba a aplicaciones orientadas a la API más reciente. Desafortunadamente, con Google requisitos mínimos de API para publicar en Play Store, solo sería posible apuntar a API 27 durante un tiempo determinado. A partir del 1 de noviembre de 2019, todas las actualizaciones de aplicaciones en Play Store deben tener como objetivo API 28 o posterior.

La segunda solución es en realidad algo que Google incorporó a Android. Es posible ejecutar un comando ADB para desactivar la lista negra por completo. Esto es excelente para uso personal y pruebas, pero puedo decirles de primera mano que intentar que los usuarios finales configuren y ejecuten ADB es una pesadilla.

Entonces, ¿dónde nos deja eso? Ya no podemos apuntar a API 27 si queremos subirlo a Play Store, y el método ADB simplemente no es viable. Sin embargo, eso no significa que nos quedemos sin opciones.

La lista negra de API oculta solo se aplica a aplicaciones de usuario que no están en la lista blanca. Las aplicaciones del sistema, las aplicaciones firmadas con la firma de la plataforma y las aplicaciones especificadas en un archivo de configuración están exentas de la lista negra. Curiosamente, el conjunto de servicios de Google Play está especificado en ese archivo de configuración. Supongo que Google es mejor que el resto de nosotros.

En fin, sigamos hablando de la lista negra. La parte que nos interesa hoy es que las aplicaciones del sistema están exentas. Eso significa, por ejemplo, que la aplicación System UI puede usar todas las funciones ocultas que desee ya que está en la partición del sistema. Obviamente, no podemos simplemente enviar una aplicación a la partición del sistema. Para eso se necesita root, un buen gestor de archivos, conocimiento de permisos... Sería más fácil usar ADB. Sin embargo, esa no es la única forma en que podemos ser una aplicación del sistema, al menos en lo que respecta a la lista negra de API oculta.

Vuelva a pensar en hace siete párrafos cuando mencioné la reflexión. Si no sabes qué es el reflejo te recomiendo leer esta página, pero aquí hay un resumen rápido. En Java, la reflexión es una forma de acceder a clases, métodos y campos normalmente inaccesibles. Es una herramienta increíblemente poderosa. Como dije en ese párrafo, la reflexión solía ser una forma de acceder a funciones que no pertenecen al SDK. La lista negra de API bloquea el uso de la reflexión, pero no bloquea el uso de doble-reflexión.

Ahora, aquí es donde se vuelve un poco extraño. Normalmente, si quisieras llamar a un método oculto, harías algo como esto (esto está en Kotlin, pero Java es similar):

val someHiddenClass = Class.forName("android.some.hidden.Class")
val someHiddenMethod = someHiddenClass.getMethod("someHiddenMethod", String::class.java)

someHiddenMethod.invoke(null, "some important string")

Sin embargo, gracias a la lista negra de API, obtendrías una excepción ClassNotFoundException. Sin embargo, si reflexionas dos veces, funciona bien:

val forName = Class::class.java.getMethod("forName", String:: class.java)
val getMethod = Class::class.java.getMethod("getMethod", String:: class.java, arrayOf>()::class.java)
val someHiddenClass = forName.invoke(null, "android.some.hidden.Class") asClass
val someHiddenMethod = getMethod.invoke(someHiddenClass, "someHiddenMethod", String::class.java)

someHiddenMethod.invoke(null, "some important string")

¿Extraño verdad? Bueno, sí, pero también no. La lista negra de API rastrea quién llama a una función. Si la fuente no está exenta, falla. En el primer ejemplo, la fuente es la aplicación. Sin embargo, en el segundo ejemplo, la fuente es el propio sistema. En lugar de usar la reflexión para obtener lo que queremos directamente, la usamos para decirle al sistema que obtenga lo que queremos. Dado que la fuente de la llamada a la función oculta es el sistema, la lista negra ya no nos afecta.

Así que hemos terminado. Ahora tenemos una manera de evitar la lista negra de API. Es un poco complicado, pero podríamos escribir una función contenedora para no tener que reflexionar dos veces cada vez. No es genial, pero es mejor que nada. Pero en realidad, no hemos terminado. Hay una mejor manera de hacer esto que nos permitirá usar la reflexión normal o un SDK modificado, como en los viejos tiempos.

Dado que la aplicación de la lista negra se evalúa por proceso (que es lo mismo que por aplicación en la mayoría de los casos), podría haber alguna forma de que el sistema registre si la aplicación en cuestión está exenta o no. Por suerte, existe y es accesible para nosotros. Usando ese nuevo truco de doble reflexión, tenemos un bloque de código de un solo uso:

val forName = Class::class.java.getDeclaredMethod("forName", String:: class.java)
val getDeclaredMethod = Class::class.java.getDeclaredMethod("getDeclaredMethod", String:: class.java, arrayOf>()::class.java)

val vmRuntimeClass = forName.invoke(null, "dalvik.system.VMRuntime") asClass
val getRuntime = getDeclaredMethod.invoke(vmRuntimeClass, "getRuntime", null) as Method
val setHiddenApiExemptions = getDeclaredMethod.invoke(vmRuntimeClass, "setHiddenApiExemptions", arrayOf(arrayOf<String>()::class.java)) asMethod

val vmRuntime = getRuntime.invoke(null)

setHiddenApiExemptions.invoke(vmRuntime, arrayOf("L"))

Bien, técnicamente esto no le dice al sistema que nuestra aplicación está exenta de la lista negra de API. En realidad, hay otro comando ADB que puedes ejecutar para especificar funciones que no deberían estar en la lista negra. Eso es lo que estamos aprovechando arriba. Básicamente, el código anula todo lo que el sistema considere exento para nuestra aplicación. Pasar "L" al final significa que todos los métodos están exentos. Si desea eximir métodos específicos, utilice la sintaxis de Smali: Landroid/some/hidden/Class; Landroid/some/other/hidden/Class;.

Ahora realmente hemos terminado. Cree una clase de aplicación personalizada, coloque ese código en el onCreate() Método, y bam, no más restricciones.


Gracias al miembro de XDA weishu, el desarrollador de VirtualXposed y Taichi, por descubrir originalmente este método. También nos gustaría agradecer al desarrollador reconocido de XDA, topjohnwu, por indicarme esta solución. Aquí hay un poco más sobre cómo funciona., aunque está en chino. Yo también escribió sobre esto en Stack Overflow, con un ejemplo en JNI también.