diff --git a/AGENTS.md b/AGENTS.md
index 042847d..0dbac7d 100644
--- a/AGENTS.md
+++ b/AGENTS.md
@@ -18,7 +18,7 @@ unless the owner explicitly asks.
## Sources of truth
-Resume point: `docs/009-handoff-20261008.md` (closure
+Resume point: `docs/009-handoff-20261016.md` (closure
`docs/009-closure-parity-20261009.md`). Doc index: `docs/README.md`.
Next LOS sync / first project OTA zip: `docs/009-upstream-rebase-ota.md`.
Read the handoff before older remaining-work docs; many fortify/CVE/`007`
diff --git a/README.md b/README.md
index 9c9d927..108becb 100644
--- a/README.md
+++ b/README.md
@@ -8,13 +8,12 @@ This tree is a **parallel product track** to `sunfish-a16-rom` (KernelSU /
split-roadmap standby). Same Spec Kit / docs home pattern; **not** a
SSYSTEMS / buildout-saar project.
-## Paths
+## Project locations
-- Laptop workspace: `/home/aventura/Workspaces/AXP_Resurection`
-- Sibling standby (KSU track): `/home/aventura/Workspaces/sunfish-a16-rom`
-- Gitea (rom-dev01): `https://a16.ssystems.de/` — repo
- `sunfish-admin/AXP-Resurection` (private)
-- Builds stay on `rom-dev01`; laptop = Cursor + adb/fastboot
+- Public canonical repository: `https://gitea.vbits.me/sunfish-admin/AXP-Resurection`
+- Builds stay on the A16 `rom-dev01` worker; local checkouts are used for
+ development and device tooling.
+- The sibling KernelSU/split-roadmap tree remains a separate track.
## Active feature
diff --git a/docs/009-eol-20261026.md b/docs/009-eol-20261026.md
new file mode 100644
index 0000000..9069212
--- /dev/null
+++ b/docs/009-eol-20261026.md
@@ -0,0 +1,73 @@
+# AXPR23 — end of life (2026-10-26)
+
+Status: **EOL / archived**. No new features, no new AXPR23 builds. AXPR24
+(LOS24 / Android 17) is the active line. AXPR23 remains the reference for how
+the LOS port, the AXP packaging and the WebView integration were done.
+
+## Final shipped artifact
+
+- ZIP: `AXP-Resurection-Pro-23.2-20261025-sunfish.zip`
+- The signed ZIP is retained in the build archive; generated ROM artifacts are
+ intentionally not stored in Git.
+- SHA-256: `9d205fd3cc945438dfff3a23b129bf88516c7654f5922bc2ee4d478f9485eef0`
+- Status at build: `FLASH_READY`
+- Signed with the project release key
+ (`C=DE, O=AXP-Resurection, OU=sunfish-a16, CN=release`), cert SHA-256
+ `694a8941b64850a2acd0739c3757286c918eb16cc9788a2d7201222f351f1876`.
+- Installed on the designated reference sunfish via A/B `adb sideload` without
+ a wipe; it booted and was verified.
+
+## Verified state on the flashed device
+
+- `ro.lineage.version`: `23.2-20261025-PRO-sunfish`.
+- WebView: `org.axpos.aosmium_wv` only, native provider through Lineage's static
+ RRO (`framework-res__lineage_sunfish__auto_generated_rro_product.apk`), order:
+ Google entries (all `availableByDefault=false`) → AOSmium
+ (`availableByDefault=true`) → AOSP. LOS `webview` prebuilt removed from the
+ image by `apply_009_single_webview.py`.
+- Runtime WebView smoke (harness `com.axpr.wvtest`): HTTPS load, reduced Vanadium
+ UA, JS, WASM, Canvas2D, WebGL 2.0, WebCrypto, Blob fetch, localStorage,
+ AudioContext, WebRTC — all pass; sandboxed renderer with seccomp.
+- `hardened_malloc`: linked statically into 64-bit libc
+ (`h_malloc_info`, `h_malloc_trim`, … present in the runtime APEX libc);
+ 32-bit stays Scudo.
+- `configstore` HAL crash loop: gone (0 occurrences; `disable_configstore` stub
+ present, HAL binary and rc absent).
+- No `FATAL EXCEPTION`, ANR, watchdog, tombstone or lowmemorykiller events in the
+ audit window. Remaining errors are AOSP telephony/RIL/SIM messages and
+ expected avc denials from apps probing `/proc` or the sensors HAL touching
+ `diag_device`.
+
+## Known device-side caveat (not part of the ROM)
+
+Vector 2.2 (bootloader-spoof host) ships a `sepolicy.rule` that Magisk 30.7 does
+not load, and it lacks the Android 16 `artd` path, so dexopt fails while Vector
+is installed. Policy-only Magisk module
+`axpr-vector-dex2oat` (v3) fixes it:
+
+- `/sdcard/Download/axpr-vector-dex2oat-v3.zip`
+- SHA-256 `e30194c26070031debad0ad0eabff1065b386de7b103b714220a5c6ae51a9881`
+- Delivered via `post-fs-data.sh` + `magiskpolicy --live` (no binaries, no hooks).
+- With it: `VectorDex2Oat` spoofs its cmdline, `dex2oat returned code 0`,
+ `cmd package compile` succeeds.
+- v3 installed on GOLDEN on 2026-10-26 and verified live: policy rules present
+ (`artd xposed_file`), `cmd package compile -m verify -f com.aurora.store` =
+ Success, 0 `art_exec` aborts. Persistence across reboot is the only remaining
+ confirmation (v3 ships the complete rule set in `policy.rules`).
+
+Details and the full rule list: `docs/010-axpr-webview-los23-port.md`.
+
+## Upstream context
+
+LineageOS 23.2 for sunfish is frozen: last nightly `20261008` (SDK 36, patch
+level 2026-09-01); `24.0` nightlies start `20261015` (SDK 37, patch level
+2026-10-01). LineageOS does not keep both lines per device. Therefore AXPR23
+cannot receive further upstream security patches and is intentionally closed.
+
+## What stays authoritative for AXPR24
+
+- The LOS→AXP port method (`scripts/009-*.sh`, `apply_009_*.py`).
+- AXP packaging/signing shape (release keys, AVB flags, Magisk preroot).
+- WebView adaptation chain Vanadium → Mulch → AOSmium, the prebuilt module
+ pattern and the provider overlay/RRO lesson.
+- The device-side IME/kernel notes in `docs/009-*`.
diff --git a/docs/009-kernel-hardening-map.md b/docs/009-kernel-hardening-map.md
index fd14026..a1a332c 100644
--- a/docs/009-kernel-hardening-map.md
+++ b/docs/009-kernel-hardening-map.md
@@ -245,6 +245,39 @@ against Kconfig `depends on` before flipping.
IDs); Lineage rarely matches AXP here.
- **Flash**: owner on return; agent does not need to flash for this phase.
+## 2026-10-16 cgroup and dirty-source audit
+
+The 20261016 GOLDEN build reports a cgroup v2 mount, but the kernel exposes no
+v2 controllers at the root. `/proc/cgroups` also lacks `memory` and `pids`, and
+the log contains:
+
+```text
+Activation of cgroup controller +memory failed ... Invalid argument
+```
+
+The source Kconfig has independent `MEMCG` and `CGROUP_PIDS` options, but the
+ship defconfig does not enable either. This is a real kernel configuration gap,
+not an Android app issue. `lmkd` still works through the legacy low-memory path,
+so it is not an immediate boot blocker, but Android 16 UID memory isolation is
+incomplete.
+
+Do not flip these options directly in the current ship tree. A candidate must
+be built in a disposable clean worktree, retain `SLAB_FREELIST_RANDOM=y`, Yama
+unset, `USERFAULTFD` off and all existing ship patches, then be checked for:
+
+1. boot and USB/ADB recovery;
+2. cgroup v2 `memory`/`pids` controller availability and UID activation;
+3. lmkd behavior, memory pressure, camera, media, radio, Wi-Fi and Bluetooth;
+4. kernel module ABI and vendor SPL compatibility.
+
+The current host kernel is also intentionally patched in place: 503 modified
+paths contain the Stage1/CVE/hardening result, and 13 untracked `*.orig` patch
+backups remain. That is why `uname` ends in `-dirty`. Do not delete or reset
+those files in the canonical tree. A future final bacon should use a disposable
+worktree with patch provenance recorded and no `.orig` residue, or explicitly
+document why the generated kernel is accepted as dirty. The current 20261016
+artifact remains the rollback/reference build until that decision is made.
+
## 2026 catalog refresh (started 2026-09-19)
CIP `kernel-sec` cloned at `/srv/rom/references/cip-kernel-sec` @ `025be706`
diff --git a/docs/009-sideload-system-updates.md b/docs/009-sideload-system-updates.md
index 70c577d..403dbba 100644
--- a/docs/009-sideload-system-updates.md
+++ b/docs/009-sideload-system-updates.md
@@ -3,8 +3,11 @@
Owner cadence: refresh bundled **system / privileged** packages about every
**2–3 months** without a full ROM reflash. **No** AXP OTA / external repos.
-Status: **stub** — fill before claiming FR-019 done. Packaging of the
-shipping image MUST already preserve update eligibility.
+Status: **documented** — commands and signing gates below define the intended
+refresh path. The owner has also verified full-ROM upgrades over existing
+userdata without a wipe. A real individual-APK refresh drill can wait for the
+final ROM candidate; packaging of the shipping image MUST preserve update
+eligibility.
**Full ROM updates** (new LOS base + rebacon) are a different path: recovery
sideload of the project zip. See [`009-upstream-rebase-ota.md`](009-upstream-rebase-ota.md).
@@ -31,3 +34,97 @@ sideload of the project zip. See [`009-upstream-rebase-ota.md`](009-upstream-reb
- [ ] Privileged permissions still grant after update (or re-flash privapp XML noted)
- [ ] Magisk version after update matches kernel/boot expectation
- [ ] Record new versions in `docs/009-version-pins.md`
+
+## Preparation and safety
+
+1. Keep the last known-good full ROM zip and its SHA-256. APK refreshes do not
+ replace a boot/kernel rollback path.
+2. Download the new APK from the upstream/project source into a host-only
+ staging directory. Never put it in Git or paste its bytes into evidence.
+3. Verify the APK before connecting the phone:
+
+```bash
+apksigner verify --verbose --print-certs NEW.apk
+sha256sum NEW.apk
+```
+
+4. Compare the signer with the installed package. For a system app, first
+ record `adb shell pm path PACKAGE` and the installed version. Do not use
+ `-d` for routine refreshes and do not accept a signer mismatch.
+
+## microG / GSF Proxy
+
+For a compatible same-signer update on a test device:
+
+```bash
+adb install -r GmsCore.apk
+adb install -r GsfProxy.apk
+adb shell pm path com.google.android.gms
+adb shell dumpsys package com.google.android.gms | grep -E 'versionName=|versionCode='
+adb shell dumpsys package com.google.android.gsf | grep -E 'versionName=|versionCode='
+```
+
+Then run microG Self-Check, open Phonesky, and verify one ordinary app update.
+If a package is privileged or its signer/permissions change, stop and rebuild
+the image or provide a recovery package with the matching privapp XML; do not
+paper over the rejection with a weaker permission policy.
+
+## Project Phonesky
+
+Phonesky must remain the project-signed artifact and must retain the same
+package identity and signer as the image:
+
+```bash
+adb install -r Phonesky.apk
+adb shell pm path com.android.vending
+adb shell dumpsys package com.android.vending | grep -E 'versionName=|versionCode='
+adb shell monkey -p com.android.vending 1
+```
+
+Verify an ordinary install/update through Phonesky. A signature mismatch,
+privileged-permission loss, or broken microG path is a failed refresh; restore
+the previous APK or the known-good ROM rather than accepting the update.
+
+## Other bundled system apps
+
+Aurora, Droidify, Fossify Gallery, Jelly and OpenEUICC may be refreshed only
+with an APK matching the shipped package identity and signing policy:
+
+```bash
+adb install -r APP.apk
+adb shell pm path PACKAGE
+adb shell dumpsys package PACKAGE | grep -E 'versionName=|versionCode='
+```
+
+For an app whose update must retain system/privileged permissions, verify its
+normal function and the relevant permissions after install. If PackageManager
+places the update under `/data/app/`, that is expected only when the signer and
+permission model remain valid; record the path change and any required XML
+handling in the version pin record.
+
+## Magisk
+
+Magisk manager APK updates and boot-preroot updates are separate:
+
+- Manager-only update: use the manager's normal update path, then verify
+ `magisk -v`, Zygisk/DenyList behavior, and the test package before banking
+ experiments.
+- Boot/runtime update: patch and test a new boot image with the matching
+ Magisk version. This is not an APK-only refresh and must use the full build
+ and LAB-only flash procedure; never mix a new Magisk manager with an older
+ preroot boot image casually.
+
+## Post-update evidence
+
+Record the following under the relevant host artifact or release note:
+
+```bash
+adb shell getprop ro.lineage.version
+adb shell pm path PACKAGE
+adb shell dumpsys package PACKAGE
+adb shell logcat -d -t 500 | grep -E 'PackageManager|avc: denied|FATAL EXCEPTION'
+```
+
+Reboot once after privileged/system-package changes and repeat the package
+path, permissions, microG Self-Check, Phonesky install, and WebView checks.
+Record versions and SHA-256 values in `docs/009-version-pins.md`.
diff --git a/docs/009-version-pins.md b/docs/009-version-pins.md
index 9307959..5b1d7b3 100644
--- a/docs/009-version-pins.md
+++ b/docs/009-version-pins.md
@@ -1,19 +1,22 @@
# 009 version pins
Policy: newest stable (or newest tagged pre-release upstream ships) that works
-on sunfish / LOS 23.2 — except Phonesky = project artifact, WebView = LOS
-default. Refresh with `git fetch --tags` / release APIs before long builds.
-Never ship from stale laptop caches.
+on sunfish / LOS 23.2 — except Phonesky = project artifact and WebView = AXPR
+AOSmium (default, only engine shipped; LOS/Google remain allowed). Refresh with
+`git fetch --tags` / release APIs before long builds. Never ship from stale
+laptop caches.
| component | version_name | version_code | upstream_ref | policy | verified_on | notes |
|-----------|--------------|--------------|--------------|--------|-------------|-------|
-| VanillaBaseline | lineage-23.2 sunfish | | `/srv/rom/artifacts/003-vanilla/20260914-lineage-23.2-sunfish` | los_default | 2026-09-14 | `repo sync` finished 2026-09-13. **Not** the 2026-10-08 nightly |
-| Next OTA upstream pin | lineage-23.2-20261008-nightly-sunfish | | https://download.lineageos.org/devices/sunfish/builds | owner pin | pending | Rebuild via `scripts/009-rebuild-from-los.sh` after `LOS_SYNC=1` + manifest diff |
+| VanillaBaseline | lineage-23.2-20261008 sunfish | 36 | `/srv/rom/artifacts/003-vanilla/20260914-lineage-23.2-sunfish` | los_default | 2026-10-25 | Revisions verified equal to the official `lineageos-23.2-sunfish-20261008-build-manifest.xml` (1130 vs 1129 projects, 0 differing). This is the **last** 23.2 nightly for sunfish |
+| AXPR24 upstream pin | lineage-24.0-20261022-nightly-sunfish | 37 | https://download.lineageos.org/devices/sunfish/builds | owner pin (AXPR24) | 2026-10-25 | SHA-256 `faddd497fe34fb8b57a46f42aa27b38f3276b8dc8a10fea5e91a93ac51e08f03`; 24.0 replaced 23.2 on sunfish from `20261015` (no parallel builds per device) |
+| Current 20261016 source snapshot | lineage-23.2 host tree | | `repo manifest -r` on `rom-dev01` | observed snapshot, not official pin | 2026-10-16 | Manifest SHA256 `44467aa549a9ce50b6c636d60b4bba3d01c398c659b7dff5d61fced2d365c4b1`; verified 2026-10-25: equals the official 20261008 revisions |
| microG GmsCore | v0.3.17.252432 | 252432034 | https://github.com/microg/GmsCore/releases/tag/v0.3.17.252432 | newest_stable | 2026-10-06 staged | default flavor, not `-hw`. PRESIGNED on `system_ext`. GOLDEN live overlay was 0.3.16.1 |
| GSF Proxy | v0.1.0 | | microG official | newest_stable | 2026-09-16 staged | |
| Phonesky | 52.9.22-24 | | project patched host artifact | project_phonesky | 2026-09-14 | `/srv/rom/prebuilts/phonesky/`; never Git |
| Magisk | v30.7 | | https://github.com/topjohnwu/Magisk/releases/tag/v30.7 | newest_stable | 2026-09-17 | Staged APK; GOLDEN oracle **29.0** layout only |
| Kernel sunfish | 4.14.357-openela | | `kernel/google/msm-4.14` @ `28f9290ae067` | los_default + FR-017 stage1 | 2026-09-18 | AXP `kernel_patches` @ `df3cab2155b3`; see `docs/009-kernel-hardening-map.md` |
+| Current 20261016 kernel image | `4.14.357-openela-g28f9290ae067-dirty` | `#82` | host patched tree | observed artifact | 2026-10-16 | `-dirty` reflects the uncommitted applied hardening/CVE tree; clean-source policy is open |
| Aurora Store | present on GOLDEN | | `com.aurora.store` | newest_stable | 2026-09-17 seen | Stage APK later |
| F-Droid | present on GOLDEN | | `org.fdroid.fdroid` | newest_stable | 2026-09-17 seen | |
| Fossify Gallery | present on GOLDEN | | `org.fossify.gallery` | newest_stable | 2026-09-17 seen | |
diff --git a/docs/archive/README.md b/docs/archive/README.md
index 580b2d1..8fbaaab 100644
--- a/docs/archive/README.md
+++ b/docs/archive/README.md
@@ -19,6 +19,7 @@ Do **not** delete these. Owner may retry on the current `009` kernel/userspace.
| Pre-closure acceptance | `../009-lab-acceptance-20261002-*`, `../009-user-zip-audit-20261007.md` | Older `UNOFFICIAL` / `dev-keys` zips |
| Vanilla LOS | `../003-*` | First `repo sync` / `mka bacon` (done) |
| Speckit dumps | `../sunfish-a16-rom-speckit-dump*.txt`, `../001-*-review-pack.txt` | Import noise |
+| USB migration / polish | `009-usb-polish-session-20261016.md` | Historical USB-to-polish session, FLASH_READY AXPR23 pointers; not an AXPR24 build input |
Fortify/CVE files keep `Status: historical` banners. Prefer linking from
[`../README.md`](../README.md).
diff --git a/docs/articulos/2026-11-03_axpr23-eol.md b/docs/articulos/2026-11-03_axpr23-eol.md
new file mode 100644
index 0000000..f9e816a
--- /dev/null
+++ b/docs/articulos/2026-11-03_axpr23-eol.md
@@ -0,0 +1,299 @@
+# AXPR23: AXP Resurrection Pro para el Pixel 4a
+
+**LineageOS 23.2 · Android 16 · `sunfish` · adaptación comunitaria · EOL**
+
+AXP Resurrection Pro (AXPR) es una adaptación de **AXP.OS Pro** para el
+Google Pixel 4a. La idea no era crear otra ROM genérica basada en LineageOS,
+sino recuperar la experiencia de AXP sobre una base Android más reciente,
+manteniendo el soporte de hardware de LineageOS y añadiendo hardening propio.
+
+AXPR23 fue la adaptación para **LineageOS 23.2 / Android 16**. Su última build
+es `23.2-20261025-PRO-sunfish`. La línea está cerrada porque LineageOS dejó de
+publicar Android 16 para `sunfish`; la siguiente generación del proyecto es
+**AXPR24**, sobre LineageOS 24 / Android 17.
+
+La forma más clara de entender la ROM es separar sus cuatro procedencias:
+
+1. la base y el soporte de dispositivo de **LineageOS**;
+2. la experiencia de producto recuperada de **AXP.OS / EXTENDROM**;
+3. las ideas y parches de hardening procedentes de **GrapheneOS / Android
+ Hardening**;
+4. el trabajo propio de **AXPR** para integrar y adaptar todo lo anterior a
+ Android 16 y al Pixel 4a.
+
+---
+
+## 1. La base: qué aporta LineageOS
+
+AXPR23 conserva LineageOS como base real del sistema, no como una colección de
+drivers reutilizados:
+
+- LineageOS 23.2 y Android 16;
+- árbol de dispositivo y soporte de hardware del Pixel 4a;
+- vendor y firmware de `sunfish`;
+- kernel Qualcomm `msm-4.14`;
+- Launcher3, Dialer y componentes centrales del sistema;
+- integración de radio, IMS, Wi‑Fi, cámara, eSIM y recovery;
+- las protecciones de Android y LineageOS ya presentes en la base, como CFI,
+ Shadow Call Stack, KASLR y PAN.
+
+Por eso AXPR sigue comportándose como una ROM LineageOS en lo esencial. Lo que
+cambia es la capa de producto, aplicaciones, ajustes y hardening que se coloca
+encima.
+
+---
+
+## 2. Lo que recuperamos de AXP.OS y EXTENDROM
+
+AXP/EXTENDROM aporta la referencia funcional de la ROM: qué aplicaciones deben
+estar integradas, cómo debe comportarse el preroot, qué controles de privacidad
+son útiles y qué partes de la experiencia merecía la pena conservar.
+
+### Servicios y aplicaciones
+
+AXPR23 integra el stack que en AXP hacía posible utilizar el teléfono sin
+depender de una instalación completa de Google:
+
+- **microG Services / GmsCore**;
+- **GSF Proxy** para aplicaciones que esperan Google Services Framework;
+- **Phonesky**, cliente Play parcheado y firmado para el proyecto;
+- las bibliotecas nativas de GmsCore necesarias para aplicaciones como Maps;
+- **Aurora Store**;
+- **Droidify**;
+- **Fossify Gallery** en lugar de Gallery2/Glimpse;
+- **Jelly**, el navegador ligero de LineageOS;
+- **OpenEUICC** como gestor privilegiado de eSIM.
+
+La ROM mantiene Launcher3 y las aplicaciones centrales de LineageOS. No intenta
+convertir el teléfono en una interfaz completamente distinta: recupera la
+selección de AXP donde aporta una diferencia práctica.
+
+### Preroot y producto
+
+El modelo de root sigue el de AXP/EXTENDROM:
+
+- Magisk se integra durante la construcción del `boot.img`;
+- el manager se activa después de instalar la ROM;
+- el usuario decide qué módulos y configuración quiere añadir;
+- la ROM no fuerza una colección de modificaciones adicionales.
+
+También se recuperan elementos de producto de AXP: identidad **AXP Resurrection
+Pro**, pantalla de bienvenida, animación de arranque, nombre de paquete de
+release y firma del sistema con las claves `release-keys` del proyecto.
+
+### Ajustes y controles de privacidad
+
+Estos cambios son funciones del sistema, no simples APKs añadidos:
+
+| Función | Qué recupera de AXP |
+|---|---|
+| **Private DNS** | Lista amplia de proveedores conocidos, sin servidores propios de AXP |
+| **Comprobación de conectividad** | Proveedor seleccionable o comprobación desactivada |
+| **SUPL y asistencia de ubicación** | Controles para limitar asistencia adicional de localización |
+| **Signature spoofing** | Opción restringida para microG y aplicaciones compatibles, desactivada por defecto |
+| **Grabación de llamadas** | Desbloqueo de la ruta de Dialer donde el dispositivo y la región lo permiten |
+| **Desactivar aplicaciones** | Acción clásica para aplicaciones instaladas por el usuario |
+| **Reinicio automático** | Reinicio por inactividad desde los ajustes de seguridad |
+| **Temporizadores Wi‑Fi/Bluetooth** | Desconexión automática tras el tiempo configurado |
+| **Quick Settings sensibles** | Algunos controles requieren desbloquear el teléfono |
+| **Capturas de pantalla** | Opción global para ventanas que usan `FLAG_SECURE`, desactivada por defecto |
+
+La meta de estos ajustes es devolver control al usuario sin imponer una política
+única. La mayoría se mantienen desactivados o conservadores por defecto.
+
+### Debloat selectivo
+
+AXPR no utiliza una eliminación ciega de todo lo que parezca de Google. La
+limpieza se adaptó a Android 16 y se hizo por grupos:
+
+1. se retiraron las aplicaciones Google de eUICC/LPA y se integró OpenEUICC;
+2. se redujeron componentes de carrier, RCS y entitlement que no son necesarios
+ para el modelo microG;
+3. se retiraron herramientas de mantenimiento que no forman parte del producto
+ final, como `bash`, `htop`, `nano`, `unrar` y `addon.d/50-lineage.sh`;
+4. se redujo la superficie de diagnóstico sin eliminar los servicios necesarios
+ para radio, IMS, ubicación y conectividad;
+5. no se distribuyen `ssh`, `sshd`, `scp`, `sftp`, `rsync` ni `procmem` como
+ herramientas de administración del teléfono.
+
+Se conservaron deliberadamente los componentes Pixel, Qualcomm, IMS y eSIM que
+el funcionamiento diario necesita.
+
+---
+
+## 3. Lo que viene de GrapheneOS y Android Hardening
+
+GrapheneOS y Android Hardening son referencias técnicas para determinadas
+protecciones. AXPR no es GrapheneOS, no usa su plataforma completa y no copia
+un kernel moderno de forma literal. Los parches se adaptaron al kernel no-GKI
+4.14 de `sunfish`.
+
+### Kernel
+
+Sobre el kernel de LineageOS se aplicó un conjunto Stage 1 de aproximadamente
+30 parches, con medidas como:
+
+- FORTIFY intra-object y comprobaciones de tamaño;
+- inicialización de memoria al reservar y liberar;
+- endurecimiento y aleatorización de las listas libres de SLAB;
+- datos `ro_after_init`;
+- sanitización de rutas sensibles;
+- restricciones de `dmesg`;
+- protección y control de determinadas rutas USB;
+- ajustes de integridad y reducción de superficie de depuración.
+
+La configuración final conserva CFI y Shadow Call Stack junto con el hardening
+que ya aportaba Android/LineageOS.
+
+### Backports CVE
+
+El inventario de la build final contiene **449 identificadores CVE** y parches
+acompañantes. Son backports seleccionados de distintas generaciones del kernel,
+con problemas de memoria, drivers, USB, Bluetooth, red, filesystem y
+subsystems Qualcomm.
+
+Algunos ejemplos del inventario son:
+
+`CVE-2015-7837`, `CVE-2017-18232`, `CVE-2020-0255`, `CVE-2021-28039`,
+`CVE-2022-21546` y `CVE-2022-48674`, además de una serie amplia de correcciones
+de 2023–2025.
+
+No se aplicó un megapatch opaco: las series se comprobaron contra el offset
+real del árbol `4.14.357-openela` y se conservaron listas de atribución. El
+inventario completo está en `kernel-cve-backport-list.txt` dentro de la
+documentación del proyecto.
+
+### Allocator y userspace
+
+La integración de **hardened_malloc** sigue la línea de trabajo de GrapheneOS y
+DivestOS, adaptada a la memoria virtual del Pixel 4a:
+
+- `hardened_malloc` en la libc de 64 bits;
+- Scudo para procesos de 32 bits;
+- excepciones específicas para cámara y SurfaceFlinger, para no romper funciones
+ básicas del dispositivo;
+- SELinux en enforcing;
+- cifrado de datos estándar de Android.
+
+La protección se aplicó de forma compatible con el hardware. No se importaron
+dependencias de MTE ni requisitos propios de dispositivos más modernos.
+
+---
+
+## 4. Trabajo propio de AXPR
+
+La parte más importante del proyecto fue hacer que esas piezas funcionaran
+juntas en una base distinta. Ese trabajo es el que convierte el conjunto en una
+adaptación de AXP y no en una lista de parches:
+
+### Portar AXP a Android 16
+
+- localizar los equivalentes de Android 16 para ajustes y servicios que AXP
+ implementaba sobre una generación anterior;
+- adaptar los cambios de Settings, SystemUI, SystemServer, Dialer y framework;
+- conservar la compatibilidad con el vendor y el kernel `sunfish`;
+- revalidar microG, Phonesky, eSIM, radio y las aplicaciones integradas;
+- combinar el debloat con los servicios que Android 16 aún necesita.
+
+### Construir el WebView del proyecto
+
+AXPR integra el AOSmium del proyecto mediante la cadena **Vanadium → Mulch →
+AOSmium**. Hubo que adaptar el build de Chromium/WebView, generar una versión
+compatible con las dos ABIs necesarias y conectar el proveedor mediante el
+overlay estático de LineageOS. El resultado final selecciona AOSmium, pero
+mantiene el mecanismo Android para instalar y elegir otros proveedores.
+
+### Integración y compatibilidad
+
+También son trabajo propio de AXPR:
+
+- seleccionar qué partes de AXP seguían teniendo sentido en Android 16;
+- adaptar los offsets y excepciones de los backports al kernel real;
+- integrar allocator, Magisk, aplicaciones, permisos y overlays en un único
+ producto;
+- mantener la firma, el empaquetado y el flujo de recovery de AXP;
+- probar el resultado en el Pixel 4a y corregir incompatibilidades concretas,
+ en lugar de importar configuraciones completas de otra ROM.
+
+---
+
+## 5. Matriz de procedencia y diferencias
+
+La siguiente tabla resume qué aporta cada proyecto y qué trabajo se hizo en
+AXPR. “GrapheneOS / AH” indica una referencia técnica o una familia de parches;
+no significa que AXPR incorpore GrapheneOS completo.
+
+| Componente | LineageOS aporta | AXP / EXTENDROM aporta | GrapheneOS / AH aporta | Trabajo AXPR en la build final |
+|---|---|---|---|---|
+| **Base y hardware** | Android 16, device tree, vendor, radio y recovery | — | — | Portar la experiencia AXP a `sunfish` sin cambiar la base de dispositivo |
+| **Servicios Google** | Base sin microG ni Play | Modelo microG + GSF Proxy + Phonesky | — | Adaptar permisos, JNI, privilegios y empaquetado a Android 16 |
+| **Aplicaciones** | Launcher3, Dialer y apps centrales | Aurora, Droidify, Fossify y OpenEUICC | — | Seleccionar versiones, ubicaciones y permisos compatibles |
+| **WebView** | Framework y selección de proveedores | Necesidad de conservar el WebView del producto | Chromium/Vanadium como referencia técnica | Construir e integrar AOSmium mediante Vanadium → Mulch → AOSmium |
+| **Root** | No incluido | Preroot de EXTENDROM | — | Integrar Magisk en `boot.img` sin convertirlo en una app de sistema obligatoria |
+| **Privacidad y UX** | Ajustes estándar | DNS, conectividad, llamadas, spoofing, timeouts y controles AXP | Ideas puntuales de SystemUI | Reubicar y adaptar los controles a Settings/SystemUI Android 16 |
+| **Debloat** | Paquetes de la base | Objetivo de reducir superficie | — | Cuatro oleadas selectivas sin romper radio, IMS, eSIM ni Qualcomm |
+| **Kernel** | `msm-4.14` y hardening Android existente | Orden y selección de parches del track AXP | Stage 1, FORTIFY, slab, sanitización y alloc hardening | Adaptar aproximadamente 30 parches al árbol real de `sunfish` |
+| **CVE** | Parches upstream disponibles | Política de backports | Fuentes y referencias de hardening | Integrar y verificar 449 identificadores en series aplicables |
+| **Allocator** | Bionic y Scudo de Android | Objetivo de allocator reforzado | `hardened_malloc` y modelo de excepciones | Adaptar `hardened_malloc` a 64 bits, Scudo a 32 bits y VA39 |
+| **SELinux y cifrado** | Base Android/LineageOS | Requisitos de producto | Referencia de endurecimiento | Mantener SELinux enforcing y cifrado de datos |
+| **Identidad y release** | Formato de build LineageOS | Forma de empaquetado AXP Pro | — | Branding, `release-keys`, ZIP completo y flujo recovery/sideload |
+
+En forma de comparación rápida:
+
+| Área | LineageOS 23.2 limpio | AXPR23 |
+|---|---|---|
+| Servicios Google | No incluidos | microG, GSF Proxy y Phonesky |
+| Aplicaciones | Conjunto estándar de LineageOS | Aurora, Droidify, Fossify, Jelly y OpenEUICC |
+| WebView | Proveedor de LineageOS | AOSmium del proyecto como proveedor seleccionado |
+| Root | No incluido | Magisk preroot en `boot.img` |
+| Ajustes | Opciones estándar | Controles de DNS, conectividad, ubicación, llamadas y privacidad de AXP |
+| Debloat | Paquetes estándar de la base | Limpieza selectiva adaptada a microG y `sunfish` |
+| Kernel | `msm-4.14` de LineageOS | Stage 1, FORTIFY, backports CVE y configuración reforzada |
+| Allocator | Bionic estándar | `hardened_malloc` para 64 bits y Scudo para 32 bits |
+| Identidad | Build LineageOS habitual | Producto PRO firmado con `release-keys` |
+| Actualizaciones | Nightlies de LineageOS | ZIP completo por recovery/sideload |
+
+AXPR mantiene la base de dispositivo de LineageOS y mejora sus capas de
+producto, privacidad y hardening. No es una copia literal de GrapheneOS ni una
+continuación oficial de AXP.OS: es una adaptación bastante directa de AXP.OS
+Pro, con las mejoras y correcciones necesarias para Android 16.
+
+---
+
+## 6. Estado de AXPR23 y última build
+
+AXPR23 está **EOL / archivada**. LineageOS dejó de publicar nuevas builds de
+Android 16 para `sunfish` al comenzar la línea Android 17. No habrá nuevas
+features ni nuevas builds AXPR23; se conserva como referencia y rollback.
+
+- **Build:** `AXP-Resurection-Pro-23.2-20261025-sunfish.zip`
+- **Base:** LineageOS 23.2 / Android 16
+- **SHA-256:** `9d205fd3cc945438dfff3a23b129bf88516c7654f5922bc2ee4d478f9485eef0`
+- **Siguiente línea:** AXPR24 sobre LineageOS 24 / Android 17
+
+AXPR24 no es una actualización automática de AXPR23. Es una nueva adaptación
+que debe volver a validar aplicaciones, WebView, allocator, kernel y packaging
+contra Android 17.
+
+## Descarga
+
+La última build publicada es un ZIP completo para el Google Pixel 4a
+(`sunfish`), firmado con las claves de release del proyecto:
+
+[**Descargar AXPR23 — AXP.OS sobre LineageOS 23 para Pixel 4a**](https://vbits.me/download/284/)
+
+El botón usa Download Monitor y conserva el contador dinámico de descargas.
+
+- **Build:** `AXP-Resurection-Pro-23.2-20261025-sunfish.zip`
+- **Tamaño:** 1.397.472.560 bytes (1,30 GiB)
+- **MD5:** `23378f132ac8b1f53606183451715a9c`
+- **SHA-256:** `9d205fd3cc945438dfff3a23b129bf88516c7654f5922bc2ee4d478f9485eef0`
+- **Instalación:** ZIP completo mediante recovery/sideload. Haz una copia de
+ seguridad antes de instalarlo.
+
+## Créditos
+
+AXPR23 se apoya en el trabajo de **AXP.OS / EXTENDROM**, **LineageOS**,
+**microG**, **GrapheneOS / Android Hardening**, **Vanadium / Mulch / AOSmium**,
+Magisk, OpenEUICC y los proyectos de las aplicaciones integradas. Es una
+adaptación comunitaria independiente para `sunfish`.
diff --git a/docs/public/AXP-Resurrection-Pro-sunfish.md b/docs/public/AXP-Resurrection-Pro-sunfish.md
index e98ee2c..f9e816a 100644
--- a/docs/public/AXP-Resurrection-Pro-sunfish.md
+++ b/docs/public/AXP-Resurrection-Pro-sunfish.md
@@ -1,436 +1,299 @@
-# AXP Resurrection Pro — Pixel 4a (sunfish)
+# AXPR23: AXP Resurrection Pro para el Pixel 4a
-**LineageOS 23.2 · Android 16 · Privacy-oriented custom ROM**
+**LineageOS 23.2 · Android 16 · `sunfish` · adaptación comunitaria · EOL**
-AXP Resurrection Pro (*AXPR*) brings the **AXP.OS Pro** experience forward onto
-**LineageOS 23.2** for the **Google Pixel 4a**. It is aimed at users who want a
-maintained Android 16 base, **microG** instead of proprietary Google Play
-services, optional **root via Magisk**, and **extra kernel and system
-hardening** drawn from established community sources (including
-**GrapheneOS-class** kernel work), without tying you to deprecated AXP update
-servers.
+AXP Resurrection Pro (AXPR) es una adaptación de **AXP.OS Pro** para el
+Google Pixel 4a. La idea no era crear otra ROM genérica basada en LineageOS,
+sino recuperar la experiencia de AXP sobre una base Android más reciente,
+manteniendo el soporte de hardware de LineageOS y añadiendo hardening propio.
-This document describes **what the ROM contains**. It is written for end users
-and reviewers, not for build infrastructure.
+AXPR23 fue la adaptación para **LineageOS 23.2 / Android 16**. Su última build
+es `23.2-20261025-PRO-sunfish`. La línea está cerrada porque LineageOS dejó de
+publicar Android 16 para `sunfish`; la siguiente generación del proyecto es
+**AXPR24**, sobre LineageOS 24 / Android 17.
-**Current generation (example build id):** `23.2-20261008` · `BP4A.251205.006`
-· `release-keys`
+La forma más clara de entender la ROM es separar sus cuatro procedencias:
+
+1. la base y el soporte de dispositivo de **LineageOS**;
+2. la experiencia de producto recuperada de **AXP.OS / EXTENDROM**;
+3. las ideas y parches de hardening procedentes de **GrapheneOS / Android
+ Hardening**;
+4. el trabajo propio de **AXPR** para integrar y adaptar todo lo anterior a
+ Android 16 y al Pixel 4a.
---
-## 1. At a glance
+## 1. La base: qué aporta LineageOS
-| Topic | What you get |
-|--------|----------------|
-| **Base** | LineageOS **23.2**, Android **16**, Pixel 4a only |
-| **Google services** | **microG** + **GSF proxy**; no built-in Google account requirement |
-| **Play Store** | **Play Store (Phonesky)** build patched for this project (exact-alarm and related fixes) |
-| **Root** | **Magisk** embedded at build time; you activate from the Magisk app after flash |
-| **Privacy apps** | **Aurora Store**, **Droidify**, **Fossify Gallery**, **Jelly** browser, **OpenEUICC** |
-| **Hardening** | Kernel patches (Graphene-derived + CVE backports), **hardened_malloc**, trimmed system surface |
-| **Updates** | Full ROM via **recovery sideload**; individual system apps can be updated in place when signed compatibly |
-| **Bootloader** | **Unlocked** workflow (orange state); not designed for relocked retail boot |
+AXPR23 conserva LineageOS como base real del sistema, no como una colección de
+drivers reutilizados:
+
+- LineageOS 23.2 y Android 16;
+- árbol de dispositivo y soporte de hardware del Pixel 4a;
+- vendor y firmware de `sunfish`;
+- kernel Qualcomm `msm-4.14`;
+- Launcher3, Dialer y componentes centrales del sistema;
+- integración de radio, IMS, Wi‑Fi, cámara, eSIM y recovery;
+- las protecciones de Android y LineageOS ya presentes en la base, como CFI,
+ Shadow Call Stack, KASLR y PAN.
+
+Por eso AXPR sigue comportándose como una ROM LineageOS en lo esencial. Lo que
+cambia es la capa de producto, aplicaciones, ajustes y hardening que se coloca
+encima.
---
-## 2. Everything added on top of LineageOS 23.2
+## 2. Lo que recuperamos de AXP.OS y EXTENDROM
-Stock **LineageOS 23.2 for sunfish** is a clean AOSP-derived system: no microG,
-no Play Store, no Magisk, **test-keys** or UNOFFICIAL builds in typical user
-trees, default Private DNS list, default connectivity check (Google), Gallery2
-or Glimpse, full Lineage tool bundle on system, Google carrier/eUICC apps from
-vendor makefiles, and the upstream **4.14** kernel without the AXP/Graphene
-CVE and hardening layers below.
+AXP/EXTENDROM aporta la referencia funcional de la ROM: qué aplicaciones deben
+estar integradas, cómo debe comportarse el preroot, qué controles de privacidad
+son útiles y qué partes de la experiencia merecía la pena conservar.
-**AXP Resurrection Pro** keeps the Lineage base (Launcher3, Dialer, updaters,
-device support) and layers the following.
+### Servicios y aplicaciones
-### 2.1 Product identity and visuals
+AXPR23 integra el stack que en AXP hacía posible utilizar el teléfono sin
+depender de una instalación completa de Google:
-| Change | LineageOS default | AXPR |
-|--------|-------------------|------|
-| Build channel | Often UNOFFICIAL / nightly naming | **PRO** product (`…-PRO-sunfish`) |
-| Signing | Test-keys on many community builds | **release-keys** (project keys) |
-| Manufacturer / brand props | Lineage | **AXP Resurrection** system branding |
-| Setup welcome | Lineage wording | **“Welcome to AXP Resurrection”** |
-| Boot animation | Narrow band layout | **Full-screen** 1080×2340 animation |
-| OTA zip name | `lineage-*.zip` | Matching **`AXP-Resurrection-Pro-*.zip`** (same payload) |
+- **microG Services / GmsCore**;
+- **GSF Proxy** para aplicaciones que esperan Google Services Framework;
+- **Phonesky**, cliente Play parcheado y firmado para el proyecto;
+- las bibliotecas nativas de GmsCore necesarias para aplicaciones como Maps;
+- **Aurora Store**;
+- **Droidify**;
+- **Fossify Gallery** en lugar de Gallery2/Glimpse;
+- **Jelly**, el navegador ligero de LineageOS;
+- **OpenEUICC** como gestor privilegiado de eSIM.
-### 2.2 Google replacement stack (not in vanilla LOS)
+La ROM mantiene Launcher3 y las aplicaciones centrales de LineageOS. No intenta
+convertir el teléfono en una interfaz completamente distinta: recupera la
+selección de AXP donde aporta una diferencia práctica.
-| Component | Purpose |
-|-----------|---------|
-| **microG Services (GmsCore)** | Open-source reimplementation of Play Services |
-| **GSF Proxy** | Framework stub for apps expecting Google Services Framework |
-| **Play Store (Phonesky)** | Play client build maintained for this ROM (exact-alarm and related fixes) |
-| **GmsCore native libraries** | ARM64 JNI libs shipped **inside the image** (required for Maps / TLS stacks) |
+### Preroot y producto
-Vanilla Lineage does **not** ship these; you would install microG yourself on a
-separate track.
+El modelo de root sigue el de AXP/EXTENDROM:
-### 2.3 Extra applications (vs typical LOS sunfish)
+- Magisk se integra durante la construcción del `boot.img`;
+- el manager se activa después de instalar la ROM;
+- el usuario decide qué módulos y configuración quiere añadir;
+- la ROM no fuerza una colección de modificaciones adicionales.
-| App | Notes |
-|-----|--------|
-| **Aurora Store** | Play store front-end |
-| **Droidify** | F-Droid client (instead of shipping classic F-Droid) |
-| **Fossify Gallery** | Replaces **Gallery2 / Glimpse** on this product |
-| **Jelly** | Lightweight browser added on **product** (Lineage 23 does not include Jelly in the default sunfish product set) |
-| **OpenEUICC** | Privileged eSIM manager where Google EUICC is removed |
-| **Magisk** | Preroot boot integration + manager (see §5 Root and Magisk) |
+También se recuperan elementos de producto de AXP: identidad **AXP Resurrection
+Pro**, pantalla de bienvenida, animación de arranque, nombre de paquete de
+release y firma del sistema con las claves `release-keys` del proyecto.
-**Unchanged vs LOS intent:** **Launcher3** (no NeoLauncher), standard Lineage
-core apps, **Lineage WebView** as default provider.
+### Ajustes y controles de privacidad
-### 2.4 Settings and framework behaviour (AXP / GrapheneOS / Calyx class)
+Estos cambios son funciones del sistema, no simples APKs añadidos:
-These are **code changes in the platform**, not separate APKs.
+| Función | Qué recupera de AXP |
+|---|---|
+| **Private DNS** | Lista amplia de proveedores conocidos, sin servidores propios de AXP |
+| **Comprobación de conectividad** | Proveedor seleccionable o comprobación desactivada |
+| **SUPL y asistencia de ubicación** | Controles para limitar asistencia adicional de localización |
+| **Signature spoofing** | Opción restringida para microG y aplicaciones compatibles, desactivada por defecto |
+| **Grabación de llamadas** | Desbloqueo de la ruta de Dialer donde el dispositivo y la región lo permiten |
+| **Desactivar aplicaciones** | Acción clásica para aplicaciones instaladas por el usuario |
+| **Reinicio automático** | Reinicio por inactividad desde los ajustes de seguridad |
+| **Temporizadores Wi‑Fi/Bluetooth** | Desconexión automática tras el tiempo configurado |
+| **Quick Settings sensibles** | Algunos controles requieren desbloquear el teléfono |
+| **Capturas de pantalla** | Opción global para ventanas que usan `FLAG_SECURE`, desactivada por defecto |
-#### Privacy and network
+La meta de estos ajustes es devolver control al usuario sin imponer una política
+única. La mayoría se mantienen desactivados o conservadores por defecto.
-| Feature | What Lineage does | What AXPR adds |
-|---------|-------------------|----------------|
-| **Private DNS presets** | Small default set | **15+ named providers** (AdGuard, Mullvad, Quad9, Cloudflare, regional DoT hosts, etc.) |
-| **Internet connectivity check** | Fixed Google-style check | **User-selectable** provider (Disabled, Amazon, Cloudflare, Google, Huawei, Kuketz, Microsoft, openSUSE, Ubuntu, …) — no AXP-owned servers |
-| **Force disable SUPL** | Not exposed | Settings toggle; forces SUPL off for stricter GNSS |
-| **Stop extra location assistance** | Not exposed | Toggle to stop extra Qualcomm / XTRA-style assistance when disabled |
-| **Kernel command line privacy** | `/proc/cmdline` often world-readable | **Mode 0440** on `/proc/cmdline` for unprivileged apps (GOLDEN/AXP class) |
+### Debloat selectivo
-#### Security, biometrics, and media metadata
+AXPR no utiliza una eliminación ciega de todo lo que parezca de Google. La
+limpieza se adaptó a Android 16 y se hizo por grupos:
-| Feature | What Lineage does | What AXPR adds |
-|---------|-------------------|----------------|
-| **Fingerprint permanent lockout** | Higher failed-attempt threshold | **Stricter lockout** (AXP constant: fewer attempts before permanent lockout) |
-| **Build serial exposure** | Legacy paths may expose serial via deprecated APIs | **`Build.SERIAL` stays unknown**; real serial only via `Build.getSerial()` + permission |
-| **Screenshot EXIF** | OS name, time, unique id in metadata | **Minimal EXIF** (width/height only — Calyx/AXP class) |
-| **Recovery OTA metadata** | Can accept packages restricted to device serial | **Rejects** update packages that carry a `serialno=` constraint |
-| **Signature spoofing** | microG base only | Developer option **`allow_signature_fake`** (default **off**) for microG / compatible apps |
-| **Call recording** | Region/carrier gated | Developer option **`extendrom_call_recording`** unlocks recording path in Dialer where supported |
-| **FLAG_SECURE / screen capture** | Apps can block capture | Optional **`disable_secure_windows`** (default **off**), works on **user** builds without `ro.debuggable=1` |
-| **Disable app** | Android 16 emphasises **Archive**; disable hidden for many user apps | **Disable** restored for **user-installed** apps in Settings |
-| **Native debug** | Standard ptrace policy | **`persist.native_debug`** — when enabled, relaxes ptrace scope for debugging (default off) |
+1. se retiraron las aplicaciones Google de eUICC/LPA y se integró OpenEUICC;
+2. se redujeron componentes de carrier, RCS y entitlement que no son necesarios
+ para el modelo microG;
+3. se retiraron herramientas de mantenimiento que no forman parte del producto
+ final, como `bash`, `htop`, `nano`, `unrar` y `addon.d/50-lineage.sh`;
+4. se redujo la superficie de diagnóstico sin eliminar los servicios necesarios
+ para radio, IMS, ubicación y conectividad;
+5. no se distribuyen `ssh`, `sshd`, `scp`, `sftp`, `rsync` ni `procmem` como
+ herramientas de administración del teléfono.
-#### Timeouts and System UI
-
-| Feature | What Lineage does | What AXPR adds |
-|---------|-------------------|----------------|
-| **Automatic reboot** | Not in stock LOS Settings | Idle **auto-reboot** (Security settings) |
-| **Bluetooth timeout** | Manual only | Turns **Bluetooth off** after configurable inactivity |
-| **Wi‑Fi timeout** | Manual only | Turns **Wi‑Fi off** after configurable inactivity |
-| **Quick Settings tiles** | Usable from lock screen per tile | **Sensitive tiles require unlock** first (GrapheneOS SystemUI patch) |
-| **Keyguard / reboot** | Standard | SystemUI / SystemServer hooks for timeout and reboot integration (AXP server extensions) |
-
-### 2.5 Packages and binaries removed (vs Lineage sunfish)
-
-Selective **deblob**, not a generic “remove all Google” script.
-
-#### Wave 1 — Google eUICC LPA
-
-| Removed | Replaced by |
-|---------|-------------|
-| **EuiccGoogle**, **EuiccGoogleOverlay** | **OpenEUICC** (EuiccSupportPixel support packages **kept**) |
-
-#### Wave 2 — Carrier / RCS Google & AOSP entitlement
-
-| Removed (examples) | Kept for radio |
-|--------------------|----------------|
-| **CarrierServices**, **CarrierSettings**, **CarrierWifi**, **CarrierSetup**, **RilConfigService**, **grilservice** | **ims**, **QtiTelephonyService**, Qualcomm daemons, **Dialer**, **GoogleParts** |
-| **PresencePolling**, **RcsService** (device makefile) | |
-| **ImsServiceEntitlement** (telephony product) | |
-
-#### Wave 3 — Lineage maintenance tools on system
-
-| Removed from system / system_ext | Still on AXPR (AXP parity) |
-|----------------------------------|----------------------------|
-| **bash**, **htop**, **nano**, **unrar**, **addon.d/50-lineage.sh** | **curl**, **vim**, **zstd**, recovery **nano** |
-
-#### Wave 4 — Debug / diag surface
-
-| Removed | Explicitly kept |
-|---------|-----------------|
-| Qualcomm **diag_*** tools, **test_diag**, **imsrcsd** | **loc_launcher**, **lowi-server**, **xtra-daemon**, IMS RTP/data daemons, **cnss-daemon**; keep Lineage **disable_configstore** (overrides HIDL configstore HAL — required on A16 VINTF) |
-| Some **AOSP debug** host tools on device where safe | **bpftool**, **alloctop**, **apexd-lsof** remain in build where the OS requires them |
-
-#### Product OpenSSH (US-S4-1)
-
-| Removed | Kept |
-|---------|------|
-| **ssh**, **sshd**, **scp**, **sftp**, **rsync**, **procmem** on product | **`init.openssh.rc`** stub only (no `sshd` binary) — remote admin via **adb** / Magisk |
-
-### 2.6 Kernel, allocator, and CVEs (summary)
-
-On top of the **same msm-4.14** tree Lineage ships:
-
-- **30 GrapheneOS-derived** kernel patches (Deny USB, allocsize, fortify, slab, sanitize, ro_after_init, randomisation, …) — see §8 below.
-- Extra **Kconfig**: FORTIFY, init-on-alloc/free, dmesg restrict, slab hardened, **SLAB_FREELIST_RANDOM**; **no Yama** (app compatibility).
-- **449** documented **CVE** backport identifiers + companion fixes (e.g. USB lock-scope instead of raw **CVE-2022-49980** one-liner).
-- **hardened_malloc** in **bionic** for 64-bit processes (with graphics/camera exclusions).
-
-Lineage’s existing hardening (CFI, shadow stack, KASLR, PAN, …) is **retained**.
-
-### 2.7 Intentionally **not** changed vs Lineage
-
-- **No** GrapheneOS-style **location lockdown** kernel (GPS behaviour stays LOS-like).
-- **No** Aosmium / proprietary AXP WebView baked in.
-- **No** AXP online OTA or external market repositories.
-- **No** KernelSU in the standard product.
-- **No** bundled Play Integrity / banking Magisk modules.
-- **Device tree / modem firmware** line unchanged (Pixel 4a vendor blobs).
+Se conservaron deliberadamente los componentes Pixel, Qualcomm, IMS y eSIM que
+el funcionamiento diario necesita.
---
-## 3. Who this ROM is for
+## 3. Lo que viene de GrapheneOS y Android Hardening
-- You use **microG** and install apps via **Aurora** / **Droidify** / Play as you choose.
-- You want **AXP-style settings** (private DNS presets, connectivity controls, classic **Disable app**, timeouts, optional signature spoofing and call-recording unlock) on a **current Lineage** tree.
-- You accept **custom ROM risks**: unlocked bootloader, community maintenance, no warranty.
+GrapheneOS y Android Hardening son referencias técnicas para determinadas
+protecciones. AXPR no es GrapheneOS, no usa su plataforma completa y no copia
+un kernel moderno de forma literal. Los parches se adaptaron al kernel no-GKI
+4.14 de `sunfish`.
-**Not goals of this ROM:** banking attestation guarantees, locked bootloader,
-proprietary AXP OTA infrastructure, or shipping every Google / carrier app from
-stock Pixel firmware.
+### Kernel
+
+Sobre el kernel de LineageOS se aplicó un conjunto Stage 1 de aproximadamente
+30 parches, con medidas como:
+
+- FORTIFY intra-object y comprobaciones de tamaño;
+- inicialización de memoria al reservar y liberar;
+- endurecimiento y aleatorización de las listas libres de SLAB;
+- datos `ro_after_init`;
+- sanitización de rutas sensibles;
+- restricciones de `dmesg`;
+- protección y control de determinadas rutas USB;
+- ajustes de integridad y reducción de superficie de depuración.
+
+La configuración final conserva CFI y Shadow Call Stack junto con el hardening
+que ya aportaba Android/LineageOS.
+
+### Backports CVE
+
+El inventario de la build final contiene **449 identificadores CVE** y parches
+acompañantes. Son backports seleccionados de distintas generaciones del kernel,
+con problemas de memoria, drivers, USB, Bluetooth, red, filesystem y
+subsystems Qualcomm.
+
+Algunos ejemplos del inventario son:
+
+`CVE-2015-7837`, `CVE-2017-18232`, `CVE-2020-0255`, `CVE-2021-28039`,
+`CVE-2022-21546` y `CVE-2022-48674`, además de una serie amplia de correcciones
+de 2023–2025.
+
+No se aplicó un megapatch opaco: las series se comprobaron contra el offset
+real del árbol `4.14.357-openela` y se conservaron listas de atribución. El
+inventario completo está en `kernel-cve-backport-list.txt` dentro de la
+documentación del proyecto.
+
+### Allocator y userspace
+
+La integración de **hardened_malloc** sigue la línea de trabajo de GrapheneOS y
+DivestOS, adaptada a la memoria virtual del Pixel 4a:
+
+- `hardened_malloc` en la libc de 64 bits;
+- Scudo para procesos de 32 bits;
+- excepciones específicas para cámara y SurfaceFlinger, para no romper funciones
+ básicas del dispositivo;
+- SELinux en enforcing;
+- cifrado de datos estándar de Android.
+
+La protección se aplicó de forma compatible con el hardware. No se importaron
+dependencias de MTE ni requisitos propios de dispositivos más modernos.
---
-## 4. Preinstalled applications
+## 4. Trabajo propio de AXPR
-| App | Role |
-|-----|------|
-| **microG Services (GmsCore)** | Replaces Google Play Services for many apps |
-| **GSF Proxy** | Google Services Framework compatibility layer |
-| **Play Store (Phonesky)** | Google Play client (project-maintained APK) |
-| **Aurora Store** | Alternative store front-end for Play |
-| **Droidify** | Lightweight F-Droid client |
-| **Fossify Gallery** | Gallery (replaces Lineage Gallery2 / Glimpse on this product) |
-| **Jelly** | Small browser on the product partition |
-| **OpenEUICC** | eSIM / EUICC management (replaces Google EUICC where removed) |
-| **Magisk** | Root manager (stub until you complete first-time setup) |
-| **LineageOS core** | Launcher3, Dialer, Messaging, etc. |
+La parte más importante del proyecto fue hacer que esas piezas funcionaran
+juntas en una base distinta. Ese trabajo es el que convierte el conjunto en una
+adaptación de AXP y no en una lista de parches:
-**WebView:** default is **Lineage / AOSP WebView**. You may install another
-WebView provider and select it under **Developer options → WebView
-implementation** (Aosmium and other legacy AXP WebViews are **not** shipped as
-default).
+### Portar AXP a Android 16
+
+- localizar los equivalentes de Android 16 para ajustes y servicios que AXP
+ implementaba sobre una generación anterior;
+- adaptar los cambios de Settings, SystemUI, SystemServer, Dialer y framework;
+- conservar la compatibilidad con el vendor y el kernel `sunfish`;
+- revalidar microG, Phonesky, eSIM, radio y las aplicaciones integradas;
+- combinar el debloat con los servicios que Android 16 aún necesita.
+
+### Construir el WebView del proyecto
+
+AXPR integra el AOSmium del proyecto mediante la cadena **Vanadium → Mulch →
+AOSmium**. Hubo que adaptar el build de Chromium/WebView, generar una versión
+compatible con las dos ABIs necesarias y conectar el proveedor mediante el
+overlay estático de LineageOS. El resultado final selecciona AOSmium, pero
+mantiene el mecanismo Android para instalar y elegir otros proveedores.
+
+### Integración y compatibilidad
+
+También son trabajo propio de AXPR:
+
+- seleccionar qué partes de AXP seguían teniendo sentido en Android 16;
+- adaptar los offsets y excepciones de los backports al kernel real;
+- integrar allocator, Magisk, aplicaciones, permisos y overlays en un único
+ producto;
+- mantener la firma, el empaquetado y el flujo de recovery de AXP;
+- probar el resultado en el Pixel 4a y corregir incompatibilidades concretas,
+ en lugar de importar configuraciones completas de otra ROM.
---
-## 5. Root and Magisk
+## 5. Matriz de procedencia y diferencias
-- Magisk is integrated into the **boot image at ROM build time** (AXP / EXTENDROM
- style **preroot**), not as a normal preinstalled Play-style system app only.
-- After flashing, open **Magisk** once and allow it to **download and install**
- the current manager APK if prompted.
-- **Do not** use “Install to inactive slot” or repeated boot repatching unless
- you know you need it: the boot image is already prepared. Extra patching often
- confuses the app and can break A/B slots.
-- The home screen line **“Ramdisk: Yes/No”** describes how Magisk would install
- on a *stock* device; on preroot builds it may read **No** even when root works.
- Check **Magisk version** and whether apps can request superuser.
-- **Zygisk**, **DenyList**, and modules are **your choice** after activation;
- the ROM does not ship banking or Play Integrity fix modules.
+La siguiente tabla resume qué aporta cada proyecto y qué trabajo se hizo en
+AXPR. “GrapheneOS / AH” indica una referencia técnica o una familia de parches;
+no significa que AXPR incorpore GrapheneOS completo.
-**KernelSU** is **not** part of this product line.
+| Componente | LineageOS aporta | AXP / EXTENDROM aporta | GrapheneOS / AH aporta | Trabajo AXPR en la build final |
+|---|---|---|---|---|
+| **Base y hardware** | Android 16, device tree, vendor, radio y recovery | — | — | Portar la experiencia AXP a `sunfish` sin cambiar la base de dispositivo |
+| **Servicios Google** | Base sin microG ni Play | Modelo microG + GSF Proxy + Phonesky | — | Adaptar permisos, JNI, privilegios y empaquetado a Android 16 |
+| **Aplicaciones** | Launcher3, Dialer y apps centrales | Aurora, Droidify, Fossify y OpenEUICC | — | Seleccionar versiones, ubicaciones y permisos compatibles |
+| **WebView** | Framework y selección de proveedores | Necesidad de conservar el WebView del producto | Chromium/Vanadium como referencia técnica | Construir e integrar AOSmium mediante Vanadium → Mulch → AOSmium |
+| **Root** | No incluido | Preroot de EXTENDROM | — | Integrar Magisk en `boot.img` sin convertirlo en una app de sistema obligatoria |
+| **Privacidad y UX** | Ajustes estándar | DNS, conectividad, llamadas, spoofing, timeouts y controles AXP | Ideas puntuales de SystemUI | Reubicar y adaptar los controles a Settings/SystemUI Android 16 |
+| **Debloat** | Paquetes de la base | Objetivo de reducir superficie | — | Cuatro oleadas selectivas sin romper radio, IMS, eSIM ni Qualcomm |
+| **Kernel** | `msm-4.14` y hardening Android existente | Orden y selección de parches del track AXP | Stage 1, FORTIFY, slab, sanitización y alloc hardening | Adaptar aproximadamente 30 parches al árbol real de `sunfish` |
+| **CVE** | Parches upstream disponibles | Política de backports | Fuentes y referencias de hardening | Integrar y verificar 449 identificadores en series aplicables |
+| **Allocator** | Bionic y Scudo de Android | Objetivo de allocator reforzado | `hardened_malloc` y modelo de excepciones | Adaptar `hardened_malloc` a 64 bits, Scudo a 32 bits y VA39 |
+| **SELinux y cifrado** | Base Android/LineageOS | Requisitos de producto | Referencia de endurecimiento | Mantener SELinux enforcing y cifrado de datos |
+| **Identidad y release** | Formato de build LineageOS | Forma de empaquetado AXP Pro | — | Branding, `release-keys`, ZIP completo y flujo recovery/sideload |
+
+En forma de comparación rápida:
+
+| Área | LineageOS 23.2 limpio | AXPR23 |
+|---|---|---|
+| Servicios Google | No incluidos | microG, GSF Proxy y Phonesky |
+| Aplicaciones | Conjunto estándar de LineageOS | Aurora, Droidify, Fossify, Jelly y OpenEUICC |
+| WebView | Proveedor de LineageOS | AOSmium del proyecto como proveedor seleccionado |
+| Root | No incluido | Magisk preroot en `boot.img` |
+| Ajustes | Opciones estándar | Controles de DNS, conectividad, ubicación, llamadas y privacidad de AXP |
+| Debloat | Paquetes estándar de la base | Limpieza selectiva adaptada a microG y `sunfish` |
+| Kernel | `msm-4.14` de LineageOS | Stage 1, FORTIFY, backports CVE y configuración reforzada |
+| Allocator | Bionic estándar | `hardened_malloc` para 64 bits y Scudo para 32 bits |
+| Identidad | Build LineageOS habitual | Producto PRO firmado con `release-keys` |
+| Actualizaciones | Nightlies de LineageOS | ZIP completo por recovery/sideload |
+
+AXPR mantiene la base de dispositivo de LineageOS y mejora sus capas de
+producto, privacidad y hardening. No es una copia literal de GrapheneOS ni una
+continuación oficial de AXP.OS: es una adaptación bastante directa de AXP.OS
+Pro, con las mejoras y correcciones necesarias para Android 16.
---
-## 6. Settings and toggles (reference)
+## 6. Estado de AXPR23 y última build
-Same features as **§2.4**; quick lookup by menu area.
+AXPR23 está **EOL / archivada**. LineageOS dejó de publicar nuevas builds de
+Android 16 para `sunfish` al comenzar la línea Android 17. No habrá nuevas
+features ni nuevas builds AXPR23; se conserva como referencia y rollback.
-These appear in **Settings** / **Developer options** (exact menus follow AOSP
-Lineage layout). Names may be localized.
+- **Build:** `AXP-Resurection-Pro-23.2-20261025-sunfish.zip`
+- **Base:** LineageOS 23.2 / Android 16
+- **SHA-256:** `9d205fd3cc945438dfff3a23b129bf88516c7654f5922bc2ee4d478f9485eef0`
+- **Siguiente línea:** AXPR24 sobre LineageOS 24 / Android 17
-### 6.1 Privacy and network
+AXPR24 no es una actualización automática de AXPR23. Es una nueva adaptación
+que debe volver a validar aplicaciones, WebView, allocator, kernel y packaging
+contra Android 17.
-| Feature | What it does |
-|---------|----------------|
-| **Private DNS providers** | Extended preset list (AdGuard, Mullvad, Quad9, Cloudflare, Google DoT, regional providers, etc.) in addition to Lineage defaults |
-| **Internet connectivity check** | Configurable connectivity validation (AXP-style behaviour; no dependency on AXP-owned servers) |
-| **Force disable SUPL** | When enabled, disables SUPL-assisted GNSS (stricter location privacy) |
-| **Stop extra location assistance** | When enabled, reduces extra Qualcomm / XTRA-style assistance daemons |
+## Descarga
-### 6.2 Security and developer
+La última build publicada es un ZIP completo para el Google Pixel 4a
+(`sunfish`), firmado con las claves de release del proyecto:
-| Feature | What it does |
-|---------|----------------|
-| **Signature spoofing** | Developer option to allow microG / selected apps to spoof signatures (restricted API) |
-| **Call recording unlock** | Toggle to allow call recording where supported (AXP / EXTENDROM class) |
-| **FLAG_SECURE override** | Optional global override for screen capture blocking (default **off**) |
-| **Native debug / ptrace** | `persist.native_debug` — when enabled, relaxes ptrace scope for debugging (default off) |
-| **Disable app** | Classic **disable** for user apps restored alongside Android’s archive behaviour |
+[**Descargar AXPR23 — AXP.OS sobre LineageOS 23 para Pixel 4a**](https://vbits.me/download/284/)
-### 6.3 Timeouts (Security / connected devices)
+El botón usa Download Monitor y conserva el contador dinámico de descargas.
-| Feature | What it does |
-|---------|----------------|
-| **Automatic reboot** | Reboot after configurable idle time |
-| **Bluetooth timeout** | Turn off Bluetooth after inactivity |
-| **Wi‑Fi timeout** | Turn off Wi‑Fi after inactivity |
+- **Build:** `AXP-Resurection-Pro-23.2-20261025-sunfish.zip`
+- **Tamaño:** 1.397.472.560 bytes (1,30 GiB)
+- **MD5:** `23378f132ac8b1f53606183451715a9c`
+- **SHA-256:** `9d205fd3cc945438dfff3a23b129bf88516c7654f5922bc2ee4d478f9485eef0`
+- **Instalación:** ZIP completo mediante recovery/sideload. Haz una copia de
+ seguridad antes de instalarlo.
-### 6.4 Branding
+## Créditos
-- **Build type:** Lineage **PRO** channel naming (`AXP Resurrection Pro` product)
-- **Release keys** (not `test-keys`) on shipped builds
-
----
-
-## 7. System surface changes (deblob summary)
-
-Full package-level detail is in **§2.5**. Short overview:
-
-AXP Resurrection Pro **selectively removes** components that AXP Pro omitted or
-that are unnecessary on a microG device. This is **not** a blind “remove
-everything Google” script.
-
-**Typical removals (by category):**
-
-| Category | Examples of what is removed or not shipped |
-|----------|---------------------------------------------|
-| **Google EUICC / LPA** | Google’s eUICC app (OpenEUICC replaces user-facing LPA) |
-| **Carrier / RCS Google** | Selected Google carrier and RCS packages not needed for microG |
-| **Lineage maintenance tools on device** | Interactive shells and convenience packages on **system** that AXP Pro did not ship (e.g. `bash`, `htop`, `nano`, `unrar`, Lineage `addon.d` helper) |
-| **Lab / debug commands** | Further debug-only binaries trimmed in the full “wave 4” product |
-
-**Retained (examples):** IMS, telephony services, Wi‑Fi / cellular stack,
-Widevine, camera, Qualcomm services required for daily use, `curl` / `vim` where
-AXP Pro kept them.
-
-**Product partition:** OpenSSH **binaries** are not shipped; an **`init.openssh.rc`
-stub** may remain (AXP Pro parity) without shipping `sshd` on product.
-
----
-
-## 8. Kernel and memory hardening
-
-**Kernel version line:** Linux **4.14** (Qualcomm msm-4.14), as shipped by
-Lineage for Pixel 4a — **not** upgraded to a GKI 6.x kernel.
-
-### 7.1 Already strong on Lineage (kept)
-
-Examples: Clang CFI, shadow call stack, stack protector, KASLR, PAN, hardened
-usercopy, restricted perf events, and related options in the upstream sunfish
-defconfig.
-
-### 7.2 GrapheneOS-derived Stage 1 pack (30 patches)
-
-Backported **GrapheneOS / Android Hardening** series via the AXP kernel patch
-collection, including:
-
-| Series | Purpose (summary) |
-|--------|-------------------|
-| **Deny USB** | Optional hardening around USB attack surface |
-| **allocsize** | Allocation size hardening helpers |
-| **bugon** | `ksize` / BUG_ON integrity checks |
-| **fortify** | Intra-object FORTIFY helpers in kernel |
-| **misc** | Miscellaneous hardening commits |
-| **random** | Brk / argument randomization |
-| **ro** | `__ro_after_init` and related read-only data hardening |
-| **sanitize** | Page / slab sanitization helpers |
-| **slab** | SLAB hardening companions |
-| **unused** | Removal of unused kernel API noise |
-
-**Kernel config enabled with this stack (examples):**
-
-- `CONFIG_FORTIFY_SOURCE`
-- `CONFIG_INIT_ON_ALLOC_DEFAULT_ON` / `CONFIG_INIT_ON_FREE_DEFAULT_ON`
-- `CONFIG_SECURITY_DMESG_RESTRICT`
-- `CONFIG_SLAB_HARDENED`
-- `CONFIG_SLAB_FREELIST_RANDOM` (US-S5-1 class)
-- **Yama LSM not enabled** (compat with apps using self-ptrace; matches classic AXP kernel posture)
-
-**Not ported:** GrapheneOS **location lockdown** kernel policy (this ROM keeps
-Lineage-style location behaviour and precision).
-
-### 7.3 Userspace allocator
-
-- **hardened_malloc** (64-bit) integrated into **bionic** for eligible processes,
- with exclusions for graphics / camera stacks where required for stability.
-
-### 7.4 CVE backports
-
-The ROM applies a large set of **Linux kernel CVE backports** on top of the
-4.14 sunfish kernel, from the same curated lists used in AXP / Divest-style
-trees:
-
-| Set | Count | Notes |
-|-----|-------|--------|
-| **Primary backport set** | **429** patch entries | Stable “clean offset” apply list |
-| **High-offset batches 1–3** | **27** extra patch entries | Larger backports with relaxed offset matching |
-| **Unique CVE identifiers (combined)** | **449** | Deduplicated ids in the list file below |
-
-**Full CVE identifier list:** [`kernel-cve-backport-list.txt`](kernel-cve-backport-list.txt)
-(one CVE id per line, sorted).
-
-**Special cases (not in the 449 list as-is):**
-
-- **CVE-2022-49980** — raw one-liner backport is **denied**; a **lock-scope /
- UAF companion patch** is applied instead (USB subsystem stability).
-- Additional CVE paths may appear on a **denylist** when they conflict with
- sunfish drivers (Wi‑Fi, audio, etc.); those are skipped intentionally.
-
-Platform **Android security patch** level follows **LineageOS merges** (e.g.
-`2026-09-01` on recent builds). **Vendor** (bootloader/modem) patch level
-remains on the **2023-09-05** line appropriate for Pixel 4a vendor blobs.
-
----
-
-## 9. Verified Boot and signing
-
-- Builds are signed with **project release keys** (`release-keys`).
-- **AVB** metadata uses the project key; **verification flags** suit an
- **unlocked** bootloader with Magisk (not a locked retail verified-boot product).
-- **Do not** downgrade vendor security patch below the ROM’s minimum without
- expecting install blocks or encryption init failures.
-
----
-
-## 10. Installation (summary)
-
-1. **Unlock bootloader** (erases data).
-2. Flash **boot** + **dtbo** from the **same** ROM package if you are not already
- on this ROM’s recovery (Pixel 4a uses recovery-in-boot).
-3. Boot **recovery** → **Factory reset / format data**.
-4. **Apply update → ADB sideload** the full `AXP-Resurrection-Pro-*.zip`.
-5. Reboot, complete setup, then **Magisk** first-time APK install as above.
-
-Use the **full OTA zip**; flashing only `boot.img` does not update system,
-microG, or apps.
-
----
-
-## 11. What this ROM does **not** promise
-
-- No official Google certification or Play Protect “device integrity” guarantee.
-- No bundled **Play Integrity Fix** or banking modules.
-- No **AXP.OS** online update servers or external market repositories.
-- No **NeoLauncher** (Lineage Launcher3 remains).
-- No **KernelSU** in the standard build.
-- **Banking apps** may work in work profiles or Island; that is **app-specific**
- and not a ship guarantee.
-
----
-
-## 12. Credits and lineage
-
-- **LineageOS** — base operating system and device support.
-- **microG project** — free Google services reimplementation.
-- **Magisk** (topjohnwu) — root framework.
-- **AXP.OS / EXTENDROM** — product philosophy, preroot Magisk packaging, and
- many userspace behaviours cloned on this port.
-- **GrapheneOS / Android Hardening** — kernel patch series (via AXP kernel
- patch collection).
-- **Community apps:** Aurora, Droidify, Fossify, OpenEUICC, Jelly, etc.
-
-AXP Resurrection Pro is an **independent community port**. It is not an official
-LineageOS, Google, or AXP.OS GmbH product.
-
----
-
-## 13. Document history
-
-| Version | Date | Notes |
-|---------|------|--------|
-| 1.1 | 2026-10-11 | Added **§2** delta vs LineageOS (features, removals, framework) |
-| 1.0 | 2026-10-11 | Initial public feature list for `23.2-20261008` generation |
-
-For maintainers: internal engineering docs remain separate under `docs/` in the
-source repository.
+AXPR23 se apoya en el trabajo de **AXP.OS / EXTENDROM**, **LineageOS**,
+**microG**, **GrapheneOS / Android Hardening**, **Vanadium / Mulch / AOSmium**,
+Magisk, OpenEUICC y los proyectos de las aplicaciones integradas. Es una
+adaptación comunitaria independiente para `sunfish`.
diff --git a/modules/intellirat/README.md b/modules/intellirat/README.md
new file mode 100644
index 0000000..b86864e
--- /dev/null
+++ b/modules/intellirat/README.md
@@ -0,0 +1,62 @@
+# IntelliRAT v0.2
+
+Systemless Magisk controller for automatic, reversible Android 16
+network-profile changes. It does not modify `/system`, `/system_ext`, `/vendor`,
+RIL, modem firmware or framework.
+
+## Scope
+
+- Backend: Android 16 `cmd phone set-allowed-network-types-for-users`.
+- Target: one slot at a time, default slot `0`.
+- HIGH: LTE + UMTS/HSPA + GSM families.
+- LOW: UMTS/HSPA + GSM families.
+- Safety: capture/restore, active-call refusal, result verification and event log.
+- No Xposed or Vector. The companion APK source is included under `app/`; the
+ module zip includes it only after the AOSP app build produces
+ `system/app/IntelliRAT/IntelliRAT.apk`.
+
+The binary masks are derived from the LOS23/A16 `TelephonyManager` constants:
+network type bits are `1 << (NETWORK_TYPE - 1)`, with LTE CA normalized to LTE.
+They must be treated as target-build data, not a universal Qualcomm constant.
+
+## Install on GOLDEN only
+
+Do not install this in the production ROM or on LAB yet. Build a Magisk module
+zip with `scripts/build-intellirat-module.sh`, install it on GOLDEN with a test SIM, and keep the
+known-good ROM available. GOLDEN now detects the inserted test SIM as
+`LOADED` on `SIMon mobile`.
+
+After Magisk loads the module:
+
+```text
+su -g 2000 -G 2000 -c /system/bin/intellirat status 0
+su -g 2000 -G 2000 -c /system/bin/intellirat capture 0
+su -g 2000 -G 2000 -c /system/bin/intellirat --dry-run high 0
+su -g 2000 -G 2000 -c /system/bin/intellirat high 0
+su -g 2000 -G 2000 -c /system/bin/intellirat status 0
+su -g 2000 -G 2000 -c /system/bin/intellirat low 0
+su -g 2000 -G 2000 -c /system/bin/intellirat restore 0
+```
+
+`capture` must be run before a transition. `restore` uses the captured user
+mask and does not guess a stock mode. `events.log` is stored under
+`/data/local/tmp/intellirat/`. The prototype uses the root-owned `shell` group
+for this small network-mask state because MagiskSU drops DAC capabilities for
+`su` commands; no credentials or private data are stored.
+
+When the companion APK is present, launch **IntelliRAT** from the app drawer.
+It shows the current backend result, slot selector, automatic toggle, delay,
+foreground-package exceptions, capture/HIGH/LOW/restore buttons and a
+persistent status notification with quick actions. The automatic policy uses
+lock delay, unlock restoration and a safe call guard.
+
+## Not implemented yet
+
+- traffic protection
+- multi-SIM/eSIM UI and subscription discovery
+- Quick Settings tile
+
+The current automatic policy is deliberately limited to Keyguard lock state, a
+configurable delay and explicit foreground-package exceptions. Wi-Fi, battery,
+traffic and multi-SIM rules remain disabled until their real device behavior is
+specified and tested with calls, IMS, SMS, data and signal loss.
diff --git a/modules/intellirat/app/Android.bp b/modules/intellirat/app/Android.bp
new file mode 100644
index 0000000..1609160
--- /dev/null
+++ b/modules/intellirat/app/Android.bp
@@ -0,0 +1,10 @@
+android_app {
+ name: "IntelliRAT",
+ srcs: ["src/**/*.java"],
+ manifest: "AndroidManifest.xml",
+ certificate: "platform",
+ platform_apis: true,
+ optimize: {
+ proguard_flags_files: ["proguard.flags"],
+ },
+}
diff --git a/modules/intellirat/app/AndroidManifest.xml b/modules/intellirat/app/AndroidManifest.xml
new file mode 100644
index 0000000..872cd06
--- /dev/null
+++ b/modules/intellirat/app/AndroidManifest.xml
@@ -0,0 +1,59 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/modules/intellirat/app/proguard.flags b/modules/intellirat/app/proguard.flags
new file mode 100644
index 0000000..3b54d88
--- /dev/null
+++ b/modules/intellirat/app/proguard.flags
@@ -0,0 +1 @@
+-keep class com.axp.intellirat.** { *; }
diff --git a/modules/intellirat/app/res/drawable/ic_intellirat.xml b/modules/intellirat/app/res/drawable/ic_intellirat.xml
new file mode 100644
index 0000000..d8bf0d0
--- /dev/null
+++ b/modules/intellirat/app/res/drawable/ic_intellirat.xml
@@ -0,0 +1,9 @@
+
+
+
diff --git a/modules/intellirat/app/src/com/axp/intellirat/ActionReceiver.java b/modules/intellirat/app/src/com/axp/intellirat/ActionReceiver.java
new file mode 100644
index 0000000..b18dead
--- /dev/null
+++ b/modules/intellirat/app/src/com/axp/intellirat/ActionReceiver.java
@@ -0,0 +1,16 @@
+package com.axp.intellirat;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+
+public final class ActionReceiver extends BroadcastReceiver {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ String command = intent.getStringExtra(IntelliRatService.EXTRA_COMMAND);
+ if (command == null) return;
+ Intent service = new Intent(context, IntelliRatService.class);
+ service.putExtra(IntelliRatService.EXTRA_COMMAND, command);
+ context.startService(service);
+ }
+}
diff --git a/modules/intellirat/app/src/com/axp/intellirat/Backend.java b/modules/intellirat/app/src/com/axp/intellirat/Backend.java
new file mode 100644
index 0000000..1daea76
--- /dev/null
+++ b/modules/intellirat/app/src/com/axp/intellirat/Backend.java
@@ -0,0 +1,53 @@
+package com.axp.intellirat;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStreamReader;
+
+final class Backend {
+ static final class Result {
+ final int exitCode;
+ final String output;
+
+ Result(int exitCode, String output) {
+ this.exitCode = exitCode;
+ this.output = output;
+ }
+
+ boolean ok() {
+ return exitCode == 0;
+ }
+ }
+
+ private Backend() {}
+
+ static Result run(String command, String slot) {
+ if (!slot.matches("[0-9]+")) {
+ return new Result(2, "Invalid slot: " + slot);
+ }
+ String shellCommand = "/system/bin/intellirat " + command + " " + slot;
+ Process process = null;
+ try {
+ process = new ProcessBuilder("su", "-g", "2000", "-G", "2000", "-c", shellCommand)
+ .redirectErrorStream(true)
+ .start();
+ StringBuilder output = new StringBuilder();
+ try (BufferedReader reader = new BufferedReader(
+ new InputStreamReader(process.getInputStream()))) {
+ String line;
+ while ((line = reader.readLine()) != null) {
+ if (output.length() > 0) output.append('\n');
+ output.append(line);
+ }
+ }
+ return new Result(process.waitFor(), output.toString());
+ } catch (IOException e) {
+ return new Result(127, "Root backend unavailable: " + e.getMessage());
+ } catch (InterruptedException e) {
+ Thread.currentThread().interrupt();
+ return new Result(130, "Interrupted");
+ } finally {
+ if (process != null) process.destroy();
+ }
+ }
+}
diff --git a/modules/intellirat/app/src/com/axp/intellirat/BootReceiver.java b/modules/intellirat/app/src/com/axp/intellirat/BootReceiver.java
new file mode 100644
index 0000000..9969061
--- /dev/null
+++ b/modules/intellirat/app/src/com/axp/intellirat/BootReceiver.java
@@ -0,0 +1,22 @@
+package com.axp.intellirat;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.os.Build;
+
+public final class BootReceiver extends BroadcastReceiver {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ if (!context.getSharedPreferences(IntelliRatService.PREFS, Context.MODE_PRIVATE)
+ .getBoolean(IntelliRatService.AUTO_ENABLED, false)) {
+ return;
+ }
+ Intent service = new Intent(context, IntelliRatService.class);
+ if (Build.VERSION.SDK_INT >= 26) {
+ context.startForegroundService(service);
+ } else {
+ context.startService(service);
+ }
+ }
+}
diff --git a/modules/intellirat/app/src/com/axp/intellirat/IntelliRatService.java b/modules/intellirat/app/src/com/axp/intellirat/IntelliRatService.java
new file mode 100644
index 0000000..6e874bb
--- /dev/null
+++ b/modules/intellirat/app/src/com/axp/intellirat/IntelliRatService.java
@@ -0,0 +1,438 @@
+package com.axp.intellirat;
+
+import android.app.Notification;
+import android.app.NotificationChannel;
+import android.app.NotificationManager;
+import android.app.PendingIntent;
+import android.app.Service;
+import android.content.Context;
+import android.content.Intent;
+import android.app.KeyguardManager;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Looper;
+import android.os.Build;
+import android.app.usage.UsageEvents;
+import android.app.usage.UsageStatsManager;
+import android.content.BroadcastReceiver;
+import android.content.IntentFilter;
+import android.os.BatteryManager;
+import android.net.ConnectivityManager;
+import android.net.NetworkCapabilities;
+
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+
+public final class IntelliRatService extends Service {
+ static final String ACTION_COMMAND = "com.axp.intellirat.COMMAND";
+ static final String EXTRA_COMMAND = "command";
+ static final String ACTION_AUTO_CONFIG = "com.axp.intellirat.AUTO_CONFIG";
+ static final String EXTRA_AUTO_ENABLED = "automatic_enabled";
+ static final String EXTRA_AUTO_DELAY_SECONDS = "automatic_delay_seconds";
+ static final String EXTRA_AUTO_UNLOCK_DELAY_SECONDS = "automatic_unlock_delay_seconds";
+ static final String EXTRA_AUTO_EXCEPTIONS = "automatic_exceptions";
+ static final String PREFS = "intellirat";
+ static final String AUTO_ENABLED = "automatic_enabled";
+ static final String AUTO_DELAY_SECONDS = "automatic_delay_seconds";
+ static final String AUTO_UNLOCK_DELAY_SECONDS = "automatic_unlock_delay_seconds";
+ static final String AUTO_EXCEPTIONS = "automatic_exceptions";
+ static final String CHANNEL = "intellirat_status";
+ private static final int NOTIFICATION_ID = 4103;
+ private static final int RETRY_DELAY_SECONDS = 30;
+ private final ExecutorService executor = Executors.newSingleThreadExecutor();
+ private final Handler handler = new Handler(Looper.getMainLooper());
+ private Runnable lowTask;
+ private Runnable unlockTask;
+ private Runnable foregroundPoll;
+ private Runnable lockPoll;
+ private Runnable dataRecovery;
+ private int dataRecoveryAttempt;
+ private boolean charging;
+ private final BroadcastReceiver chargingReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ boolean nowCharging = isCharging();
+ if (nowCharging != charging) {
+ charging = nowCharging;
+ onChargingChanged();
+ }
+ }
+ };
+ private boolean keyguardLocked;
+ private boolean foregroundExceptionActive;
+
+ @Override
+ public void onCreate() {
+ super.onCreate();
+ createChannel();
+ startForeground(NOTIFICATION_ID, notification("Starting root backend..."));
+ keyguardLocked = isDeviceKeyguardLocked();
+ charging = isCharging();
+ IntentFilter powerFilter = new IntentFilter();
+ powerFilter.addAction(Intent.ACTION_POWER_CONNECTED);
+ powerFilter.addAction(Intent.ACTION_POWER_DISCONNECTED);
+ registerReceiver(chargingReceiver, powerFilter);
+ refresh("status");
+ if (isAutomaticEnabled()) {
+ configureAutomatic(true,
+ getSharedPreferences(PREFS, MODE_PRIVATE)
+ .getInt(AUTO_DELAY_SECONDS, 30),
+ getSharedPreferences(PREFS, MODE_PRIVATE)
+ .getInt(AUTO_UNLOCK_DELAY_SECONDS, 0),
+ getSharedPreferences(PREFS, MODE_PRIVATE)
+ .getString(AUTO_EXCEPTIONS, ""));
+ }
+ scheduleForegroundPoll();
+ scheduleLockPoll();
+ }
+
+ @Override
+ public int onStartCommand(Intent intent, int flags, int startId) {
+ if (intent != null && ACTION_AUTO_CONFIG.equals(intent.getAction())) {
+ configureAutomatic(
+ intent.getBooleanExtra(EXTRA_AUTO_ENABLED, false),
+ intent.getIntExtra(EXTRA_AUTO_DELAY_SECONDS, 30),
+ intent.getIntExtra(EXTRA_AUTO_UNLOCK_DELAY_SECONDS, 0),
+ intent.getStringExtra(EXTRA_AUTO_EXCEPTIONS));
+ } else if (intent != null && intent.hasExtra(EXTRA_COMMAND)) {
+ enqueueTransition(intent.getStringExtra(EXTRA_COMMAND), "Manual transition");
+ }
+ return START_STICKY;
+ }
+
+ @Override
+ public void onDestroy() {
+ cancelLow();
+ cancelUnlock();
+ cancelForegroundPoll();
+ cancelLockPoll();
+ cancelDataRecovery();
+ unregisterReceiver(chargingReceiver);
+ executor.shutdownNow();
+ super.onDestroy();
+ }
+
+ @Override
+ public IBinder onBind(Intent intent) {
+ return null;
+ }
+
+ private void configureAutomatic(boolean enabled, int delaySeconds, int unlockDelaySeconds,
+ String exceptions) {
+ if (delaySeconds < 5 || delaySeconds > 3600) {
+ updateNotification("Automatic policy rejected: delay must be 5-3600 seconds");
+ return;
+ }
+ if (unlockDelaySeconds < 0 || unlockDelaySeconds > 3600) {
+ updateNotification("Automatic policy rejected: unlock delay must be 0-3600 seconds");
+ return;
+ }
+ getSharedPreferences(PREFS, MODE_PRIVATE).edit()
+ .putBoolean(AUTO_ENABLED, enabled)
+ .putInt(AUTO_DELAY_SECONDS, delaySeconds)
+ .putInt(AUTO_UNLOCK_DELAY_SECONDS, unlockDelaySeconds)
+ .putString(AUTO_EXCEPTIONS, exceptions == null ? "" : exceptions)
+ .apply();
+ cancelLow();
+ cancelUnlock();
+ if (!enabled) {
+ cancelForegroundPoll();
+ cancelLockPoll();
+ cancelDataRecovery();
+ enqueueTransition("high", "Automatic policy disabled");
+ return;
+ }
+ scheduleLockPoll();
+ scheduleForegroundPoll();
+ executor.execute(() -> {
+ if (charging) {
+ updateNotification("Charging: HIGH/LTE forced");
+ runTransition("high", "Charging forced HIGH");
+ return;
+ }
+ updateNotification("Automatic policy enabled: LOW after lock "
+ + delaySeconds + "s, HIGH after unlock " + unlockDelaySeconds + "s");
+ keyguardLocked = isDeviceKeyguardLocked();
+ if (!keyguardLocked) {
+ scheduleUnlockHigh();
+ } else if (isForegroundException()) {
+ runTransition("high", "Streaming exception kept HIGH");
+ } else {
+ scheduleLow();
+ }
+ });
+ }
+
+ private void scheduleLow() {
+ cancelLow();
+ if (!isAutomaticEnabled() || !keyguardLocked || charging) return;
+ int delay = getSharedPreferences(PREFS, MODE_PRIVATE)
+ .getInt(AUTO_DELAY_SECONDS, 30);
+ lowTask = () -> {
+ if (isAutomaticEnabled() && keyguardLocked) {
+ enqueueTransition("low", "Automatic locked LOW");
+ }
+ };
+ handler.postDelayed(lowTask, delay * 1000L);
+ updateNotification("Locked: LOW in " + delay + "s");
+ }
+
+ private void cancelLow() {
+ if (lowTask != null) {
+ handler.removeCallbacks(lowTask);
+ lowTask = null;
+ }
+ }
+
+ private void scheduleUnlockHigh() {
+ cancelUnlock();
+ if (!isAutomaticEnabled() || keyguardLocked || charging) return;
+ int delay = getSharedPreferences(PREFS, MODE_PRIVATE)
+ .getInt(AUTO_UNLOCK_DELAY_SECONDS, 0);
+ unlockTask = () -> {
+ if (isAutomaticEnabled() && !keyguardLocked) {
+ enqueueTransition("high", "Automatic unlocked HIGH");
+ }
+ };
+ if (delay == 0) {
+ handler.post(unlockTask);
+ } else {
+ handler.postDelayed(unlockTask, delay * 1000L);
+ updateNotification("Unlocked: HIGH in " + delay + "s");
+ }
+ }
+
+ private void cancelUnlock() {
+ if (unlockTask != null) {
+ handler.removeCallbacks(unlockTask);
+ unlockTask = null;
+ }
+ }
+
+ private void scheduleLockPoll() {
+ cancelLockPoll();
+ if (!isAutomaticEnabled()) return;
+ lockPoll = () -> {
+ if (!isAutomaticEnabled()) return;
+ boolean locked = isDeviceKeyguardLocked();
+ if (locked != keyguardLocked) {
+ keyguardLocked = locked;
+ cancelLow();
+ if (locked) {
+ if (foregroundExceptionActive) {
+ enqueueTransition("high", "Streaming exception kept HIGH");
+ } else {
+ scheduleLow();
+ }
+ } else {
+ scheduleUnlockHigh();
+ }
+ }
+ handler.postDelayed(lockPoll, 1000L);
+ };
+ handler.post(lockPoll);
+ }
+
+ private void cancelLockPoll() {
+ if (lockPoll != null) {
+ handler.removeCallbacks(lockPoll);
+ lockPoll = null;
+ }
+ }
+
+ private void scheduleDataRecovery() {
+ cancelDataRecovery();
+ dataRecoveryAttempt = 0;
+ dataRecovery = () -> {
+ if (!isAutomaticEnabled() || !keyguardLocked || hasValidatedData()
+ || dataRecoveryAttempt >= 3) {
+ return;
+ }
+ int attempt = ++dataRecoveryAttempt;
+ executor.execute(() -> {
+ Backend.Result result = Backend.run("low", "0");
+ updateNotification(result.ok()
+ ? "LOW data recovery attempt " + attempt + ": " + result.output
+ : "LOW data recovery failed: " + result.output);
+ handler.postDelayed(dataRecovery, attempt * 15000L);
+ });
+ };
+ handler.postDelayed(dataRecovery, 10000L);
+ }
+
+ private void cancelDataRecovery() {
+ if (dataRecovery != null) {
+ handler.removeCallbacks(dataRecovery);
+ dataRecovery = null;
+ }
+ dataRecoveryAttempt = 0;
+ }
+
+ private boolean hasValidatedData() {
+ ConnectivityManager connectivity =
+ (ConnectivityManager) getSystemService(CONNECTIVITY_SERVICE);
+ NetworkCapabilities capabilities = connectivity.getNetworkCapabilities(
+ connectivity.getActiveNetwork());
+ return capabilities != null
+ && capabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_VALIDATED);
+ }
+
+ private boolean isDeviceKeyguardLocked() {
+ KeyguardManager keyguard = (KeyguardManager) getSystemService(KEYGUARD_SERVICE);
+ if (keyguard.isKeyguardLocked()) return true;
+ return Build.VERSION.SDK_INT >= 23 && keyguard.isDeviceLocked();
+ }
+
+ private boolean isCharging() {
+ Intent battery = registerReceiver(null, new IntentFilter(Intent.ACTION_BATTERY_CHANGED));
+ if (battery == null) return false;
+ int status = battery.getIntExtra(BatteryManager.EXTRA_STATUS, -1);
+ return status == BatteryManager.BATTERY_STATUS_CHARGING
+ || status == BatteryManager.BATTERY_STATUS_FULL;
+ }
+
+ private void onChargingChanged() {
+ if (!isAutomaticEnabled()) return;
+ cancelLow();
+ cancelUnlock();
+ cancelDataRecovery();
+ if (charging) {
+ enqueueTransition("high", "Charging forced HIGH");
+ updateNotification("Charging: HIGH/LTE forced");
+ } else if (keyguardLocked) {
+ scheduleLow();
+ } else {
+ scheduleUnlockHigh();
+ }
+ }
+
+ private void scheduleForegroundPoll() {
+ cancelForegroundPoll();
+ if (!isAutomaticEnabled()) return;
+ foregroundPoll = () -> {
+ if (!isAutomaticEnabled()) return;
+ if (!keyguardLocked) {
+ boolean exception = isForegroundException();
+ if (exception != foregroundExceptionActive) {
+ foregroundExceptionActive = exception;
+ if (exception) {
+ enqueueTransition("high", "Foreground exception kept HIGH");
+ }
+ }
+ }
+ handler.postDelayed(foregroundPoll, 5000L);
+ };
+ handler.post(foregroundPoll);
+ }
+
+ private void cancelForegroundPoll() {
+ if (foregroundPoll != null) {
+ handler.removeCallbacks(foregroundPoll);
+ foregroundPoll = null;
+ }
+ }
+
+ private boolean isForegroundException() {
+ String configured = getSharedPreferences(PREFS, MODE_PRIVATE)
+ .getString(AUTO_EXCEPTIONS, "");
+ if (configured.trim().isEmpty()) return false;
+ String packageName = null;
+ try {
+ UsageStatsManager usage = (UsageStatsManager) getSystemService(USAGE_STATS_SERVICE);
+ UsageEvents events = usage.queryEvents(
+ System.currentTimeMillis() - 30000L, System.currentTimeMillis());
+ UsageEvents.Event event = new UsageEvents.Event();
+ while (events != null && events.hasNextEvent()) {
+ events.getNextEvent(event);
+ if (event.getEventType() == UsageEvents.Event.MOVE_TO_FOREGROUND) {
+ packageName = event.getPackageName();
+ }
+ }
+ } catch (SecurityException ignored) {
+ return false;
+ }
+ if (packageName == null) return foregroundExceptionActive;
+ for (String candidate : configured.split("[,\\n\\r ]+")) {
+ if (candidate.trim().equals(packageName)) return true;
+ }
+ return false;
+ }
+
+ private void enqueueTransition(String command, String label) {
+ executor.execute(() -> runTransition(command, label));
+ }
+
+ private void runTransition(String command, String label) {
+ Backend.Result result = Backend.run(command, "0");
+ String text = result.ok() ? label + ": " + result.output
+ : label + " failed: " + result.output;
+ updateNotification(text);
+ if (result.ok() && "low".equals(command) && isAutomaticEnabled() && keyguardLocked) {
+ scheduleDataRecovery();
+ } else if ("high".equals(command)) {
+ cancelDataRecovery();
+ }
+ if (!result.ok() && "low".equals(command) && isAutomaticEnabled() && keyguardLocked) {
+ handler.postDelayed(() -> {
+ if (isAutomaticEnabled() && keyguardLocked) {
+ enqueueTransition("low", "Automatic locked LOW retry");
+ }
+ }, RETRY_DELAY_SECONDS * 1000L);
+ }
+ }
+
+ private void refresh(String command) {
+ executor.execute(() -> {
+ Backend.Result result = Backend.run(command, "0");
+ String text = result.ok() ? result.output : "Backend error: " + result.output;
+ updateNotification(text);
+ });
+ }
+
+ private void updateNotification(String text) {
+ ((NotificationManager) getSystemService(NOTIFICATION_SERVICE))
+ .notify(NOTIFICATION_ID, notification(text));
+ }
+
+ private boolean isAutomaticEnabled() {
+ return getSharedPreferences(PREFS, MODE_PRIVATE).getBoolean(AUTO_ENABLED, false);
+ }
+
+ private Notification notification(String text) {
+ Intent open = new Intent(this, MainActivity.class);
+ PendingIntent content = PendingIntent.getActivity(
+ this, 1, open, PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE);
+ return new Notification.Builder(this, CHANNEL)
+ .setSmallIcon(android.R.drawable.ic_menu_manage)
+ .setContentTitle("IntelliRAT")
+ .setContentText(oneLine(text))
+ .setStyle(new Notification.BigTextStyle().bigText(text))
+ .setContentIntent(content)
+ .setOngoing(true)
+ .addAction(action("HIGH", "high", 2))
+ .addAction(action("LOW", "low", 3))
+ .build();
+ }
+
+ private Notification.Action action(String title, String command, int requestCode) {
+ Intent intent = new Intent(this, ActionReceiver.class);
+ intent.setAction(ACTION_COMMAND);
+ intent.putExtra(EXTRA_COMMAND, command);
+ PendingIntent pending = PendingIntent.getBroadcast(
+ this, requestCode, intent,
+ PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE);
+ return new Notification.Action.Builder(null, title, pending).build();
+ }
+
+ private String oneLine(String text) {
+ return text.replace('\n', ' ');
+ }
+
+ private void createChannel() {
+ NotificationChannel channel = new NotificationChannel(
+ CHANNEL, "IntelliRAT status", NotificationManager.IMPORTANCE_LOW);
+ channel.setDescription("Current IntelliRAT network profile");
+ ((NotificationManager) getSystemService(NOTIFICATION_SERVICE))
+ .createNotificationChannel(channel);
+ }
+}
diff --git a/modules/intellirat/app/src/com/axp/intellirat/IntelliRatTileService.java b/modules/intellirat/app/src/com/axp/intellirat/IntelliRatTileService.java
new file mode 100644
index 0000000..7b67a85
--- /dev/null
+++ b/modules/intellirat/app/src/com/axp/intellirat/IntelliRatTileService.java
@@ -0,0 +1,66 @@
+package com.axp.intellirat;
+
+import android.service.quicksettings.Tile;
+import android.service.quicksettings.TileService;
+
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+
+public final class IntelliRatTileService extends TileService {
+ private final ExecutorService executor = Executors.newSingleThreadExecutor();
+
+ @Override
+ public void onStartListening() {
+ super.onStartListening();
+ refreshTile();
+ }
+
+ @Override
+ public void onClick() {
+ super.onClick();
+ updateTile(Tile.STATE_INACTIVE, "IntelliRAT");
+ executor.execute(() -> {
+ Backend.Result status = Backend.run("status", "0");
+ if (!status.ok()) {
+ updateTile(Tile.STATE_INACTIVE, "IntelliRAT");
+ return;
+ }
+ String command = status.output.contains("state=HIGH") ? "low" : "high";
+ Backend.Result result = Backend.run(command, "0");
+ updateTile(result.ok() && "high".equals(command)
+ ? Tile.STATE_ACTIVE : Tile.STATE_INACTIVE, "IntelliRAT");
+ });
+ }
+
+ @Override
+ public void onDestroy() {
+ executor.shutdownNow();
+ super.onDestroy();
+ }
+
+ private void refreshTile() {
+ executor.execute(() -> {
+ Backend.Result result = Backend.run("status", "0");
+ if (!result.ok()) {
+ updateTile(Tile.STATE_INACTIVE, "LOW");
+ } else if (result.output.contains("state=HIGH")) {
+ updateTile(Tile.STATE_ACTIVE, "HIGH");
+ } else if (result.output.contains("state=LOW")) {
+ updateTile(Tile.STATE_INACTIVE, "LOW");
+ } else {
+ updateTile(Tile.STATE_UNAVAILABLE, "IntelliRAT");
+ }
+ });
+ }
+
+ private void updateTile(int state, String label) {
+ getMainExecutor().execute(() -> {
+ Tile tile = getQsTile();
+ if (tile == null) return;
+ tile.setState(state);
+ tile.setLabel("IntelliRAT");
+ tile.setContentDescription("IntelliRAT " + label);
+ tile.updateTile();
+ });
+ }
+}
diff --git a/modules/intellirat/app/src/com/axp/intellirat/MainActivity.java b/modules/intellirat/app/src/com/axp/intellirat/MainActivity.java
new file mode 100644
index 0000000..62fc2c8
--- /dev/null
+++ b/modules/intellirat/app/src/com/axp/intellirat/MainActivity.java
@@ -0,0 +1,297 @@
+package com.axp.intellirat;
+
+import android.Manifest;
+import android.app.Activity;
+import android.app.AlertDialog;
+import android.app.AppOpsManager;
+import android.content.Intent;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+import android.graphics.Typeface;
+import android.os.Build;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Process;
+import android.provider.Settings;
+import android.view.Gravity;
+import android.view.View;
+import android.widget.Button;
+import android.widget.EditText;
+import android.widget.LinearLayout;
+import android.widget.ScrollView;
+import android.widget.Switch;
+import android.widget.TextView;
+import android.text.Editable;
+import android.text.TextWatcher;
+
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+public final class MainActivity extends Activity {
+ private final ExecutorService executor = Executors.newSingleThreadExecutor();
+ private TextView status;
+ private EditText slot;
+ private EditText delay;
+ private EditText unlockDelay;
+ private EditText exceptions;
+ private Switch automatic;
+ private final Handler applyHandler = new Handler(Looper.getMainLooper());
+ private final Runnable applyTask = this::configureAutomaticPolicy;
+
+ @Override
+ protected void onCreate(Bundle state) {
+ super.onCreate(state);
+ setTitle("IntelliRAT");
+ buildUi();
+ requestNotifications();
+ requestUsageAccessIfNeeded();
+ startService(new Intent(this, IntelliRatService.class));
+ runCommand("status");
+ }
+
+ @Override
+ protected void onDestroy() {
+ executor.shutdownNow();
+ applyHandler.removeCallbacks(applyTask);
+ super.onDestroy();
+ }
+
+ private void buildUi() {
+ LinearLayout root = new LinearLayout(this);
+ root.setOrientation(LinearLayout.VERTICAL);
+ root.setPadding(32, 28, 32, 28);
+
+ TextView title = new TextView(this);
+ title.setText("IntelliRAT");
+ title.setTextSize(28);
+ title.setTypeface(Typeface.DEFAULT, Typeface.BOLD);
+ root.addView(title, new LinearLayout.LayoutParams(-1, -2));
+
+ TextView subtitle = new TextView(this);
+ subtitle.setText("Android 16 reversible network profile controller");
+ subtitle.setPadding(0, 4, 0, 24);
+ root.addView(subtitle, new LinearLayout.LayoutParams(-1, -2));
+
+ LinearLayout slotRow = new LinearLayout(this);
+ slotRow.setGravity(Gravity.CENTER_VERTICAL);
+ TextView slotLabel = new TextView(this);
+ slotLabel.setText("Slot: ");
+ slotRow.addView(slotLabel, new LinearLayout.LayoutParams(-2, -2));
+ slot = new EditText(this);
+ slot.setText("0");
+ slot.setSingleLine(true);
+ slot.setInputType(2);
+ slotRow.addView(slot, new LinearLayout.LayoutParams(120, -2));
+ root.addView(slotRow, new LinearLayout.LayoutParams(-1, -2));
+
+ TextView mode = new TextView(this);
+ mode.setText("Automatic lock policy: LOW after lock delay, HIGH after unlock.");
+ mode.setPadding(0, 20, 0, 12);
+ root.addView(mode, new LinearLayout.LayoutParams(-1, -2));
+
+ TextView lockLabel = new TextView(this);
+ lockLabel.setText("Lock LOW delay (seconds)");
+ root.addView(lockLabel, new LinearLayout.LayoutParams(-1, -2));
+
+ LinearLayout policy = new LinearLayout(this);
+ policy.setGravity(Gravity.CENTER_VERTICAL);
+ automatic = new Switch(this);
+ automatic.setText("Automatic lock policy");
+ automatic.setChecked(getSharedPreferences(IntelliRatService.PREFS, MODE_PRIVATE)
+ .getBoolean(IntelliRatService.AUTO_ENABLED, false));
+ automatic.setOnCheckedChangeListener((button, checked) -> scheduleAutomaticApply());
+ policy.addView(automatic, new LinearLayout.LayoutParams(0, -2, 1));
+ delay = new EditText(this);
+ delay.setText(String.valueOf(getSharedPreferences(IntelliRatService.PREFS, MODE_PRIVATE)
+ .getInt(IntelliRatService.AUTO_DELAY_SECONDS, 30)));
+ delay.setHint("seconds");
+ delay.setSingleLine(true);
+ delay.setInputType(2);
+ policy.addView(delay, new LinearLayout.LayoutParams(220, -2));
+ addApplyWatcher(delay);
+ root.addView(policy, new LinearLayout.LayoutParams(-1, -2));
+
+ TextView unlockLabel = new TextView(this);
+ unlockLabel.setText("Unlock HIGH delay (seconds; 0 = immediate)");
+ root.addView(unlockLabel, new LinearLayout.LayoutParams(-1, -2));
+ unlockDelay = new EditText(this);
+ unlockDelay.setText(String.valueOf(getSharedPreferences(IntelliRatService.PREFS, MODE_PRIVATE)
+ .getInt(IntelliRatService.AUTO_UNLOCK_DELAY_SECONDS, 0)));
+ unlockDelay.setHint("unlock HIGH seconds (0 = immediate)");
+ unlockDelay.setSingleLine(true);
+ unlockDelay.setInputType(2);
+ addApplyWatcher(unlockDelay);
+ root.addView(unlockDelay, new LinearLayout.LayoutParams(-1, -2));
+
+ Button selectExceptions = new Button(this);
+ selectExceptions.setText("Select exception apps");
+ selectExceptions.setOnClickListener(view -> showExceptionPicker());
+ root.addView(selectExceptions, new LinearLayout.LayoutParams(-1, -2));
+
+ exceptions = new EditText(this);
+ exceptions.setHint("Exception packages, one per line (for example com.spotify.music)");
+ exceptions.setInputType(android.text.InputType.TYPE_CLASS_TEXT
+ | android.text.InputType.TYPE_TEXT_FLAG_MULTI_LINE);
+ exceptions.setMinLines(2);
+ exceptions.setText(getSharedPreferences(IntelliRatService.PREFS, MODE_PRIVATE)
+ .getString(IntelliRatService.AUTO_EXCEPTIONS, ""));
+ addApplyWatcher(exceptions);
+ root.addView(exceptions, new LinearLayout.LayoutParams(-1, -2));
+
+ LinearLayout actions = new LinearLayout(this);
+ actions.setOrientation(LinearLayout.VERTICAL);
+ addButton(actions, "Refresh status", "status");
+ addButton(actions, "HIGH LTE / 3G / 2G", "high");
+ addButton(actions, "LOW 3G / 2G", "low");
+ root.addView(actions, new LinearLayout.LayoutParams(-1, -2));
+
+ status = new TextView(this);
+ status.setTextIsSelectable(true);
+ status.setTypeface(Typeface.MONOSPACE);
+ status.setPadding(0, 24, 0, 0);
+ status.setText("Waiting for root backend...");
+ ScrollView scroll = new ScrollView(this);
+ scroll.addView(status);
+ root.addView(scroll, new LinearLayout.LayoutParams(-1, 0, 1));
+
+ setContentView(root);
+ }
+
+ private void addButton(LinearLayout parent, String label, String command) {
+ Button button = new Button(this);
+ button.setText(label);
+ button.setOnClickListener(view -> runCommand(command));
+ parent.addView(button, new LinearLayout.LayoutParams(-1, -2));
+ }
+
+ private void runCommand(String command) {
+ String selectedSlot = slot == null ? "0" : slot.getText().toString().trim();
+ status.setText("Running: " + command + " slot " + selectedSlot + "...");
+ executor.execute(() -> {
+ Backend.Result result = Backend.run(command, selectedSlot);
+ runOnUiThread(() -> status.setText(
+ (result.ok() ? "OK\n" : "FAILED (" + result.exitCode + ")\n")
+ + result.output));
+ });
+ }
+
+ private void configureAutomaticPolicy() {
+ String value = delay == null ? "30" : delay.getText().toString().trim();
+ int seconds;
+ try {
+ seconds = Integer.parseInt(value);
+ } catch (NumberFormatException e) {
+ status.setText("Invalid delay: use 5-3600 seconds");
+ return;
+ }
+ if (seconds < 5 || seconds > 3600) {
+ status.setText("Invalid delay: use 5-3600 seconds");
+ return;
+ }
+ Intent service = new Intent(this, IntelliRatService.class)
+ .setAction(IntelliRatService.ACTION_AUTO_CONFIG)
+ .putExtra(IntelliRatService.EXTRA_AUTO_ENABLED,
+ automatic != null && automatic.isChecked())
+ .putExtra(IntelliRatService.EXTRA_AUTO_DELAY_SECONDS, seconds)
+ .putExtra(IntelliRatService.EXTRA_AUTO_UNLOCK_DELAY_SECONDS,
+ parseUnlockDelay())
+ .putExtra(IntelliRatService.EXTRA_AUTO_EXCEPTIONS,
+ exceptions == null ? "" : exceptions.getText().toString());
+ startService(service);
+ status.setText("Applying automatic policy...");
+ }
+
+ private void addApplyWatcher(EditText field) {
+ field.addTextChangedListener(new TextWatcher() {
+ @Override public void beforeTextChanged(CharSequence s, int start, int count, int after) { }
+ @Override public void onTextChanged(CharSequence s, int start, int before, int count) {
+ scheduleAutomaticApply();
+ }
+ @Override public void afterTextChanged(Editable s) { }
+ });
+ }
+
+ private void scheduleAutomaticApply() {
+ applyHandler.removeCallbacks(applyTask);
+ applyHandler.postDelayed(applyTask, 600L);
+ }
+
+ private void requestUsageAccessIfNeeded() {
+ AppOpsManager appOps = (AppOpsManager) getSystemService(APP_OPS_SERVICE);
+ int mode = appOps.checkOpNoThrow(AppOpsManager.OPSTR_GET_USAGE_STATS,
+ Process.myUid(), getPackageName());
+ if (mode != AppOpsManager.MODE_ALLOWED) {
+ startActivity(new Intent(Settings.ACTION_USAGE_ACCESS_SETTINGS));
+ }
+ }
+
+ private int parseUnlockDelay() {
+ String value = unlockDelay == null ? "0" : unlockDelay.getText().toString().trim();
+ int seconds;
+ try {
+ seconds = Integer.parseInt(value);
+ } catch (NumberFormatException e) {
+ return 0;
+ }
+ return Math.max(0, Math.min(3600, seconds));
+ }
+
+ private void showExceptionPicker() {
+ PackageManager packageManager = getPackageManager();
+ List applications = new ArrayList<>();
+ for (ApplicationInfo application : packageManager.getInstalledApplications(
+ PackageManager.GET_META_DATA)) {
+ if ((application.flags & ApplicationInfo.FLAG_SYSTEM) != 0
+ || application.packageName.equals("android")
+ || application.packageName.equals(getPackageName())) {
+ continue;
+ }
+ applications.add(application);
+ }
+ Collections.sort(applications, Comparator.comparing(application ->
+ packageManager.getApplicationLabel(application).toString().toLowerCase()));
+ String[] labels = new String[applications.size()];
+ boolean[] checked = new boolean[applications.size()];
+ Set selected = new HashSet<>();
+ for (String value : exceptions.getText().toString().split("[,\\n\\r ]+")) {
+ if (!value.trim().isEmpty()) selected.add(value.trim());
+ }
+ for (int i = 0; i < applications.size(); i++) {
+ ApplicationInfo application = applications.get(i);
+ labels[i] = packageManager.getApplicationLabel(application).toString();
+ checked[i] = selected.contains(application.packageName);
+ }
+ new AlertDialog.Builder(this)
+ .setTitle("Keep HIGH for selected apps")
+ .setMultiChoiceItems(labels, checked, (dialog, which, isChecked) -> {
+ checked[which] = isChecked;
+ })
+ .setPositiveButton("Use selected", (dialog, which) -> {
+ StringBuilder value = new StringBuilder();
+ for (int i = 0; i < applications.size(); i++) {
+ if (checked[i]) {
+ if (value.length() > 0) value.append('\n');
+ value.append(applications.get(i).packageName);
+ }
+ }
+ exceptions.setText(value.toString());
+ })
+ .setNegativeButton("Cancel", null)
+ .show();
+ }
+
+ private void requestNotifications() {
+ if (Build.VERSION.SDK_INT >= 33
+ && checkSelfPermission(Manifest.permission.POST_NOTIFICATIONS)
+ != PackageManager.PERMISSION_GRANTED) {
+ requestPermissions(new String[]{Manifest.permission.POST_NOTIFICATIONS}, 7);
+ }
+ }
+}
diff --git a/modules/intellirat/config/default.conf b/modules/intellirat/config/default.conf
new file mode 100644
index 0000000..469c56e
--- /dev/null
+++ b/modules/intellirat/config/default.conf
@@ -0,0 +1,6 @@
+# IntelliRAT v0.1 manual prototype.
+# Masks are Android TelephonyManager bitmasks encoded as 20-bit binary strings.
+# HIGH = LTE + UMTS/HSPA + GSM families. LOW = UMTS/HSPA + GSM families.
+TARGET_SLOT=0
+HIGH_MASK=00011101001110000111
+LOW_MASK=00011100001110000111
diff --git a/modules/intellirat/customize.sh b/modules/intellirat/customize.sh
new file mode 100755
index 0000000..8929bbe
--- /dev/null
+++ b/modules/intellirat/customize.sh
@@ -0,0 +1,57 @@
+#!/system/bin/sh
+
+INSTALL_LOG=/data/local/tmp/intellirat-install.log
+INSTALL_APK=/data/local/tmp/intellirat-install.apk
+
+printf '%s phase=customize start\n' "$(date -u '+%Y-%m-%dT%H:%M:%SZ')" >> "$INSTALL_LOG"
+set_perm "$MODPATH/service.sh" 0 0 0755
+set_perm "$MODPATH/uninstall.sh" 0 0 0755
+set_perm "$MODPATH/system/bin/intellirat" 0 0 0755
+set_perm "$MODPATH/payload/IntelliRAT.apk" 0 0 0644
+
+package_ready=0
+attempt=0
+while [ "$attempt" -lt 30 ]; do
+ if cmd package path android >/dev/null 2>&1; then
+ package_ready=1
+ break
+ fi
+ attempt=$((attempt + 1))
+ sleep 1
+done
+
+if [ "$package_ready" != 1 ]; then
+ printf '%s phase=customize error=package_service_timeout\n' \
+ "$(date -u '+%Y-%m-%dT%H:%M:%SZ')" >> "$INSTALL_LOG"
+ abort "IntelliRAT: Android package service did not become available"
+fi
+
+if ! cp "$MODPATH/payload/IntelliRAT.apk" "$INSTALL_APK"; then
+ printf '%s phase=customize error=copy_failed\n' \
+ "$(date -u '+%Y-%m-%dT%H:%M:%SZ')" >> "$INSTALL_LOG"
+ abort "IntelliRAT: could not stage APK for PackageManager"
+fi
+chmod 0644 "$INSTALL_APK"
+install_result=$(pm install -r --user 0 "$INSTALL_APK" 2>&1 || true)
+printf '%s phase=customize result=%s\n' \
+ "$(date -u '+%Y-%m-%dT%H:%M:%SZ')" "$install_result" >> "$INSTALL_LOG"
+rm -f "$INSTALL_APK"
+case "$install_result" in
+ *Success*) ;;
+ *) abort "IntelliRAT APK installation failed: $install_result" ;;
+esac
+
+for user in $(cmd user list 2>/dev/null | awk -F '[{}:]' '/UserInfo/ {print $2}'); do
+ if [ "$user" != 0 ]; then
+ pm uninstall --user "$user" com.axp.intellirat >/dev/null 2>&1 || true
+ fi
+done
+
+TILE='custom(com.axp.intellirat/.IntelliRatTileService)'
+tiles=$(settings get secure sysui_qs_tiles 2>/dev/null || true)
+case ",$tiles," in
+ *",$TILE,"*) ;;
+ *) settings put secure sysui_qs_tiles "$tiles,$TILE" ;;
+esac
+printf '%s phase=customize integration_complete\n' \
+ "$(date -u '+%Y-%m-%dT%H:%M:%SZ')" >> "$INSTALL_LOG"
diff --git a/modules/intellirat/module.prop b/modules/intellirat/module.prop
new file mode 100644
index 0000000..5612640
--- /dev/null
+++ b/modules/intellirat/module.prop
@@ -0,0 +1,6 @@
+id=intellirat
+name=IntelliRAT
+version=0.2.0
+versionCode=2
+author=AXP Resurrection
+description=Automatic reversible Android 16 RAT profile controller
diff --git a/modules/intellirat/service.sh b/modules/intellirat/service.sh
new file mode 100755
index 0000000..f3a5b56
--- /dev/null
+++ b/modules/intellirat/service.sh
@@ -0,0 +1,51 @@
+#!/system/bin/sh
+
+# Initialize IntelliRAT state and register its Quick Settings tile.
+MODDIR=${0%/*}
+STATE_DIR=/data/local/tmp/intellirat
+APK="$MODDIR/payload/IntelliRAT.apk"
+INSTALL_APK=/data/local/tmp/intellirat-install.apk
+package_ready=1
+
+if ! pm path com.axp.intellirat >/dev/null 2>&1 && [ -f "$APK" ]; then
+ if cp "$APK" "$INSTALL_APK" 2>/dev/null; then
+ chmod 0644 "$INSTALL_APK"
+ install_result=$(pm install -r --user 0 "$INSTALL_APK" 2>&1 || true)
+ printf '%s install_result=%s\n' "$(date -u '+%Y-%m-%dT%H:%M:%SZ')" "$install_result" \
+ >> /data/local/tmp/intellirat-install.log
+ rm -f "$INSTALL_APK"
+ else
+ printf '%s install_error=copy_failed source=%s\n' \
+ "$(date -u '+%Y-%m-%dT%H:%M:%SZ')" "$APK" >> /data/local/tmp/intellirat-install.log
+ fi
+elif [ "$package_ready" = 1 ] && ! pm path com.axp.intellirat >/dev/null 2>&1; then
+ printf '%s install_error=payload_missing path=%s\n' \
+ "$(date -u '+%Y-%m-%dT%H:%M:%SZ')" "$APK" >> /data/local/tmp/intellirat-install.log
+fi
+for user in $(cmd user list 2>/dev/null | awk -F '[{}:]' '/UserInfo/ {print $2}'); do
+ if [ "$user" != 0 ]; then
+ pm uninstall --user "$user" com.axp.intellirat >/dev/null 2>&1 || true
+ fi
+done
+
+mkdir -p "$STATE_DIR"
+chown 0 2000 "$STATE_DIR"
+chmod 0770 "$STATE_DIR"
+
+if [ ! -f "$STATE_DIR/config.conf" ]; then
+ cp "$MODDIR/config/default.conf" "$STATE_DIR/config.conf"
+ chown 0 2000 "$STATE_DIR/config.conf"
+ chmod 0600 "$STATE_DIR/config.conf"
+fi
+
+printf '%s\n' "$(date -u '+%Y-%m-%dT%H:%M:%SZ') module_loaded version=0.1.0" \
+ >> "$STATE_DIR/events.log"
+chown 0 2000 "$STATE_DIR/events.log"
+chmod 0600 "$STATE_DIR/events.log"
+
+TILE='custom(com.axp.intellirat/.IntelliRatTileService)'
+tiles=$(settings get secure sysui_qs_tiles 2>/dev/null || true)
+case ",$tiles," in
+ *",$TILE,"*) ;;
+ *) settings put secure sysui_qs_tiles "$tiles,$TILE" ;;
+esac
diff --git a/modules/intellirat/system/bin/intellirat b/modules/intellirat/system/bin/intellirat
new file mode 100755
index 0000000..16caee3
--- /dev/null
+++ b/modules/intellirat/system/bin/intellirat
@@ -0,0 +1,296 @@
+#!/system/bin/sh
+
+set -u
+
+STATE_DIR=${INTELLIRAT_STATE_DIR:-/data/local/tmp}
+CONFIG_FILE=${INTELLIRAT_CONFIG:-$STATE_DIR/config.conf}
+LOG_FILE=${INTELLIRAT_LOG:-$STATE_DIR/events.log}
+CMD_BIN=${INTELLIRAT_CMD:-cmd}
+DUMPSYS_BIN=${INTELLIRAT_DUMPSYS:-dumpsys}
+
+DEFAULT_SLOT=0
+DEFAULT_HIGH_MASK=00011101001110000111
+DEFAULT_LOW_MASK=00011100001110000111
+
+usage() {
+ cat <<'EOF'
+IntelliRAT v0.1 manual prototype
+
+Usage:
+ intellirat status [slot]
+ intellirat capture [slot]
+ intellirat high [slot]
+ intellirat low [slot]
+ intellirat restore [slot]
+ intellirat --dry-run high|low|restore [slot]
+
+The prototype never changes RAT automatically. Capture the current state before
+the first transition. Restore returns the captured user network mask.
+EOF
+}
+
+fail() {
+ printf 'IntelliRAT: %s\n' "$*" >&2
+ exit 1
+}
+
+load_config() {
+ TARGET_SLOT=$DEFAULT_SLOT
+ HIGH_MASK=$DEFAULT_HIGH_MASK
+ LOW_MASK=$DEFAULT_LOW_MASK
+
+ if [ -f "$CONFIG_FILE" ]; then
+ # The file is created root-only by service.sh; reject unsafe ownership
+ # only when the platform exposes stat in the available shell.
+ # shellcheck disable=SC1090
+ . "$CONFIG_FILE"
+ fi
+
+ case "$TARGET_SLOT" in
+ ''|*[!0-9]*) fail "invalid TARGET_SLOT" ;;
+ esac
+ case "$HIGH_MASK:$LOW_MASK" in
+ *[!01:]*|:*) fail "masks must be non-empty binary strings" ;;
+ esac
+}
+
+ensure_state_dir() {
+ mkdir -p "$STATE_DIR" || fail "cannot create $STATE_DIR"
+ if [ "$STATE_DIR" = /data/local/tmp ]; then
+ return
+ fi
+ chown 0 2000 "$STATE_DIR" 2>/dev/null || true
+ chmod 0770 "$STATE_DIR" 2>/dev/null || true
+}
+
+log_event() {
+ ensure_state_dir
+ printf '%s %s\n' "$(date -u '+%Y-%m-%dT%H:%M:%SZ')" "$*" >> "$LOG_FILE"
+ chmod 0600 "$LOG_FILE" 2>/dev/null || true
+}
+
+slot_or_default() {
+ if [ "${1:-}" = '' ]; then
+ printf '%s' "$TARGET_SLOT"
+ else
+ case "$1" in
+ ''|*[!0-9]*) fail "invalid slot: $1" ;;
+ esac
+ printf '%s' "$1"
+ fi
+}
+
+state_file() {
+ printf '%s/intellirat-sub-%s.state' "$STATE_DIR" "$1"
+}
+
+read_user_names() {
+ slot=$1
+ result=$(
+ "$CMD_BIN" phone get-allowed-network-types-for-users -s "$slot" 2>&1
+ ) || fail "cannot read network types for slot $slot: $result"
+ case "$result" in
+ *'No valid subscription found'*|*'No valid NETWORK_TYPES_BITMASK'*)
+ fail "no valid subscription for slot $slot: $result"
+ ;;
+ esac
+ result=$(printf '%s' "$result" | tr -d '[:space:]\r\n')
+ [ -n "$result" ] || fail "empty network type response for slot $slot"
+ printf '%s' "$result"
+}
+
+network_names_to_decimal() {
+ names=$1
+ mask=0
+ old_ifs=$IFS
+ IFS='|'
+ # shellcheck disable=SC2086
+ for name in $names; do
+ case "$name" in
+ GPRS) mask=$((mask | 1)) ;;
+ EDGE) mask=$((mask | 2)) ;;
+ UMTS) mask=$((mask | 4)) ;;
+ CDMA) mask=$((mask | 8)) ;;
+ EVDO_0) mask=$((mask | 16)) ;;
+ EVDO_A) mask=$((mask | 32)) ;;
+ 1xRTT) mask=$((mask | 64)) ;;
+ HSDPA) mask=$((mask | 128)) ;;
+ HSUPA) mask=$((mask | 256)) ;;
+ HSPA) mask=$((mask | 512)) ;;
+ HSPA+) mask=$((mask | 16384)) ;;
+ IDEN) mask=$((mask | 1024)) ;;
+ EVDO_B) mask=$((mask | 2048)) ;;
+ LTE) mask=$((mask | 4096)) ;;
+ EHRPD) mask=$((mask | 8192)) ;;
+ HSPAP) mask=$((mask | 16384)) ;;
+ GSM) mask=$((mask | 32768)) ;;
+ TD_SCDMA) mask=$((mask | 65536)) ;;
+ IWLAN) mask=$((mask | 131072)) ;;
+ LTE_CA) mask=$((mask | 4096)) ;;
+ NR) mask=$((mask | 524288)) ;;
+ UNKNOWN|NONE) ;;
+ *) IFS=$old_ifs; fail "unknown network type from telephony: $name" ;;
+ esac
+ done
+ IFS=$old_ifs
+ printf '%s' "$mask"
+}
+
+decimal_to_binary() {
+ mask=$1
+ output=
+ bit=19
+ while [ "$bit" -ge 0 ]; do
+ value=$((1 << bit))
+ if [ $((mask & value)) -ne 0 ]; then
+ output="${output}1"
+ else
+ output="${output}0"
+ fi
+ bit=$((bit - 1))
+ done
+ printf '%s' "$output"
+}
+
+read_user_binary() {
+ names=$(read_user_names "$1")
+ decimal=$(network_names_to_decimal "$names")
+ decimal_to_binary "$decimal"
+}
+
+call_in_progress() {
+ dump=$(
+ "$DUMPSYS_BIN" telecom 2>/dev/null || true
+ )
+ current_calls=$(printf '%s\n' "$dump" | awk '
+ /^ All calls:/ { in_calls=1; next }
+ /^ Active dialing, or connecting calls:/ { in_calls=1; next }
+ /^ Ringing calls:/ { in_calls=1; next }
+ /^ Holding calls:/ { in_calls=1; next }
+ /^[[:space:]]*[A-Za-z][A-Za-z _-]*:$/ { in_calls=0 }
+ in_calls { print }
+ ')
+ printf '%s\n' "$current_calls" | grep -Eq \
+ '(^|[^A-Za-z])(ACTIVE|RINGING|DIALING|CONNECTING)([^A-Za-z]|$)|mCallState=(ACTIVE|RINGING|DIALING|CONNECTING)'
+}
+
+write_state() {
+ slot=$1
+ original_names=$2
+ original_binary=$3
+ file=$(state_file "$slot")
+ tmp="${file}.tmp.$$"
+ {
+ printf 'original_names=%s\n' "$original_names"
+ printf 'original_mask=%s\n' "$original_binary"
+ printf 'captured_at=%s\n' "$(date -u '+%Y-%m-%dT%H:%M:%SZ')"
+ } > "$tmp" || fail "cannot write $file"
+ chmod 0600 "$tmp"
+ mv "$tmp" "$file" || fail "cannot commit $file"
+}
+
+capture() {
+ slot=$1
+ ensure_state_dir
+ names=$(read_user_names "$slot")
+ decimal=$(network_names_to_decimal "$names")
+ binary=$(decimal_to_binary "$decimal")
+ file=$(state_file "$slot")
+ if [ -f "$file" ] && [ "${INTELLIRAT_FORCE_CAPTURE:-0}" != 1 ]; then
+ fail "state already captured for slot $slot; set INTELLIRAT_FORCE_CAPTURE=1 to replace it"
+ fi
+ write_state "$slot" "$names" "$binary"
+ log_event "capture slot=$slot mask=$binary names=$names"
+ printf 'captured slot=%s mask=%s names=%s\n' "$slot" "$binary" "$names"
+}
+
+get_original_mask() {
+ file=$(state_file "$1")
+ [ -f "$file" ] || {
+ printf '%s' "$HIGH_MASK"
+ return
+ }
+ value=$(grep '^original_mask=' "$file" | cut -d= -f2-)
+ case "$value" in
+ ''|*[!01]*) fail "invalid saved state for slot $1" ;;
+ esac
+ printf '%s' "$value"
+}
+
+apply_mask() {
+ slot=$1
+ target=$2
+ reason=$3
+ current=$(read_user_binary "$slot")
+ if [ "$current" = "$target" ]; then
+ log_event "noop slot=$slot state=$reason mask=$target"
+ printf 'already state=%s slot=%s mask=%s\n' "$reason" "$slot" "$target"
+ return 0
+ fi
+
+ if call_in_progress; then
+ fail "telephony call is active; transition refused"
+ fi
+
+ if [ "${INTELLIRAT_DRY_RUN:-0}" = 1 ]; then
+ printf 'dry-run cmd phone set-allowed-network-types-for-users -s %s %s\n' \
+ "$slot" "$target"
+ return 0
+ fi
+
+ result=$(
+ "$CMD_BIN" phone set-allowed-network-types-for-users -s "$slot" "$target" 2>&1
+ ) || fail "network transition failed: $result"
+ case "$result" in
+ *completed*) ;;
+ *) fail "telephony did not confirm transition: $result" ;;
+ esac
+
+ actual=$(read_user_binary "$slot")
+ [ "$actual" = "$target" ] || fail "verification mismatch: requested=$target actual=$actual"
+ log_event "transition slot=$slot previous=$current state=$reason requested=$target actual=$actual"
+ printf 'transition slot=%s state=%s previous=%s actual=%s\n' \
+ "$slot" "$reason" "$current" "$actual"
+}
+
+status() {
+ slot=$1
+ names=$(read_user_names "$slot")
+ decimal=$(network_names_to_decimal "$names")
+ current=$(decimal_to_binary "$decimal")
+ state=UNKNOWN
+ [ "$current" = "$HIGH_MASK" ] && state=HIGH
+ [ "$current" = "$LOW_MASK" ] && state=LOW
+ saved=NO
+ [ -f "$(state_file "$slot")" ] && saved=YES
+ printf 'slot=%s state=%s mask=%s names=%s saved_original=%s\n' \
+ "$slot" "$state" "$current" "$names" "$saved"
+}
+
+main() {
+ dry_run=0
+ if [ "${1:-}" = '--dry-run' ]; then
+ dry_run=1
+ INTELLIRAT_DRY_RUN=1
+ export INTELLIRAT_DRY_RUN
+ shift
+ fi
+ load_config
+ ensure_state_dir
+
+ command=${1:-help}
+ shift || true
+ slot=$(slot_or_default "${1:-}")
+
+ case "$command" in
+ help|-h|--help) usage ;;
+ status) status "$slot" ;;
+ capture) capture "$slot" ;;
+ high) apply_mask "$slot" "$HIGH_MASK" HIGH ;;
+ low) apply_mask "$slot" "$LOW_MASK" LOW ;;
+ restore) apply_mask "$slot" "$(get_original_mask "$slot")" RESTORE ;;
+ *) usage >&2; exit 2 ;;
+ esac
+}
+
+main "$@"
diff --git a/modules/intellirat/uninstall.sh b/modules/intellirat/uninstall.sh
new file mode 100755
index 0000000..30e8d46
--- /dev/null
+++ b/modules/intellirat/uninstall.sh
@@ -0,0 +1,7 @@
+#!/system/bin/sh
+
+for user in $(cmd user list 2>/dev/null | awk -F '[{}:]' '/UserInfo/ {print $2}'); do
+ pm uninstall --user "$user" com.axp.intellirat >/dev/null 2>&1 || true
+done
+rm -f /data/local/tmp/intellirat-sub-*.state
+rm -f /data/local/tmp/intellirat-events.log
diff --git a/scripts/build-intellirat-module.sh b/scripts/build-intellirat-module.sh
new file mode 100755
index 0000000..749aec1
--- /dev/null
+++ b/scripts/build-intellirat-module.sh
@@ -0,0 +1,24 @@
+#!/bin/sh
+# Package the systemless IntelliRAT prototype without touching a ROM build.
+set -eu
+
+ROOT=$(CDPATH= cd -- "$(dirname "$0")/.." && pwd)
+MODULE="$ROOT/modules/intellirat"
+OUTPUT=${1:-"$ROOT/artifacts/IntelliRAT-v0.2.0.zip"}
+
+test -f "$MODULE/module.prop"
+test -x "$MODULE/system/bin/intellirat"
+test ! -e "$MODULE/action.sh"
+if [ ! -f "$MODULE/payload/IntelliRAT.apk" ]; then
+ echo "WARNING: companion APK is not built; packaging backend-only module" >&2
+fi
+mkdir -p "$(dirname "$OUTPUT")"
+rm -f "$OUTPUT"
+
+(
+ cd "$MODULE"
+ zip -qr "$OUTPUT" . -x README.md 'app/*' 'app/**'
+)
+
+printf 'OK: %s\n' "$OUTPUT"
+sha256sum "$OUTPUT"
diff --git a/specs/009-axp-los23-port/spec.md b/specs/009-axp-los23-port/spec.md
index a9d0dc5..2cada18 100644
--- a/specs/009-axp-los23-port/spec.md
+++ b/specs/009-axp-los23-port/spec.md
@@ -4,8 +4,10 @@
**Created**: 2026-09-16
-**Status**: Spec clarified 2026-09-17 (banking deferred; kernel hardening in;
-localization compare later) — tasks updated; ready for `/speckit-implement`
+**Status**: Implementation closure baseline built 2026-10-16. LAB acceptance,
+official upstream manifest equivalence, FR-019, cgroup decision and clean
+kernel-source policy remain open; banking is deferred and localization compare
+is later.
**Input**: User description: "009-axp based LOS22 feature port to LOS23 —
take vanilla LineageOS 23 for sunfish and integrate the full AXP.OS (LOS22 /
diff --git a/tests/test_009_next_bacon_guards.py b/tests/test_009_next_bacon_guards.py
index fd6e4a0..e78d363 100644
--- a/tests/test_009_next_bacon_guards.py
+++ b/tests/test_009_next_bacon_guards.py
@@ -78,6 +78,17 @@ class NextBaconGuardsTest(unittest.TestCase):
self.assertIn("SUNFISH_A16_008_CALL_REC_FLAG_SECURE", ship)
self.assertIn("extendrom_call_recording", ship)
+ def test_defconfig_enables_android_cgroup_controllers_and_hides_dirty_suffix(self) -> None:
+ apply = (
+ ROOT
+ / "vendor/sunfish-a16/patches/009-kernel-hardening/apply_uss5_1_defconfig.sh"
+ ).read_text()
+ self.assertIn("CONFIG_MEMCG=y", apply)
+ self.assertIn("CONFIG_CGROUP_PIDS=y", apply)
+ self.assertIn("# CONFIG_LOCALVERSION_AUTO is not set", apply)
+ self.assertIn("CONFIG_SECURITY_YAMA", apply)
+ self.assertIn("CONFIG_USERFAULTFD", apply)
+
def test_artifact_metadata_names_49980_and_vendor_spl(self) -> None:
ship = SHIP.read_text()
self.assertIn(
diff --git a/tests/test_intellirrat.py b/tests/test_intellirrat.py
new file mode 100644
index 0000000..5013482
--- /dev/null
+++ b/tests/test_intellirrat.py
@@ -0,0 +1,146 @@
+#!/usr/bin/env python3
+"""Host-side guards for the IntelliRAT v0.1 manual prototype."""
+
+from pathlib import Path
+import os
+import subprocess
+import tempfile
+import unittest
+
+
+ROOT = Path(__file__).resolve().parents[1]
+MODULE = ROOT / "modules/intellirat"
+CONTROLLER = MODULE / "system/bin/intellirat"
+
+
+class IntelliRatTest(unittest.TestCase):
+ def test_prototype_is_manual_and_uses_modern_command(self) -> None:
+ script = CONTROLLER.read_text()
+ service = (MODULE / "service.sh").read_text()
+ self.assertIn("get-allowed-network-types-for-users", script)
+ self.assertIn("set-allowed-network-types-for-users", script)
+ self.assertIn("capture", script)
+ self.assertIn("original_mask", script)
+ self.assertIn("call_in_progress", script)
+ self.assertNotIn("setPreferredNetworkType", script)
+ self.assertNotIn("preferred_network_mode", script)
+ self.assertNotIn("Xposed", script)
+ self.assertNotIn("intellirat high", service)
+ self.assertNotIn("intellirat low", service)
+
+ def test_companion_app_has_menu_and_persistent_notification_surface(self) -> None:
+ manifest = (MODULE / "app/AndroidManifest.xml").read_text()
+ activity = (MODULE / "app/src/com/axp/intellirat/MainActivity.java").read_text()
+ service = (MODULE / "app/src/com/axp/intellirat/IntelliRatService.java").read_text()
+ boot = (MODULE / "app/src/com/axp/intellirat/BootReceiver.java").read_text()
+ tile = (MODULE / "app/src/com/axp/intellirat/IntelliRatTileService.java").read_text()
+ self.assertIn("POST_NOTIFICATIONS", manifest)
+ self.assertIn("RECEIVE_BOOT_COMPLETED", manifest)
+ self.assertIn("PACKAGE_USAGE_STATS", manifest)
+ self.assertIn("foregroundServiceType=\"specialUse\"", manifest)
+ for command in ("status", "capture", "high", "low", "restore"):
+ self.assertIn(f'addButton(actions, "', activity)
+ self.assertIn(f'"{command}")', activity)
+ self.assertIn("startForeground", service)
+ self.assertIn("KeyguardManager", service)
+ self.assertIn("isKeyguardLocked", service)
+ self.assertIn("Automatic unlocked HIGH", service)
+ self.assertIn("AUTO_UNLOCK_DELAY_SECONDS", service)
+ self.assertIn("AUTO_DELAY_SECONDS", service)
+ self.assertIn("AUTO_EXCEPTIONS", service)
+ self.assertIn("UsageStatsManager", service)
+ self.assertIn("START_STICKY", service)
+ self.assertIn("BOOT_COMPLETED", manifest)
+ self.assertIn("startForegroundService", boot)
+ self.assertIn("Automatic lock policy", activity)
+ self.assertIn("Apply automatic policy", activity)
+ self.assertIn("Exception packages", activity)
+ self.assertIn("IntelliRatTileService", manifest)
+ self.assertIn("onClick", tile)
+ self.assertIn('"low"', tile)
+ self.assertIn('"high"', tile)
+ self.assertIn('action("HIGH", "high"', service)
+ self.assertIn('action("LOW", "low"', service)
+ self.assertIn('action("RESTORE", "restore"', service)
+
+ def test_shell_files_parse(self) -> None:
+ for path in (CONTROLLER, MODULE / "service.sh"):
+ result = subprocess.run(["sh", "-n", str(path)], capture_output=True, text=True)
+ self.assertEqual(result.returncode, 0, result.stderr)
+
+ def test_capture_low_restore_and_call_guard(self) -> None:
+ with tempfile.TemporaryDirectory() as tmp:
+ root = Path(tmp)
+ bin_dir = root / "bin"
+ bin_dir.mkdir()
+ fake_state = root / "network"
+ fake_state.write_text(
+ "GSM|GPRS|EDGE|UMTS|HSDPA|HSUPA|HSPA|LTE|HSPAP|TD_SCDMA\n"
+ )
+ fake_call = root / "call"
+ fake_call.write_text("IDLE\n")
+
+ (bin_dir / "cmd").write_text(
+ """#!/bin/sh
+state=${FAKE_NETWORK_STATE:?}
+if [ "$2" = get-allowed-network-types-for-users ]; then
+ tr -d '\\n' < "$state"
+ exit 0
+fi
+if [ "$2" = set-allowed-network-types-for-users ]; then
+ case "$5" in
+ 00011100001110000111)
+ printf '%s\\n' 'GSM|GPRS|EDGE|UMTS|HSDPA|HSUPA|HSPA|HSPAP|TD_SCDMA' > "$state" ;;
+ 00011101001110000111)
+ printf '%s\\n' 'GSM|GPRS|EDGE|UMTS|HSDPA|HSUPA|HSPA|LTE|HSPAP|TD_SCDMA' > "$state" ;;
+ *) printf 'unexpected mask\\n' >&2; exit 1 ;;
+ esac
+ printf '%s\\n' 'set-allowed-network-types-for-users completed'
+ exit 0
+fi
+printf 'unexpected command\\n' >&2
+exit 1
+"""
+ )
+ (bin_dir / "dumpsys").write_text(
+ """#!/bin/sh
+cat "$FAKE_CALL"
+"""
+ )
+ for path in (bin_dir / "cmd", bin_dir / "dumpsys"):
+ path.chmod(0o755)
+
+ env = os.environ.copy()
+ env.update(
+ {
+ "INTELLIRAT_STATE_DIR": str(root / "state"),
+ "INTELLIRAT_CMD": str(bin_dir / "cmd"),
+ "INTELLIRAT_DUMPSYS": str(bin_dir / "dumpsys"),
+ "FAKE_NETWORK_STATE": str(fake_state),
+ "FAKE_CALL": str(fake_call),
+ }
+ )
+
+ def run(*args: str) -> subprocess.CompletedProcess[str]:
+ return subprocess.run(
+ ["sh", str(CONTROLLER), *args],
+ env=env,
+ capture_output=True,
+ text=True,
+ )
+
+ self.assertEqual(run("capture", "0").returncode, 0)
+ low = run("low", "0")
+ self.assertEqual(low.returncode, 0, low.stderr + low.stdout)
+ self.assertIn("state=LOW", run("status", "0").stdout)
+ self.assertEqual(run("restore", "0").returncode, 0)
+ self.assertIn("state=HIGH", run("status", "0").stdout)
+
+ fake_call.write_text("mCallState=ACTIVE\n")
+ blocked = run("low", "0")
+ self.assertNotEqual(blocked.returncode, 0)
+ self.assertIn("call is active", blocked.stderr)
+
+
+if __name__ == "__main__":
+ unittest.main()
diff --git a/vendor/sunfish-a16/Android.mk b/vendor/sunfish-a16/Android.mk
index 6e371db..f092f77 100644
--- a/vendor/sunfish-a16/Android.mk
+++ b/vendor/sunfish-a16/Android.mk
@@ -96,3 +96,25 @@ include $(BUILD_PREBUILT)
# EuiccGoogle is removed by apply_009_drop_euicc_google.py from
# vendor/google/sunfish/sunfish-vendor.mk.
+
+# AXPR AOSmium WebView (org.axpos.aosmium_wv), presigned with the AXPR release
+# key. Installed on /product/app (system/system_ext have no room for ~275 MB).
+# It is the default WebView provider via config_webview_packages.xml; AOSP/LOS
+# and Google providers stay in the allowlist.
+include $(CLEAR_VARS)
+LOCAL_MODULE := AosmiumWebView
+LOCAL_MODULE_CLASS := APPS
+LOCAL_PRODUCT_MODULE := true
+LOCAL_CERTIFICATE := PRESIGNED
+LOCAL_SRC_FILES := prebuilt/AosmiumWebView.apk
+LOCAL_REPLACE_PREBUILT_APK_INSTALLED := $(LOCAL_PATH)/prebuilt/AosmiumWebView.apk
+LOCAL_MODULE_SUFFIX := $(COMMON_ANDROID_PACKAGE_SUFFIX)
+# Chromium-built WebView declares these as optional uses-library entries; the
+# ROM does not provide android.ext.adservices or android.test.base, so keep
+# them optional to satisfy enforce_uses_libraries.
+LOCAL_OPTIONAL_USES_LIBRARIES := \
+ android.test.base \
+ androidx.window.extensions \
+ android.ext.adservices
+LOCAL_DEX_PREOPT := false
+include $(BUILD_PREBUILT)
diff --git a/vendor/sunfish-a16/apps.mk b/vendor/sunfish-a16/apps.mk
index 9dd066b..96dff8f 100644
--- a/vendor/sunfish-a16/apps.mk
+++ b/vendor/sunfish-a16/apps.mk
@@ -9,6 +9,15 @@ PRODUCT_PACKAGES += \
FossifyGallery \
OpenEUICC
+# AXPR AOSmium WebView: default provider, /product/app, presigned release key.
+PRODUCT_PACKAGES += AosmiumWebView
+
+# The LOS/AOSP `webview` prebuilt is removed by
+# patches/009-axp-los23-port/apply_009_single_webview.py (it is added later by
+# build/make/target/product/media_product.mk, so filtering here has no effect).
+# Its provider allowlist entry remains, so it can be installed and selected
+# later.
+
# EuiccGoogle is removed by apply_009_drop_euicc_google.py. inherit-product
# merges PRODUCT_PACKAGES per makefile, so a filter-out here does not see
# the sunfish-vendor.mk entries.
diff --git a/vendor/sunfish-a16/branding/overlay/common/frameworks/base/core/res/res/xml/config_webview_packages.xml b/vendor/sunfish-a16/branding/overlay/common/frameworks/base/core/res/res/xml/config_webview_packages.xml
index 5b5039f..3c7c1a9 100644
--- a/vendor/sunfish-a16/branding/overlay/common/frameworks/base/core/res/res/xml/config_webview_packages.xml
+++ b/vendor/sunfish-a16/branding/overlay/common/frameworks/base/core/res/res/xml/config_webview_packages.xml
@@ -17,20 +17,28 @@
-
+
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
-
+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
-
+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
-
+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
-
+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
+
+
+
+
diff --git a/vendor/sunfish-a16/patches/009-axp-los23-port/apply_009_single_webview.py b/vendor/sunfish-a16/patches/009-axp-los23-port/apply_009_single_webview.py
new file mode 100644
index 0000000..008946d
--- /dev/null
+++ b/vendor/sunfish-a16/patches/009-axp-los23-port/apply_009_single_webview.py
@@ -0,0 +1,37 @@
+#!/usr/bin/env python3
+"""009: ship a single WebView engine (AXPR AosmiumWebView only).
+
+The AOSP/Lineage `webview` prebuilt (~255 MB) is added by
+build/make/target/product/media_product.mk, which is inherited later than
+vendor/sunfish-a16/apps.mk, so a PRODUCT_PACKAGES filter there has no effect.
+Remove the package from the media_product list instead.
+
+The provider allowlist (config_webview_packages.xml) still declares
+com.android.webview and the Google providers, so they can be installed and
+selected later; they are simply not preinstalled.
+
+Idempotent: guarded by a marker comment.
+"""
+from pathlib import Path
+
+TREE = Path(__file__).resolve().parents[4]
+MEDIA = TREE / "build/make/target/product/media_product.mk"
+MARKER = "# sunfish-a16-009-single-webview"
+LINE = " webview \\\n"
+
+if not MEDIA.is_file():
+ raise SystemExit(f"FAIL: missing {MEDIA}")
+
+text = MEDIA.read_text()
+
+if MARKER in text:
+ print(f"already applied: {MEDIA} (single-webview)")
+ raise SystemExit(0)
+
+if LINE not in text:
+ raise SystemExit(f"FAIL: expected 'webview' entry not found in {MEDIA}")
+
+text = text.replace(LINE, "", 1)
+text = text.rstrip("\n") + f"\n\n{MARKER}: AosmiumWebView is the only shipped WebView.\n"
+MEDIA.write_text(text)
+print(f"patched: {MEDIA} (webview package removed)")
diff --git a/vendor/sunfish-a16/patches/009-kernel-hardening/apply_uss5_1_defconfig.sh b/vendor/sunfish-a16/patches/009-kernel-hardening/apply_uss5_1_defconfig.sh
index 86fa793..0d1db45 100755
--- a/vendor/sunfish-a16/patches/009-kernel-hardening/apply_uss5_1_defconfig.sh
+++ b/vendor/sunfish-a16/patches/009-kernel-hardening/apply_uss5_1_defconfig.sh
@@ -16,6 +16,14 @@
# sunfish_defconfig turns it on. This kernel has no UFFDIO_MOVE (that ioctl
# is 6.1+), so ART only opens a fd and times out. Match GOLDEN.
#
+# MEMCG / CGROUP_PIDS: Android 16 mounts cgroup v2 and attempts per-UID memory
+# and PID controller activation. The old sunfish defconfig leaves both off,
+# which produces `+memory ... Invalid argument` at runtime. Enable the native
+# 4.14 controllers for the next candidate.
+#
+# LOCALVERSION_AUTO: the kernel source is intentionally patched in the build;
+# do not expose the source worktree's incidental `-dirty` suffix in uname.
+#
# Usage: apply_uss5_1_defconfig.sh
set -euo pipefail
@@ -23,7 +31,8 @@ K=${1:?kernel tree}
DEF="$K/arch/arm64/configs/sunfish_defconfig"
test -f "$DEF"
-for k in SECURITY_YAMA SLAB_FREELIST_RANDOM USERFAULTFD HAVE_ARCH_USERFAULTFD_MINOR; do
+for k in SECURITY_YAMA SLAB_FREELIST_RANDOM USERFAULTFD HAVE_ARCH_USERFAULTFD_MINOR \
+ MEMCG CGROUP_PIDS LOCALVERSION_AUTO; do
sed -i "/CONFIG_${k}/d" "$DEF"
done
@@ -31,6 +40,9 @@ done
echo "# CONFIG_SECURITY_YAMA is not set"
echo "CONFIG_SLAB_FREELIST_RANDOM=y"
echo "# CONFIG_USERFAULTFD is not set"
+ echo "CONFIG_MEMCG=y"
+ echo "CONFIG_CGROUP_PIDS=y"
+ echo "# CONFIG_LOCALVERSION_AUTO is not set"
} >> "$DEF"
-echo "OK: US-S5-1 defconfig (SLAB_FREELIST_RANDOM; YAMA, USERFAULTFD off) on $K"
+echo "OK: US-S5-1 defconfig (SLAB_FREELIST_RANDOM; MEMCG, CGROUP_PIDS on; YAMA, USERFAULTFD, LOCALVERSION_AUTO off) on $K"