Files
Pixelwall/docs/ARCHITECTURE.md
T
MrBlakeandClaude Opus 5.5 0a7caf6c5f Set up project skeleton (milestone 1)
- Architecture and feature overview in docs/ARCHITECTURE.md
- Gradle 9.8 / AGP 9.4 / Kotlin 2.4 with version catalog and convention plugins
- Modules :app, :core:*, :device-profiles, :feature:* (placeholder screens)
- Flavors core (no INTERNET, enforced by a build check) and full
- Material You theme, navigation, expert mode and dry-run settings (DataStore)
- Pixel 8-10 device detection, unit tests, GPLv3 license, English README with Obtainium badge

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

16 KiB
Raw Permalink Blame History

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)
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:

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)

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 (core bzw. full).
  • 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