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