Files
MrBlakeandClaude Opus 5.5 d1614e13a9 Fix Obtainium install: force Forgejo (Codeberg) source
The badge now uses an obtainium://app link with overrideSource and an APK
filter per flavor, because Obtainium does not detect self-hosted Gitea.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-15 22:12:07 +02:00

269 lines
16 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Pixelwall – Architektur- und Feature-Übersicht
Status: Entwurf v0.1 (vor Implementierungsbeginn)
Zielgeräte: Google Pixel 8 – Pixel 10, Stock-Android 15/16, ohne Root
Lizenz: GPLv3 · Vertrieb: F-Droid (reproducible builds)
---
## 1. Leitprinzipien
1. **Nichts ohne Bestätigung.** Jede schreibende Aktion durchläuft dieselbe Pipeline
(Plan → Kompatibilitätsprüfung → Bestätigung → Snapshot → Ausführen → Verifizieren → Protokoll).
Der Dry-Run-Modus bricht nach der Bestätigung ab und zeigt nur den Plan.
2. **Alles ist rückgängig machbar.** Vor jeder Änderung wird der Ist-Zustand gesichert;
jede Operation kennt ihre Umkehrung.
3. **Transparenz über Rechte.** Jede Einstellung nennt die nötige Berechtigungsstufe
(Normal / Shizuku / ADB-Grant). Nicht verfügbare Funktionen werden **ausgegraut mit Begründung**,
nie ausgeblendet.
4. **Offline und ohne Tracking.** Keine Analytics, keine Google-Bibliotheken (kein GMS, kein Firebase),
keine `INTERNET`-Berechtigung im Flavor `core` (siehe §9).
5. **Gerätewissen ist Daten, nicht Code.** Pixel-spezifische Pfade, Settings-Keys und
Besonderheiten liegen in `:device-profiles`; neue Hersteller kommen als weiteres Profil hinzu.
---
## 2. Modulstruktur
```
:app Application, Navigation, Hilt-Root, Theme (Material 3 / Dynamic Color)
:core:model Reine Kotlin-Datentypen (Finding, Risk, Operation, Profile, PrivilegeLevel …)
:core:domain Use Cases, Regelwerk/Checks, Scoring, Change-Pipeline (reines Kotlin, JVM-testbar)
:core:data Room (Änderungsprotokoll, Snapshots, Profile, Tracker-DB), DataStore, Repositories
:core:system Lesender Zugriff auf Android-APIs (PackageManager, Settings, KeyguardManager …)
:core:privileged PrivilegedExecutor-Interface + Implementierungen: Shizuku, WRITE_SECURE_SETTINGS, Fake
:core:ui Gemeinsame Compose-Komponenten (SettingCard, RiskBadge, PrivilegeChip, ScoreRing)
:core:testing Fakes, Test-Fixtures, Hilt-Testmodule
:device-profiles DeviceProfile-API + Pixel-Profil (Android 15/16) + Generic-AOSP-Fallback
:feature:dashboard Audit & Score
:feature:permissions Matrix Apps × Berechtigungen, AppOps, zeitlich begrenzte Rechte
:feature:network Privates DNS, Funk-/Scan-Einstellungen, (optional) Firewall
:feature:system System- & Google-Einstellungen, Google-Konto-Checkliste, inaktive Apps
:feature:profiles Profile, Import/Export, Regeln/Automatisierung
:feature:trackers Tracker-Analyse (Exodus-Signaturen)
:feature:monitor Hintergrund-Watcher, Zugriffs-Zeitleiste
:feature:changelog Änderungsprotokoll, Undo, „Alles zurücksetzen“
:feature:onboarding Rechte-Einrichtung (Shizuku, ADB-Anleitung), Expertenmodus
```
`:core` wird gegenüber der Vorgabe in Untermodule aufgeteilt, damit Domain-Logik und Scoring
ohne Android-Abhängigkeit als schnelle JVM-Unit-Tests laufen.
Abhängigkeitsrichtung: `feature → domain → model`, `data/system/privileged → domain (Interfaces)`.
Features kennen sich gegenseitig nicht; Navigation läuft über `:app`.
Build: Gradle Version Catalog (`gradle/libs.versions.toml`), Convention Plugins in `build-logic/`
(android-library, compose, hilt, feature). minSdk 34, targetSdk = aktuellste stabile API.
---
## 3. Berechtigungsstufen
| Stufe | Wie | Kann | Grenzen |
|---|---|---|---|
| **Normal** | keine Zusatzrechte | Lesen (Settings.Global/Secure lesbar, PackageManager, KeyguardManager, Build-Infos), Deep-Links per Intent | keine Änderungen an fremden Apps/Systemwerten |
| **ADB-Grant** | einmalig `adb shell pm grant <pkg> android.permission.WRITE_SECURE_SETTINGS` | `Settings.Secure/Global.put*` direkt aus der App, auch ohne laufendes Shizuku | kein `pm`, `appops`, `cmd` |
| **Shizuku** | Shizuku-API (Shell-UID 2000) | `pm grant/revoke`, `appops set/get`, `settings put`, `cmd …`, `dumpsys` | Shizuku muss nach jedem Neustart neu gestartet werden (Wireless Debugging) |
```kotlin
enum class PrivilegeLevel { NORMAL, ADB_GRANT, SHIZUKU }
interface PrivilegedExecutor {
val level: StateFlow<PrivilegeLevel> // aktuell verfügbare Stufe
suspend fun putSetting(ns: SettingsNamespace, key: String, value: String?): ExecResult
suspend fun shell(command: ShellCommand): ExecResult // nur Shizuku; typisierte Befehle, kein freier String
}
```
- `ShellCommand` ist eine **sealed class** (`PmRevoke`, `PmGrant`, `AppOpsSet`, `CmdWifi…`), keine
freien Strings → keine Injection, im Expertenmodus als roher Befehl darstellbar.
- Implementierungen: `ShizukuExecutor`, `SecureSettingsExecutor`, `CompositeExecutor` (wählt die
niedrigste ausreichende Stufe), `FakeExecutor` (Tests, Dry-Run-Vorschau).
- Fehlerfälle als typisierte Ergebnisse: `ShizukuNotRunning`, `PermissionMissing`, `CommandFailed(exitCode, stderr)`,
`UnsupportedOnDevice(reason)`.
---
## 4. Change-Pipeline & Undo
```
ChangeRequest ─► Planner ─► CompatibilityChecker ─► Bestätigungsdialog
│ (Dry-Run: Ende, Plan anzeigen)
▼
SnapshotStore ◄── Ist-Werte lesen
▼
PrivilegedExecutor.apply ─► Verify (neu lesen) ─► ChangeLog (Room)
```
- **Operation** (sealed): `PutSetting(ns, key, new)`, `SetPermission(pkg, perm, granted)`,
`SetAppOp(pkg, op, mode)`, `SetPrivateDns(mode, host)`, `ForceHibernate(pkg)` …
Jede Operation liefert `fun inverse(snapshot): Operation`.
- **ChangeSet** = atomare Gruppe (z. B. ein Profil anwenden). Schlägt eine Operation fehl, werden
die bereits ausgeführten des Sets zurückgerollt.
- **Undo**: pro Operation, pro ChangeSet und global („Alles zurücksetzen“ = alle offenen ChangeSets
in umgekehrter Reihenfolge, Ausgangszustand = erster Snapshot je Key).
- **Drift-Erkennung**: Der Watcher vergleicht Soll (letzte Änderung) mit Ist und meldet, wenn
ein System-Update oder der Nutzer etwas zurückgesetzt hat.
- Zeitlich begrenzte Rechte sind normale ChangeSets mit `expiresAt`; ein WorkManager-Job
(+ exakter Alarm als Backup) führt das Undo aus.
---
## 5. Regelwerk & Scoring
Ein **Check** ist deklarativ beschrieben und gerätespezifisch parametrisiert:
```kotlin
data class CheckDefinition(
val id: CheckId, // z. B. "network.private_dns"
val category: Category, // PERMISSIONS, NETWORK, SYSTEM, GOOGLE, APPS
val risk: Risk, // LOW, MEDIUM, HIGH
val requiredLevelToRead: PrivilegeLevel,
val fix: Fix?, // Operationen oder DeepLink
val compatibilityTags: Set<CompatTag>, // ANDROID_AUTO, BANKING, WEARABLE, FIND_MY_DEVICE …
)
```
Ergebnis je Check: `PASS`, `FAIL`, `UNKNOWN` (nicht lesbar ohne höhere Stufe), `UNSUPPORTED`.
**Score** (Vorschlag, in Unit-Tests festgeschrieben):
- Gewicht: HIGH = 10, MEDIUM = 5, LOW = 2
- Teilscore = 100 × (Summe Gewichte PASS) / (Summe Gewichte PASS + FAIL); UNKNOWN/UNSUPPORTED zählen nicht
- Gesamtscore = gewichteter Mittelwert der Teilscores (System 30 %, Berechtigungen 25 %, Netzwerk 20 %, Apps 15 %, Google 10 %)
- Harte Deckel: z. B. keine Bildschirmsperre → Gesamtscore max. 40; Patch > 90 Tage alt → max. 70
Geplante Audit-Checks (Stufe zum Lesen):
| Check | Quelle | Stufe |
|---|---|---|
| Bildschirmsperre | `KeyguardManager.isDeviceSecure` | Normal |
| Verschlüsselung | `DevicePolicyManager.storageEncryptionStatus` (FBE) | Normal |
| Patch-Stand | `Build.VERSION.SECURITY_PATCH` vs. Referenzdatum im Regelwerk | Normal |
| Bootloader / Verified Boot | `ro.boot.verifiedbootstate`, `ro.boot.flash.locked` (über Shizuku `getprop`, sonst Key-Attestation) | Normal/Shizuku |
| Entwickleroptionen, USB-Debugging | `Settings.Global.DEVELOPMENT_SETTINGS_ENABLED`, `ADB_ENABLED` | Normal |
| Unbekannte Quellen | Apps mit `REQUEST_INSTALL_PACKAGES` + AppOp-Status | Normal/Shizuku |
| Privates DNS | `Settings.Global private_dns_mode/_specifier` | Normal |
| 2G erlaubt | `TelephonyManager.getAllowedNetworkTypesForReason` (Shell) | Shizuku |
| WLAN-/BT-Scanning | `wifi_scan_always_enabled`, `ble_scan_always_enabled` | Normal |
| Werbe-ID | nur Deep-Link (GMS-intern, keine API ohne GMS-Bibliothek) | Normal (manuell) |
| Sperrbildschirm-Benachrichtigungen | `lock_screen_show_notifications`, `…_allow_private_notifications` | Normal |
| Smart Lock / Trust Agents | `enabled_trust_agents` | Normal/Shizuku |
| Bedienungshilfen-Dienste | `enabled_accessibility_services` | Normal |
| Geräteadministratoren | `DevicePolicyManager.activeAdmins` | Normal |
| VPN-Status | `ConnectivityManager` + `always_on_vpn_app` | Normal |
| Benachrichtigungszugriff | `enabled_notification_listeners` | Normal |
| Memory Tagging (MTE), Titan M2, Privater Bereich | Geräteprofil + Properties | Normal/Shizuku |
Hinweis: Lesbarkeit einzelner Secure-Keys wurde für Apps mit targetSdk ≥ 31 eingeschränkt; das
Geräteprofil hält je Android-Version fest, welche Keys lesbar sind, sonst fällt der Check auf
Shizuku zurück oder ergibt `UNKNOWN`.
---
## 6. Geräteprofile (`:device-profiles`)
```kotlin
interface DeviceProfile {
fun matches(device: DeviceInfo): Boolean // Hersteller, Modell (shiba, husky, tokay, caiman, komodo, comet, …), SDK
val settingsKeys: Map<SettingId, SettingKeySpec> // Namespace, Key, Werte, ab/bis API
val deepLinks: Map<DeepLinkId, IntentSpec> // Action/Component inkl. Fallbacks
val features: Map<FeatureId, FeatureSupport> // Supported / Unsupported(reasonRes)
}
```
- Profile als Kotlin-DSL (typsicher, testbar), nicht als JSON → keine Laufzeit-Parsing-Fehler.
- `PixelProfile` (Android 15/16), `AospFallbackProfile` für unbekannte Geräte.
- Deep-Links mit Fallback-Kette (spezifische Pixel-Seite → AOSP-Action → `Settings.ACTION_SETTINGS`).
- Unit-Tests stellen sicher, dass jedes Feature für jedes Profil eine Aussage hat.
---
## 7. Features (Kurzfassung)
1. **Dashboard**: Score-Ring, Teilscores, Befundliste nach Risiko, Ein-Klick-Fix/Deep-Link.
2. **Berechtigungen**: Matrix (LazyColumn mit fixierter App-Spalte, horizontal scrollbare
Berechtigungen), Filter (System/Nutzer, unbenutzt seit X Tagen via `UsageStatsManager`,
Trackeranzahl), Massenaktionen, AppOps über Shizuku, zeitlich begrenzte Rechte.
3. **Tracker**: Exodus-Signaturen als Asset in Room; Erkennung über Klassennamen aus den
DEX-Dateien der APKs (`sourceDir` lesbar, eigener schlanker DEX-String-Parser, kein Netz).
4. **Netzwerk**: Privates DNS mit Presets + Validierung (Hostname-Syntax; Erreichbarkeit nur
ohne Netz-Berechtigung nicht prüfbar → Verify über `LinkProperties.isPrivateDnsActive`),
2G, Scanning, NFC, offene WLANs. Firewall: siehe §9.
5. **System & Google**: Schalter wo technisch möglich, sonst Deep-Link + Checkliste
(Google-Konto-Seiten öffnen im Browser, Häkchen lokal gespeichert).
6. **Profile**: Ausgewogen / Streng / Paranoid / Unterwegs-Auto als Mengen von Operationen;
eigene Profile; Export als JSON mit SHA-256-Prüfsumme und Ed25519-Signatur (Schlüssel lokal
im Android Keystore erzeugt); Regeln (WLAN-SSID, Zeitfenster) über WorkManager/Broadcasts.
7. **Überwachung**: periodischer WorkManager-Job (≥ 6 h, nur bei nicht-niedrigem Akku) +
`PACKAGE_ADDED`-Receiver; Zugriffsverlauf über `appops`/`dumpsys` (Shizuku), da die
Privacy-Dashboard-API (`PermissionManager` Usage) System-API ist.
---
## 8. Qualität & Tests
- JVM-Unit-Tests: Scoring, Check-Auswertung, Operation-Inversen, Profil-Diffs, Import-Validierung, DEX-Parser.
- Instrumented Tests (Emulator API 34/35/36): Dashboard laden, Änderung mit `FakeExecutor` anwenden + Undo, Dry-Run.
- Compose-UI-Tests inkl. Semantik für TalkBack; Screenshot-Prüfung bei 200 % Schriftgröße.
- Lint/Detekt im Build; `strings.xml` de/en, Lint-Regel gegen Hardcoded-Texte.
- F-Droid: keine proprietären Abhängigkeiten, feste Versionen, `dependenciesInfo { includeInApk = false }`.
---
## 9. Bekannte Grenzen & offene Fragen
**Technisch nicht (vollständig) möglich ohne Root – wird als Deep-Link/Checkliste umgesetzt:**
- Werbe-ID löschen, Google-Nutzungs-/Diagnosedaten, Google-Konto-Aktivitäten (GMS-intern bzw. Web).
- Kamera-/Mikrofon-Kacheln programmatisch in die Schnelleinstellungen legen (nur eigene Kacheln
per `requestAddTileService`); die Sensorsperre selbst ist via Shizuku (`cmd sensor_privacy`) zu prüfen.
- Ein paar Shell-Befehle (2G, Sensor-Privacy) müssen auf echten Pixel-Geräten verifiziert werden;
bis dahin `UNSUPPORTED` mit Begründung.
**Entscheidungen (2026-10-15):**
1. **Name/Package**: App „Pixelwall“, applicationId `org.pixelwall.app`, Namespace `org.pixelwall.*`.
2. **Firewall**: zwei Product Flavors.
- `core`: **ohne** `INTERNET`. Firewall nur als Sperre pro App (WLAN/Mobil/Roaming) per `VpnService`.
- `full`: mit `INTERNET`. Zusätzlich DNS-Weiterleitung mit lokalem Logging, Hosts-Blocklisten
und optionale Regelwerk-Updates (standardmäßig aus).
Hinweis in der UI: Es kann nur ein VPN gleichzeitig aktiv sein.
3. **Build-Umgebung**: JDK 21 und Android-SDK-Kommandozeilentools werden nur für den Nutzer installiert (`~/Android/Sdk`).
**Noch zu klären (blockiert nichts):**
- `QUERY_ALL_PACKAGES`: nötig für Audit und Tracker-Analyse; auf F-Droid unproblematisch.
- Exodus-Daten: Lizenz der Tracker-Signaturen vor dem Bündeln prüfen (Attribution in der App).
---
## 9a. Verteilung: F-Droid und Obtainium
Pixelwall soll direkt über **Obtainium** aus den Releases auf `git.mrblake.cc` (Gitea) aktualisierbar sein:
- **Releases mit APK-Assets**: Jeder Git-Tag `vX.Y.Z` bekommt ein Gitea-Release mit
`pixelwall-core-vX.Y.Z.apk` und `pixelwall-full-vX.Y.Z.apk` (universelle APKs, kein ABI-Split nötig).
In Obtainium wählt man den Flavor per APK-Filter-Regex (`pixelwall-core-` bzw. `pixelwall-full-`).
- **Quelle „Forgejo (Codeberg)“**: Obtainium erkennt den eigenen Gitea-Host nicht automatisch. Der README-Button
nutzt deshalb `obtainium://app/{JSON}` mit `overrideSource: "Codeberg"` und APK-Filter (wie bei MrTerm).
- **Versionen**: `versionName` = Tag ohne „v“; `versionCode` steigt streng monoton
(Schema `MAJOR*10000 + MINOR*100 + PATCH`), damit Obtainium Updates sicher erkennt.
- **Stabiler Signaturschlüssel**: Release-Keystore liegt außerhalb des Repos; Pfad und Passwörter kommen aus
Umgebungsvariablen bzw. `~/.gradle/gradle.properties`. Ein Schlüsselwechsel würde Updates brechen.
- **Getrennte applicationIds** (`org.pixelwall.app`, `org.pixelwall.app.full`): beide Flavors parallel installierbar.
- Ein Gradle-Task `dist` legt die signierten, korrekt benannten APKs plus SHA-256-Prüfsummen in `dist/` ab;
das Hochladen ins Gitea-Release erfolgt per API nur nach ausdrücklicher Freigabe.
- README und Release-Texte (Titel, Notes) sind **englisch**.
- Zugangsdaten (`.giteatoken`) sind per `.gitignore` ausgeschlossen und werden nie committet oder ausgegeben.
## 10. Meilensteine
| # | Inhalt | Abnahme |
|---|---|---|
| M1 | Projektgerüst: Module, Version Catalog, Convention Plugins, Hilt, Theme, Navigation, de/en | `./gradlew assembleDebug test lint` grün |
| M2 | Audit/Dashboard (nur lesend), Geräteprofil Pixel, Scoring | Unit-Tests Scoring/Checks |
| M3 | Berechtigungsmatrix (lesend + Deep-Links) | UI-Tests Matrix |
| M4 | Shizuku + ADB-Grant + Change-Pipeline + Undo + Dry-Run + Expertenmodus | Tests mit FakeExecutor |
| M5 | Netzwerk (Privates DNS, Funk), ggf. Firewall | – |
| M6 | Profile, Import/Export, Regeln, Kompatibilitätswarnungen | – |
| M7 | Tracker-Analyse | DEX-Parser-Tests |
| M8 | Überwachung, Zeitleiste | WorkManager-Tests |
| M9 | Release-Pipeline: Signatur, `dist`-Task, Gitea-Release für Obtainium, F-Droid-Metadaten; finale README und Release-Notes **auf Englisch** | Obtainium installiert & aktualisiert |