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]>
This commit is contained in:
2026-10-15 22:02:14 +02:00
co-authored by Claude Opus 5.5
commit 0a7caf6c5f
117 changed files with 3175 additions and 0 deletions
+266
View File
@@ -0,0 +1,266 @@
# 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 (`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 |