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]>
269 lines
16 KiB
Markdown
269 lines
16 KiB
Markdown
# 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 |
|