009: publish AXPR23 final source and public documentation

This commit is contained in:
sunfish-admin committed 2026-11-06 21:11:35 +01:00
1 parent 72930e0fd6
commit 2c714d8d34
35 files changed
+2498 -403

No files matched your search

+1 -1
View File
@@ -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`
+5 -6
View File
@@ -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
+73
View File
@@ -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-*`.
+33
View File
@@ -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`
+99 -2
View File
@@ -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`.
+8 -5
View File
@@ -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 | |
+1
View File
@@ -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).
+299
View File
@@ -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`.
+243 -380
View File
@@ -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`.
+62
View File
@@ -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.
+10
View File
@@ -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"],
},
}
@@ -0,0 +1,59 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.axp.intellirat">
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_SPECIAL_USE" />
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
<uses-permission android:name="android.permission.PACKAGE_USAGE_STATS" />
<uses-permission android:name="android.permission.QUERY_ALL_PACKAGES" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<application
android:allowBackup="false"
android:label="IntelliRAT"
android:supportsRtl="true"
android:theme="@android:style/Theme.Material.NoActionBar">
<activity
android:name=".MainActivity"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<service
android:name=".IntelliRatService"
android:exported="false"
android:foregroundServiceType="specialUse">
<property
android:name="android.app.PROPERTY_SPECIAL_USE_FGS_SUBTYPE"
android:value="telephony network profile controller" />
</service>
<receiver
android:name=".ActionReceiver"
android:exported="false" />
<receiver
android:name=".BootReceiver"
android:directBootAware="false"
android:exported="false">
<intent-filter>
<action android:name="android.intent.action.BOOT_COMPLETED" />
<action android:name="android.intent.action.USER_UNLOCKED" />
</intent-filter>
</receiver>
<service
android:name=".IntelliRatTileService"
android:label="IntelliRAT HIGH/LOW"
android:icon="@drawable/ic_intellirat"
android:permission="android.permission.BIND_QUICK_SETTINGS_TILE"
android:exported="true">
<intent-filter>
<action android:name="android.service.quicksettings.action.QS_TILE" />
</intent-filter>
</service>
</application>
</manifest>
+1
View File
@@ -0,0 +1 @@
-keep class com.axp.intellirat.** { *; }
@@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="#FFFFFFFF"
android:pathData="M3,5h18v14H3zM5,7v10h14V7zM7,9h2v2H7zM11,9h6v2h-6zM7,13h2v2H7zM11,13h6v2h-6z" />
</vector>
@@ -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);
}
}
@@ -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();
}
}
}
@@ -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);
}
}
}
@@ -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);
}
}
@@ -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();
});
}
}
@@ -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<ApplicationInfo> 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<String> 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);
}
}
}
+6
View File
@@ -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
+57
View File
@@ -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"
+6
View File
@@ -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
+51
View File
@@ -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
+296
View File
@@ -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 "$@"
+7
View File
@@ -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
+24
View File
@@ -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"
+4 -2
View File
@@ -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 /
+11
View File
@@ -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(
+146
View File
@@ -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()
+22
View File
@@ -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)
+9
View File
@@ -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.
@@ -17,20 +17,28 @@
<webviewproviders>
<webviewprovider description="Google WebView" packageName="com.google.android.webview" availableByDefault="true">
<webviewprovider description="Google WebView" packageName="com.google.android.webview" availableByDefault="false">
<signature>MIIDuzCCAqOgAwIBAgIJANi6DgBQG4ZTMA0GCSqGSIb3DQEBBQUAMHQxCzAJBgNVBAYTAlVTMRMwEQYDVQQIDApDYWxpZm9ybmlhMRYwFAYDVQQHDA1Nb3VudGFpbiBWaWV3MRQwEgYDVQQKDAtHb29nbGUgSW5jLjEQMA4GA1UECwwHQW5kcm9pZDEQMA4GA1UEAwwHd2VidmlldzAeFw0xNDA4MDgyMzIwMjBaFw00MTEyMjQyMzIwMjBaMHQxCzAJBgNVBAYTAlVTMRMwEQYDVQQIDApDYWxpZm9ybmlhMRYwFAYDVQQHDA1Nb3VudGFpbiBWaWV3MRQwEgYDVQQKDAtHb29nbGUgSW5jLjEQMA4GA1UECwwHQW5kcm9pZDEQMA4GA1UEAwwHd2VidmlldzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMbtaFX0r5aZJMAbPVMAgK1ZZ29dTn91VsGxXv2hqrQo7IpqEy2JmPvPnoMsSiuTAe+UcQy8oKDQ2aYVSAd1DGIy+nSRyFTt3LSIAdwSBkB1qT4a+OqkpsR6bSNXQXQ18lCQu9gREY3h3QlYBQAyzRxw4hRGlrXAzuSz1Ec4W+6x4nLG5DG61MAMR8ClF9XSqbmGB3kyZ70A0X9OPYYxiMWP1ExaYvpaVqjyZZcrPwr+vtW8oCuGBUtHpBUH3OoG+9s2YMcgLG7vCK9awKDqlPcJSpIAAj6uGs4gORmkqxZRMskLSTWbhP4p+3Ap8jYzTVB6Y1/DMVmYTWRMcPW0macCAwEAAaNQME4wHQYDVR0OBBYEFJ6bAR6/QVm4w9LRSGQiaR5Rhp3TMB8GA1UdIwQYMBaAFJ6bAR6/QVm4w9LRSGQiaR5Rhp3TMAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEFBQADggEBAEQu8QiVxax7/diEiJrgKE1LwdXsIygJK/KnaKdnYEkAQpeu/QmrLiycm+OFbL1qHJIB7OuI/PQBUtcaNSiJSCVgtwtEbZWWIdsynqG/Nf4aGOndXegSQNRH54M05sRHLoeRycPrY7xQlEwGikNFR76+5UdwFBQI3Gn22g6puJnVukQm/wXQ+ajoiS4QclrNlixoDQsZ4STLH4+Wju2wIWKFFArIhVEIlbamq+p6BghuzH3aIz/Fy0YTQKi7SA+0fuNeCaqlSm5pYSt6p5CH89y1Fr+wFc5r3iLRnUwRcy08ESC7bZJnxV3d/YQ5valTxBbzku/dQbXVj/xg69H8l8M</signature>
</webviewprovider>
<webviewprovider description="Google WebView Beta" packageName="com.google.android.webview.beta" availableByDefault="true">
<webviewprovider description="Google WebView Beta" packageName="com.google.android.webview.beta" availableByDefault="false">
<signature>MIIFxzCCA6+gAwIBAgIVAO+zzx6JOZx3HWDG+fzlC1m53BsqMA0GCSqGSIb3DQEBCwUAMHQxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpDYWxpZm9ybmlhMRYwFAYDVQQHEw1Nb3VudGFpbiBWaWV3MRQwEgYDVQQKEwtHb29nbGUgSW5jLjEQMA4GA1UECxMHQW5kcm9pZDEQMA4GA1UEAxMHQW5kcm9pZDAeFw0xOTA0MDUxNzIxNTRaFw00OTA0MDUxNzIxNTRaMHQxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpDYWxpZm9ybmlhMRYwFAYDVQQHEw1Nb3VudGFpbiBWaWV3MRQwEgYDVQQKEwtHb29nbGUgSW5jLjEQMA4GA1UECxMHQW5kcm9pZDEQMA4GA1UEAxMHQW5kcm9pZDCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAIbFePU+vsq6CEYIREsFFhwzXDoCwO6aK9ic9WJubui5VV7OqPCZmoZ/LY3852JK1A1OrWU643Ra7Z/wcvBXHHy5g08bF35gfelz9l53rIiG/1ubAApmxMNqASmQ5bMseetUIrBVqFnWspOWyrNuD0UsF4YkLgm+T1X5Hz1RNZwIV7WcVlL5qC2tm6kEy50bcAhbo5dDDud+ft8oc9g7vAiUpV0yqtNjZ+tsdJI4DrW5Nf2UhB8E+91se2IzQyWT6Vs/wB67HwroGr8qwPQYLQvzcyxZo2yA+qU2+k1IgJGTG5/0K/LheNxjqOKj6Zuhceff0JlbmrGKKqbXlz1F/E582MfqjeETB1gjw9Y0tHWEofEGdL4+ub8ZBJmveH9iz4BVKplzmYACLYWCGICiHBvmkkvx9dhmf5SsUsL9o4axPtAOKtjcKIDXBVqOtYCZssoVe9FFlZDHxRhQlbGY6ip0CK/lYlcx8iLfvI8Hf2AlwZa4j7HpFityaAWgYvo9x7bMJbBHiN/HW/NuhCF1B54KnQUmzQoyEnr5Qb4NjiDiXNzR8gYWYa9a/6Zg9iggr4jIbbEVanvCw9FAAZKRR8rk0ToJuFk2fRJdi2NhKo1GbVWOjj1Cd/Xbahd/uZhZGf1Uc61bG4rn9NU4GqliO2Wl2L78EkxuLDKXdovdHFjzAgMBAAGjUDBOMAwGA1UdEwQFMAMBAf8wHQYDVR0OBBYEFCAIx3WLD1UQ3YUE9ZBt36vfdQ6eMB8GA1UdIwQYMBaAFCAIx3WLD1UQ3YUE9ZBt36vfdQ6eMA0GCSqGSIb3DQEBCwUAA4ICAQBdHaxW69Hx4WPMXAVC5+S419L7/+AgxTyCn7YsD77aiKlIU20BMhvGln1d/Og6y1mf/z/x+GAm1ZfQniOih21Gxq3h4C3FvJ7gHgrjtZ6r0ymz7a1YOYc6LG1DNrTK7gKs5syifUd68PxUwNm5U5R9ixj0iglZDAC0apehGH/nfyJ4btS39N+oSQkUz3FpLoD5wYRlfdzhL2rMVDcv4WcEVDO8X8Md5Bk3hYtkh5MHzbVlu2yQ1qcNeXLUxUVeTjGebMu7B54Fgf+tHSBFBiyDdocpgF0B7RjF5579MXLpxdf9hoQImgzmp6xe0oHwqS1nQZR0pYfwP1Y8vxMSUWTPbY0YB9jQaElXznE/eQBuXe4kRkjEO6QvhziiQFxBlbfGSEFVySeqgWR91tJ0OiEWkMraQaI2bz941Qbt12PhS7r0KIkAsC5LpVRDOEgmo2e6+evst7DXpIAUvzNNSHrnmwmMGZ4QP4AUi7uIsclDJUT9sXDhwutx2Edic0X8+ZZ8D4e+HEupXI30z72En0U6ZyPqb6Ll9SOgK6pN6dbhdakucvRHCpmSfIlO8XXmPN5x3RsSteZl20Mc+ZSQuninApdirEs/9CfRoSSYXlNabxklBZd5jeb0py09FjjE+Nqf4EdZFYvzpYFSGJz1RUrsaDWOmx4gF3YeXo8iUbGtPA==</signature>
</webviewprovider>
<webviewprovider description="Google WebView Dev" packageName="com.google.android.webview.dev" availableByDefault="true">
<webviewprovider description="Google WebView Dev" packageName="com.google.android.webview.dev" availableByDefault="false">
<signature>MIIFxzCCA6+gAwIBAgIVAMQCAZgONsTyki5gq7E1h9s6KFvaMA0GCSqGSIb3DQEBCwUAMHQxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpDYWxpZm9ybmlhMRYwFAYDVQQHEw1Nb3VudGFpbiBWaWV3MRQwEgYDVQQKEwtHb29nbGUgSW5jLjEQMA4GA1UECxMHQW5kcm9pZDEQMA4GA1UEAxMHQW5kcm9pZDAeFw0xOTA0MDUxNzE3MDJaFw00OTA0MDUxNzE3MDJaMHQxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpDYWxpZm9ybmlhMRYwFAYDVQQHEw1Nb3VudGFpbiBWaWV3MRQwEgYDVQQKEwtHb29nbGUgSW5jLjEQMA4GA1UECxMHQW5kcm9pZDEQMA4GA1UEAxMHQW5kcm9pZDCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAJjFQ4Jm1Tf5BESiSnU1w5H5//IbLr6wNIfVbws8mbPb3fq9vs3LAHbtfzgWJ3eG2eNx7GH6ge+XjaNiZzQ8223Fqu9AxBinve4KSm6Mrkq7gk7lMCbUUIap6m0ftDa+7lrxbhjWHrFzFq8Muq43CM7i8CxLdKTGSx098dcvY6giOQstAKQyPmlbH6zjwnS8KNYakDKv0Fv4VdKi14T6ANXAblicfTaclA1HLWhFqv5DaKL8YobQu8FN2Egk9nlimwExbRs1twrmuyX2tNtgX7vMvt3+HHTrkYweARVvvqVoEXkNGfZPlIkExBZSHpJgqSIb6vcBzgntneo9X+iB7K0VRovkK20PDuxKvrt07dZ15qACGC364O1NKSK0+1tiRU6tPI8P1VI3JR2c8C5cnCg9zGCHinZR3iyY+ISA5G+bVEU8WWhwMuSuM2Vo5hmpDnX+p1K2XiuNdV2VblXPKZIfV3xahCZTvemVBt6WhrxeshhTBG2+Z2Qz0TSZSFkeX4ruo0WffOy7A2ybwhmCWryltgDe04Z+kkF+kmwZ6N/cQWWBqYPTILYfs/t9XWz3VyrFEJK+Uf6T+/JO3T0nGZZm4+kr5wH6YMbffrF5S8t0/DpIZfxExcyPnwxYAkI8TohMR4BPX4FPJL5j5ulZbnEbAqoVC6nphlVSulRLM5LFAgMBAAGjUDBOMAwGA1UdEwQFMAMBAf8wHQYDVR0OBBYEFBDgxJQIS7rzqWgJ5oSIuvg3hos6MB8GA1UdIwQYMBaAFBDgxJQIS7rzqWgJ5oSIuvg3hos6MA0GCSqGSIb3DQEBCwUAA4ICAQAiEpFVrLKCY99LxFmIqUvE/pL2ULzhBtZ0YQvWT4QgMABrC2ZKsG0Ttx2OyGXjI3Hl9sEDGdCTA2D0lN3gS44RYSxCK0RANF2wamY5vGgEA4uqQ6o/JIH9VLTEk7A7g+Lu4fl4lnIX3+kw2Thfk4CQ15cWI9tISUPfC4rO2vG9Ct9G3kkXvEj1r7cRH7wmGJcOWKoiMVsLNVIfCYPO4qZOnj+ZpVkxLxRTTjJzqXPYNsHlfywM3zoqtexDdwXFVGP8M/cW7pIp46UE0bvU9jHurF+ECwUykLn/GHG36DYyKAFeIsgpGsLZXW9jHLspSaVF3kR77qPpvlhuF29rfY4E2Bd2d24HHWR3mHMosdzKomatOZbeh6Dj0wdrq+GmRpC70knWrXxmNshYSJri41jvAUEnXVAkbQznxeCct6ST2JhYzuNP8OGVxO0xqs/Hpu9x1aSN0BUotO7ZJPqVR5qCpgEWE1BuWljutrhpI5/d0Oz/DKs14TDrPjlrYJgmR766MnqxQAzYAcM1nC6QcxAkc+N1BLGujI+WQbz9sAlUC0fOf3KuVqemt9XoIh5Z33kJ1QI/VKflCQtlvgSBnJX9zge/iAEImud+Z3MyPT6FxVtkEM9KwBWytT1TvJTyPeOK9SV+2w7xbCoLC8apPlamx13yDngxg0c3eJAd9+p2OA==</signature>
</webviewprovider>
<webviewprovider description="Google WebView Canary" packageName="com.google.android.webview.canary" availableByDefault="true">
<webviewprovider description="Google WebView Canary" packageName="com.google.android.webview.canary" availableByDefault="false">
<signature>MIIFxzCCA6+gAwIBAgIVAMtzFe17x7TJnswywhkvEJa+6EIQMA0GCSqGSIb3DQEBCwUAMHQxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpDYWxpZm9ybmlhMRYwFAYDVQQHEw1Nb3VudGFpbiBWaWV3MRQwEgYDVQQKEwtHb29nbGUgSW5jLjEQMA4GA1UECxMHQW5kcm9pZDEQMA4GA1UEAxMHQW5kcm9pZDAeFw0xOTA0MDUxNzIzMDZaFw00OTA0MDUxNzIzMDZaMHQxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpDYWxpZm9ybmlhMRYwFAYDVQQHEw1Nb3VudGFpbiBWaWV3MRQwEgYDVQQKEwtHb29nbGUgSW5jLjEQMA4GA1UECxMHQW5kcm9pZDEQMA4GA1UEAxMHQW5kcm9pZDCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAJbi91Ehi2xTxKCzeC6XO+COhaJp4dv6LqA8EfTBZEKoLQZ6FQQcDE4dirK78tBIbF4SQLkWt+Lxh3LnV6yfOt3hNmK3Rc9IhFAi0A56dGWazNo0pY8DiCftmPwnkPNT2aTZzmvWuDQHjLoMju2y7nDOliZThOybF9KMKSVsAr2Ahcmx4cRdyxCZKAutzsYOoBk77dFIO0kSaEPof9/Zlan4mAEnQ6GWgbK5wyIYN7DuaCDkJ5FYxUJxyp53/c5jKF/zZRPoGzXlt5e22yymD8MPSj716OolmKPFLUtzCVUnrm4xmmtgheWS1X43tRWit0rPl92bBHFiSECyIQriNS5ZlLuh712G6D7A3uaQqIIRmKVRE6DT3xRWI3l78cDquWIgU+x99qS9bb/txv/oaT1SIlM/rJij11iOISJCJ++vcyuR4j3pydkfl5ePH8YKAmUOCFlDJM3vOt7aqYpx3Ql5GFLeHOoSw4vOzwvMSROV/i6mXBv1d5O1o5XzDUWuIsFFroC1yz11/s547eV091BGuB60pyv7pXZY2EFOjjFcqWfsKrmcxzqCwel9bDd1WvsUY2Dt6Xl13hFMZ9ab2XiZCqA+OF+PrZKJYhTcpcdEjTYbVYEofst5Zg6WWL28WfQlDq0QwVDWFAsBdnegp40dNKwdkjp0LTXYdykzu8otAgMBAAGjUDBOMAwGA1UdEwQFMAMBAf8wHQYDVR0OBBYEFI0WYMtA13YCvaXP3y7SD7+BUoGGMB8GA1UdIwQYMBaAFI0WYMtA13YCvaXP3y7SD7+BUoGGMA0GCSqGSIb3DQEBCwUAA4ICAQAmJmDx+9Bfshs9PtNPb+2PEckvwheOurS15JAV10OTvmcpu0AON56RpItaMm29Uea5wYxaPcPwUQDum0vahHt7FIYxnPIxQ0FxsBDSVDn5veWMVXf5oSVDzsR61+x7i67Qk3dsMgRrY0PS/HZv+cFl0fb87b54mrtFIl8P9KjV5g5PUNJI2BIwLOKgnlU2kDrE1emm7lIKvoJqwb9JjlWl0lB+xXmntNQ+ZNvCzULnr1o0QblVK1Iowe++17GzOtrUlkATTmGx2exaeEEsZcQ23J6u8XycAk9+80aspTjZHC9aneQVcIdbCEBdWkF3xQ0TSnytT/0jLnLBnk2/kIV2ynw2zu/nMczgW2eUnfiIjqzBuP0uRMJ8NFPv/4Agmdqex+A233K9wYc92iflm/aVT3qw7wQntsmSesSBfZgLVVQ++dVD0bd5qYtyELCA2DNCaORU62bfNnPNTkQQo/FeyFDBLNzfWxiDINk6SxNxrZLgQFj9gil+CBgltrJ90Qv8Ats1ES66A0o+2T/j0GA9nOkfznID0VU1SlmOub/0SMwZL98WpkYqYTu/9AOqwzZBO/uT4ADANnMrMd4dtzZ1n0AdSvr1QyqO9XJRmW0k1PlmBM6iPU9p3JMVEqeQ7/zYnYq0tCK+k1mvA5BQ20NVSfea27X7/9EgGanchrFdCQ==</signature>
</webviewprovider>
<!-- The default WebView implementation -->
<!-- AXPR default WebView: AOSmium, matched by the AXPR release certificate.
AOSP/LOS WebView stays allowed and selectable as fallback. -->
<webviewprovider description="AXPR AOSmium WebView" packageName="org.axpos.aosmium_wv" availableByDefault="true">
<signature>
MIIDtTCCAp2gAwIBAgIUTJblq5nAW98vX9yhEc0Bby+gWy0wDQYJKoZIhvcNAQELBQAwaTELMAkGA1UEBhMCREUxCzAJBgNVBAgMAk5BMQswCQYDVQQHDAJOQTEYMBYGA1UECgwPQVhQLVJlc3VyZWN0aW9uMRQwEgYDVQQLDAtzdW5maXNoLWExNjEQMA4GA1UEAwwHcmVsZWFzZTAgFw0yNjA5MjkxNjM2NTBaGA8yMDU0MDIxNDE2MzY1MFowaTELMAkGA1UEBhMCREUxCzAJBgNVBAgMAk5BMQswCQYDVQQHDAJOQTEYMBYGA1UECgwPQVhQLVJlc3VyZWN0aW9uMRQwEgYDVQQLDAtzdW5maXNoLWExNjEQMA4GA1UEAwwHcmVsZWFzZTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALcLm1u2161EvRarM9Es8g+0jqkAFqaF92IzRLQUZSipI3/+8qIC3WBWTx6znMwNOaLH4quWkzWU7IEg0HXCf1mO26mMGTpilP/ZboB9QUntGVfJ9Eapt0PDI89hgXTxYYiX/FWnGX0VWSY4wimbhK2D3P4u7aTt1A6ZrGhzXTb99V9uPgo+S5Cvrmrue2H0oM8ps5Cb3gQ1bdDUCh1MOgHocpJP/i80CsGfJ/hPvcSEWt29zS5fsbr5B9lNXTghKqn2Bq/ze6PXl66zB6gjoAbnz0/BTOg1cB1fq7yRjUyAw9adeQKsuCBl0/LFA25ORDfDWwJ28q+wfpzwAcDdG+ECAwEAAaNTMFEwHQYDVR0OBBYEFDfChe+j21EqxGzjIDbiSHh3IloXMB8GA1UdIwQYMBaAFDfChe+j21EqxGzjIDbiSHh3IloXMA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQELBQADggEBAKmlveoSMQRgEZbGGt/MsuNowK8nQsJmYyFgEftZFxdXkpOVRs9W+LYHGyq9SfMaLBP+0g6+VR1riw8nVAkZoGoR/8tODRx2GhLCdHlEpcUx2+iXrfVbE1fyyIzNWUd18RvQ8dh50Fi95dsW4sk/mAVuQnvcVm4hLDbuFcXMLK2RMIECYKhfWIO+dsijOP9EgQebWCYTBEcaj9l5g1CmLfsuEe0L6dlKNBXUS3vgpQoXixN4l+Lzx4nIzpc2NDmpsbOB6bAND/qK/0PnQdBCXiZ5Cz5GJPHRJJ/F7cWUS7kLWN6RnzSaXhf4rlJVlmTvlvpJbkZU/fR9Ji3kaWHYjNE=
</signature>
</webviewprovider>
<!-- AOSP/LOS WebView stays available in the implementation list. -->
<webviewprovider description="AOSP WebView" packageName="com.android.webview" availableByDefault="true" />
</webviewproviders>
@@ -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)")
@@ -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 <kernel-tree>
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"