OASIS_MOBILE/docs/ARQUITECTURA_APK.md
hacklab b277f8e1cf pruebas en la APK: huecos dobles de las barras, contraste y zonas tactiles
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).
2026-10-20 13:50:46 +02:00

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:

  1. isExtractionNeeded(): mira si hay que descomprimir.
  2. configureWebView() y la pantalla de carga.
  3. En otro hilo: copyNodeProjectIfNeeded() y después startNode(), que lanza node nodejs-project/main.js y el servicio en primer plano.
  4. 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.js lo 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) y models → 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: nuestro src/, main.js y package.json, con el node_modules del 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.