Probado con la APK instalada en el emulador de Android Studio (Pixel 9, Android 16): - La barra de arriba llevaba 65px de mas y la de abajo 24: el contenedor de la app del dev ya se aparta de las barras del sistema y nuestro CSS volvia a sumar safe-area-inset. Ahora --inset-top y --inset-bottom, a 0, en mobile_base.css. - Borrar en una tarjeta: la barra de confirmar se metia en la fila de botones y 'Si, borrar' salia letra a letra. La fila ya puede saltar de linea. - Acampada: .time-chip y .pref-pill del dev quedaban en negro sobre gris oscuro, y la pildora elegida con texto claro sobre el resalte en las paletas oscuras. - Zonas tactiles a 44px: barra de arriba, fichas, pasos de la bienvenida, aviso de bienvenida, pie, casillas con su etiqueta, botones del buzon; enlaces en el texto a 24px. - Acceso a un bloque de Ajustes: el titulo ya no queda bajo la barra de arriba. En la guia y en ARQUITECTURA_APK.md: no usar env(safe-area-inset-*), los tamaños minimos y el fallo del boton atras de la app del dev (la tecla retrocede dos pasos).
7 KiB
Cómo está hecha la APK de Oasis
Análisis de la APK oficial de epsylon (oasis-v1.3.3.apk, sha256 4e8d60d4…5550d)
con REA 6.7.0 y JADX headless 0.7.1, el 20/10/2026. Lo que se cita sale del código
decompilado de com.solarnethub.oasis.OasisActivity; las salidas completas están
en OASIS_APK/ANALISIS_REA/ (fuera del repo).
Las piezas
La app es una envoltura Android pequeña (cuatro clases propias) alrededor de un servidor Node.js que corre dentro del móvil, con nodejs-mobile.
APK
├── classes.dex OasisActivity pantalla: WebView, extracción, arranque de node
│ OasisService servicio en primer plano (sigue vivo con la app cerrada)
│ OasisAudioBridge, OasisStreamDecoder micro, altavoz y cámara del Teléfono
├── lib/*/libnode.so node para el móvil
└── assets/nodejs-project.zip
└── nodejs-project/ main.js, package.json, src/ (backend, vistas, CSS, node_modules)
La interfaz no es nativa: es HTML que sirve node en http://127.0.0.1:3000 y que la
WebView enseña. Todo lo que vemos (vistas, CSS, textos) va dentro del zip.
El arranque
onCreate:
isExtractionNeeded(): mira si hay que descomprimir.configureWebView()y la pantalla de carga.- En otro hilo:
copyNodeProjectIfNeeded()y despuésstartNode(), que lanzanode nodejs-project/main.jsy el servicio en primer plano. - Cuando node responde en
127.0.0.1:3000, la WebView carga la página.
Cuándo se descomprime el zip
// copyNodeProjectIfNeeded()
File file2 = new File(file, ".version");
String appVersion = getAppVersion(); // versionName + "." + versionCode
String strTrim = file2.exists() ? readFile(file2).trim() : null;
if (file.exists() && appVersion.equals(strTrim)) {
Log.i("OASIS", "Node project already up to date (v" + appVersion + ")");
return;
}
...
stashLegacyState(file3, file4); // copia src/configs a .configs-stash
deleteRecursive(file);
extractZipAsset(ASSET_ZIP, ...);
restoreLegacyState(file4, file3); // vuelve a poner los configs de antes
writeFile(file2, appVersion);
Solo se descomprime si cambia versionName.versionCode. Una APK nueva con el mismo
nombre y número de versión arranca con el código de la vez anterior, aunque dentro
lleve otro. Eso es lo que pasaba con nuestras APK de prueba: el versionCode solo
subía con cada commit, y al instalar una APK hecha sobre el mismo commit (o con
cambios sin commitear) se veía el CSS y las vistas de antes. Desde el 20/10/2026
sobre_oficial.sh pone una huella del código en el versionName.
Al descomprimir se conservan los ficheros de src/configs (la config de cada cual:
tema, idioma, workflow, colores). Consecuencia: si cambiamos la config de serie en
configs/oasis-config.json, solo la ve quien instala de cero; quien actualiza se
queda con la suya.
La WebView no guarda caché
// configureWebView()
settings.setCacheMode(2); // LOAD_NO_CACHE
this.webView.clearCache(true);
Cada página y cada hoja de estilo se piden de nuevo al node del móvil. Si después de instalar se ve CSS viejo, no es la caché: es que node está sirviendo el código viejo (ver el punto anterior).
La WebView solo navega dentro de 127.0.0.1:3000 y localhost:3000. Los enlaces
a una .apk los descarga ella misma y el resto de enlaces http(s) los abre en otra
app (el navegador). Hay rutas que intercepta para el Teléfono (/phone/speaker,
/phone/accept-speaker).
El backend por dentro
rea analyze-javascript-application sobre el nodejs-project oficial, sin
node_modules (463 ficheros JavaScript). Del grafo de imports, contando solo
ficheros del dev:
backend/backend.jslo cose todo: hace 896 de los imports. Rutas, middleware y la llamada a cada modelo y cada vista pasan por él.- Las capas van
backend → views(568 imports),backend ↔ models(218 y 223) ymodels → configs(180). Las vistas casi no importan nada del backend (3). - Lo más importado, y por tanto lo que más rompe si cambia:
views/main_views.js(217),models/typed_log.js(194),configs/config-manager.js(152),views/clearnet_view.js(116),backend/dedupe.js(112).
Cuadra con la lista de lo que no se toca de la guía: un cambio nuestro en
backend.js o en main_views.js tropieza con casi todo lo demás en cada versión.
Por eso lo nuestro va en fork_routes.js, mobile_ui.js y las hojas propias, que
el dev no importa.
Barras del sistema y botón atrás
onCreate pone un OnApplyWindowInsetsListener que aparta el contenedor de la
WebView de la barra de estado, del recorte de la cámara y de la barra de gestos
(setPadding con esos insets), salvo en pantalla completa. Pero devuelve los insets
sin consumir, y la WebView los sigue anunciando como safe-area-inset-*: si el CSS
los suma, el hueco sale doble (65 px de más arriba y 24 abajo en un Pixel 9 con
Android 16). Por eso nuestras hojas usan --inset-top y --inset-bottom a 0.
El botón atrás pasa por goBackInWebView(), que hace webView.goBack() o, si no
puede, carga la página anterior de su propia lista (visitedUrls). Con el gesto de
atrás se llama una vez. Con la tecla (móviles con tres botones) se llama dos veces,
por onKeyDown y por registerPredictiveBack, y se retrocede dos pasos: con el menú
abierto, en vez de cerrarlo se va a la pantalla anterior. Es de su código Android;
hay que pedírselo al dev.
Lo que hace nuestra APK encima
android/scripts/sobre_oficial.sh coge esta APK tal cual y cambia:
assets/nodejs-project.zip: nuestrosrc/,main.jsypackage.json, con elnode_modulesdel dev (el podado para el móvil).- El paquete, a
net.laenre.oasis, para que conviva con la oficial. - El icono, por el adaptativo de
android/icono/. versionName, a<versión>-hacklab.<huella del código>.
La parte Android (Java, puente de audio, servicio) es la del dev sin tocar.
Repetir el análisis
npm install --global --prefix ~/.local rea-agents@6.7.0
# JADX headless 0.7.1, comprobando la huella que fija REA
curl -fL -o jadx-headless-mcp-0.7.1-all.jar \
https://github.com/1013503897/jadx-headless-mcp/releases/download/v0.7.1/jadx-headless-mcp-0.7.1-all.jar
echo '6e5eacf500b64292bfb73c49797c1958f6ee44646e43e868039ae7feb573ff75 jadx-headless-mcp-0.7.1-all.jar' | sha256sum -c -
export REA_JADX_MCP_JAR=$PWD/jadx-headless-mcp-0.7.1-all.jar JAVA_HOME=/opt/android-studio/jbr
rea inspect-android-package oasis-v1.3.3.apk
rea inspect-android-class oasis-v1.3.3.apk com.solarnethub.oasis.OasisActivity
rea inspect-android-method oasis-v1.3.3.apk com.solarnethub.oasis.OasisActivity copyNodeProjectIfNeeded
# el backend: sacar nodejs-project del zip y quitarle src/server/node_modules
# (con ellas, 16.500 ficheros, se corta por tiempo)
rea --json analyze-javascript-application /ruta/absoluta/nodejs-project-sin-node_modules
Con cada versión nueva del dev conviene mirar otra vez copyNodeProjectIfNeeded,
configureWebView y el manifiesto: si cambia la forma de extraer o la caché,
sobre_oficial.sh puede tener que cambiar también.