# 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 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 // 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, // 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 // Namespace, Key, Werte, ab/bis API val deepLinks: Map // Action/Component inkl. Fallbacks val features: Map // 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 |