Files

22 KiB
Raw Permalink Blame History

Firebase

Das Firebase-Projekt heißt MetalCircle. Android ist dauerhaft als de.pinguholic.concerts registriert. Manuelle Testnachrichten funktionieren bereits. Automatischer Versand für Freundschaftsanfragen, Direktnachrichten und Veranstaltungseinladungen ist implementiert und benötigt einen separaten serverseitigen Zugang sowie PUSH_ENABLED=true.

Unterschiedliche Konfigurationsdateien

  • android/android/app/google-services.json: lokale, Git-ignorierte Android-Client-Konfiguration. Projekt-/App-Kennungen und der Client-API-Key werden vom Build in die APK übernommen; sie sind kein Backend-Privatschlüssel.
  • Service-Account-JSON: privater Schlüssel für das Backend. Niemals in Git, APK, Docker-Image, Webassets, Chat, Wiki oder Logs aufnehmen. Die Android-Datei ersetzt diesen Zugang nicht.

Lokale Entwicklungs-/Testumgebung

  1. In Firebase MetalCircle auswählen und die tatsächliche Projekt-ID notieren; sie kann vom Anzeigenamen abweichen.
  2. In der zugehörigen Google Cloud Console die Firebase Cloud Messaging API (HTTP v1) prüfen/aktivieren.
  3. Der lokale Test verwendet den dedizierten Service Account metalcircle-push-local@metalcircle-30d9b.iam.gserviceaccount.com. Für dessen Versand ist die Projektrolle Firebase Cloud Messaging API Admin, Rollen-ID roles/firebasecloudmessaging.admin, erforderlich. Diese genaue Rolle ist diesem lokalen Test-Service-Account auf Projektebene im Projekt metalcircle-30d9b zugewiesen. Sie enthält cloudmessaging.messages.create. Nicht mit ähnlich benannten Firebase-Administrationsrollen verwechseln; OAuth-Tokenbezug allein beweist keine Versandberechtigung. Keine persönlichen oder Gitea-Zugänge verwenden. Siehe Firebase IAM und FCM-Rollen.
  4. Den privaten JSON-Schlüssel geschützt außerhalb des Repositories und Docker-Buildkontexts speichern. Der Betreiber verwaltet die Datei. Firebase Admin Setup beschreibt Service-Account-Dateien.
  5. Dateirechte einschränken, beispielsweise chmod 600 /absoluter/pfad/firebase-service-account.json. Keine Inhalte ausgeben.
  6. In der lokalen .env die folgenden Werte selbst eintragen:
PUSH_ENABLED=true
FIREBASE_PROJECT_ID=<tatsaechliche-test-projekt-id>
FIREBASE_SERVICE_ACCOUNT_FILE=/absoluter/pfad/firebase-service-account.json
  1. In der eigenen lokalen HTTP-Testumgebung starten:
COOKIE_SECURE=false docker compose -f compose.yml -f compose.push.yml up -d --build web

compose.push.yml liest FIREBASE_SERVICE_ACCOUNT_FILE als absoluten Host-Pfad, bindet diese Datei read-only unter /run/secrets/firebase-service-account.json in den web-Container ein und setzt GOOGLE_APPLICATION_CREDENTIALS=/run/secrets/firebase-service-account.json. Die Quelldatei muss existieren. Der Sender prüft, dass FIREBASE_PROJECT_ID zur Projekt-ID des Credentials passt. Die Android-App muss dasselbe Firebase-Projekt verwenden. Schlüsseldatei, .env und Token gehören weder ins Repository noch in ein Image.

Cloud-Staging richtet der Betreiber separat ein; dort hinter HTTPS COOKIE_SECURE=true lassen. Codex auf PinguCore greift nicht automatisch darauf zu. Produktion bekommt später eigene Credentials, keine kopierten Testschlüssel.

Verifizierter lokaler E2E-Stand

Am 15.09.2026 wurde der vollständige lokale Weg mit dem Android-Testgerät und dem lokalen Backend erfolgreich geprüft. Nach Zuweisung von roles/firebasecloudmessaging.admin verschwand der vorherige HTTP-403-Fehler PERMISSION_DENIED. Das Service-Account-Credential stimmte mit FIREBASE_PROJECT_ID=metalcircle-30d9b überein; der Firebase Admin SDK Versand wurde vom FCM HTTP-v1-Endpunkt angenommen.

Freundschaftsanfrage und Veranstaltungseinladung wurden im Android Notification Manager nachgewiesen. Die Direktnachrichten-Benachrichtigung wurde auf dem Gerät gesehen; Antippen öffnete den zugehörigen Chat. Alle drei Benachrichtigungen verwenden generische Vorschautexte ohne Nachrichtentext oder private Veranstaltungsdetails. Die installierte Test-App war 1.1.0-debug, Package ID de.pinguholic.concerts.

Für eine Wiederholung lokale App und Backend verwenden; mit adb reverse tcp:8080 tcp:8080 wird der Android-Testbuild an den lokalen Port 8080 weitergeleitet. Keine Cloud-Staging- oder Produktionsumgebung verwenden. In der Datenbank bedeutet push_notifications.state='sent', dass der Firebase-Sendeaufruf angenommen wurde; für einen vollständigen E2E-PASS zusätzlich den tatsächlichen Android-Empfang über Notification Manager oder gleichwertige Gerätebeobachtung prüfen.

Automatisierte Backend-Tests simulieren Firebase und belegen nicht die Cloud-IAM-Berechtigung. Die lokale Suite mit 53 Tests und der Android-Debug-Build waren erfolgreich; der oben beschriebene Gerätetest hat zusätzlich den echten FCM-Versand und Empfang bestätigt. Bei erneutem configuration-/403-Fehler Projekt-ID, aktiven Service Account, dessen roles/firebasecloudmessaging.admin-Zuweisung im richtigen Firebase-Projekt, API-Aktivierung und Secret-Mount prüfen. Logs enthalten absichtlich keine vollständigen Firebase-Fehlerantworten oder Secrets. Keine Legacy-Server-Keys einsetzen.

Pre-Production Deployment

Bestand und Grenzen

Im Repository gibt es compose.yml für Web/DB, die optionale Secret-Einbindung compose.push.yml und eine lokale, ignorierte compose.dev.yml mit Reload/Quellcode-Mount. Die vom Betreiber bestätigte aktuelle Pre-Production-Adresse ist https://konzerte.pinguholic.de/; das Repository bleibt kai/pingu-concerts. Der Server-Checkout liegt unter /opt/pingu-concerts; die aktive Konfiguration liegt außerhalb unter /home/kai/.config/metalcircle/preprod.env. Die folgenden Befehle werden vom Betreiber im bestehenden Pre-Production-Checkout ausgeführt. Den vorhandenen Compose-Projektnamen und gegebenenfalls serverseitige Overrides beibehalten, damit dieselbe Pre-Production-Datenbank und dieselben Upload-Volumes verwendet werden. Keine zweite Installation mit denselben festen Containernamen auf demselben Docker-Host starten.

Neu ist compose.preprod.yml: Es übernimmt per extends die Einbindung aus compose.push.yml, aktiviert Push und erzwingt sichere Cookies. Start mit compose.yml + compose.preprod.yml; die dritte Datei muss nicht zusätzlich angegeben werden. Pre-Production benötigt HTTPS am vorhandenen Reverse Proxy. Die Basisdatei behält PUSH_ENABLED=false als Standard; lokale Konfiguration und spätere Produktion werden nicht automatisch aktiviert.

Einmalig in Google Cloud: eigener Zugang

  1. Projekt metalcircle-30d9b auswählen. Unter APIs & Services → Enabled APIs prüfen, dass Firebase Cloud Messaging API (fcm.googleapis.com, HTTP v1) aktiviert ist; nur falls nötig aktivieren.
  2. Unter IAM & Admin → Service Accounts → Create service account den Namen und die ID metalcircle-push-preprod anlegen. Erwartete Adresse: metalcircle-push-preprod@metalcircle-30d9b.iam.gserviceaccount.com.
  3. Diesem Service Account im genannten Projekt Firebase Cloud Messaging API Admin, exakt roles/firebasecloudmessaging.admin, zuweisen. Der Versand benötigt cloudmessaging.messages.create; die vordefinierte FCM-API-Rolle enthält diese Berechtigung. Keine Editor-/Owner-/Firebase-Admin-Rolle und keine zusätzliche Service Account Token Creator-Rolle für die hier verwendete JSON-Schlüssel-Authentifizierung vergeben. Nicht die ähnlich benannte Rolle roles/firebasenotifications.admin auswählen. Offizielle Rollenübersicht, FCM HTTP v1.
  4. Beim neuen Service Account unter Keys → Add key → Create new key → JSON einen Schlüssel erzeugen und über einen sicheren Übertragungsweg auf den Pre-Production-Host bringen. Der Betreiber führt diesen Schritt aus. Nicht die Admin-SDK-Schaltfläche für einen allgemeinen Standard-Service-Account verwenden und keine Organisationsrichtlinie für verbotene Schlüssel umgehen.

Der lokale Account metalcircle-push-local@metalcircle-30d9b.iam.gserviceaccount.com und sein Schlüssel bleiben ausschließlich lokal. Produktion bekommt später einen weiteren eigenen Account/Schlüssel. Unterschiedliche Service Accounts im selben Firebase-Projekt trennen Credentials, aber nicht die FCM-Token-Zielgruppe: Die separate Backend-Datenbank und frische Registrierung sind deshalb zwingend.

A. Einmalig auf dem Pre-Production-Host

Verbindlicher Host-Pfad: /home/kai/.secrets/metalcircle/firebase-push-preprod.json. Der Key gehört dem Deployment-Benutzer kai und hat Modus 600, das Verzeichnis Modus 700. Der bestehende read-only Mount bleibt /run/secrets/firebase-service-account.json; create_host_path: false verhindert Verzeichnisse anstelle fehlender Dateien. Bei Rootless-Docker oder abweichender Container-UID muss der Betreiber die UID-Abbildung prüfen; nicht auf weltweite Leserechte ausweichen.

Die gesamte aktive Konfiguration liegt in /home/kai/.config/metalcircle/preprod.env (Eigentümer kai, Modus 600). Vorlage: config/preprod.env.example. Neben vollständigen DB-/Admin-/Gitea-Werten benötigt Firebase:

PUSH_ENABLED=true
FIREBASE_PROJECT_ID=metalcircle-30d9b
FIREBASE_SERVICE_ACCOUNT_FILE=/home/kai/.secrets/metalcircle/firebase-push-preprod.json
COOKIE_SECURE=true

GOOGLE_APPLICATION_CREDENTIALS setzt Compose intern. Kein ~ als Pfad und keine Credential-Inhalte in diese Anleitung übernehmen. Die alte Checkout-.env wird vom Deployment nicht mehr gelesen und nicht automatisch gelöscht. Die vollständige Migrationsanleitung beschreibt Übernahme, Prüfung, Deployment und das spätere manuelle Archivieren.

B. Deployment im Pre-Production-Checkout

cd /opt/pingu-concerts
./scripts/deploy-preprod.sh --check
./scripts/deploy-preprod.sh

Das Script verlangt einen sauberen main-Checkout und verwendet durchgehend --env-file /home/kai/.config/metalcircle/preprod.env mit compose.yml + compose.preprod.yml. Pflichtwerte, Host-Dateirechte und externer Firebase-Key werden vor dem Build geprüft. Vor dem Containerwechsel müssen anschließend Firebase und Gitea in temporären Containern PASS liefern. Danach wird die Env-Datei außerhalb des Checkouts gesichert (700/600, letzte zehn Kopien) und nur web mit up -d --no-deps web aktualisiert. Beide Preflights laufen danach erneut im tatsächlichen Webcontainer. Ablauf, Abbruchverhalten und manueller Fallback.

Der bestehende Firebase-Preflight bleibt offline: Push aktiv, Containerpfad, reguläre Datei, restriktive Rechte, read-only Mount, gültiges Credential, Projekt und exakter Pre-Production-Service-Account. PASS belegt kein IAM und keine FCM-Zustellung. Gitea wird zusätzlich read-only als metalcircle-bot auf Erreichbarkeit geprüft; keine Test-Issues oder Attachments werden erstellt.

Vor Aktivierung bestätigen: Die vorhandene DB ist die Pre-Production-DB, keine lokalen Daten/Sessions wurden importiert. Frisch in Pre-Production anmelden und das Android-Gerät dort registrieren. Keine Local-Datenbank kopieren.

Eine bewusste Notabschaltung des Pushversands erfolgt außerhalb des normalen Deployment-Scripts: In der externen preprod.env PUSH_ENABLED=false setzen und den Webcontainer ausdrücklich mit --env-file /home/kai/.config/metalcircle/preprod.env, compose.yml + compose.push.yml aktualisieren. Das reguläre Pre-Production-Override erzwingt Push und ist für diese Ausnahme wegzulassen; COOKIE_SECURE=true in der externen Env beibehalten. Kein down, kein DB-Recreate. Für den nächsten normalen Deployment-Lauf Push wieder aktivieren und beide Preflights bestehen lassen.

Schlüsselrotation: neue Pre-Production-Datei geschützt am Host ersetzen und den Webcontainer über den normalen Deployment-Weg neu erstellen, damit Mount und SDK-Credential erneuert werden. Während eines laufenden Deployments Env/Key nicht bearbeiten.

Android mit Pre-Production verbinden

android/capacitor.config.ts verwendet METALCIRCLE_SERVER_URL. Die Standardadresse https://konzerte.pinguholic.de/ ist bereits eure bestätigte aktuelle Pre-Production. Eine App mit dieser Zieladresse benötigt für den neuen Backend-Zugang keine neue APK. Nur einen noch auf localhost zeigenden Testbuild oder eine App mit abweichender Adresse neu synchronisieren und bauen. Die Zieladresse wird beim Sync in die APK übernommen, nicht durch eine Backend-.env geändert:

# Im Verzeichnis android/ auf dem Build-Rechner:
METALCIRCLE_SERVER_URL=https://konzerte.pinguholic.de/ METALCIRCLE_LOCAL_TEST=0 npm run sync
npm run build
adb install -r android/app/build/outputs/apk/debug/app-debug.apk

Vor dem Wechsel am alten Backend abmelden. Package ID bleibt de.pinguholic.concerts; die APK ersetzt bei passender Signatur die bestehende Installation. Bei Signaturkonflikt nicht ungefragt App-Daten löschen. Kein adb reverse für das HTTPS-Pre-Production-Backend verwenden. Danach auf Pre-Production als Empfänger B anmelden, Benachrichtigungen erlauben und unter „Mein Profil“ die Registrierung bestätigen lassen. Für weitere Local-/Prod-Builds den gewünschten Ziel-URL erneut ausdrücklich setzen.

native-push.js verwendet ausschließlich relative /api/push/...-URLs und dieselbe authentifizierte Origin wie die geladene Web-App. /api/push/session liefert die aktuelle Session-Bindung, POST /api/push/devices registriert/aktualisiert nur den eingeloggten Benutzer. Geräte-ID und Token sind eindeutig; dieselbe Geräte-ID aktualisiert den Token. Ein Sitzungs-/Umgebungswechsel löscht im nativen Plugin den alten Token, leert eigene Notifications und registriert neu. Logout entfernt die serverseitige Session samt Geräte-/Outbox-Verknüpfungen; Kategorie-Abwahl und verweigerte Android-Berechtigung sind zusätzlich berücksichtigt. Datenbanken und aktive Geräte-/Session-Tabellen zwischen Umgebungen nicht kopieren.

Smoke-Test: Backend bis zum Antippen

Zwei Pre-Production-Testkonten A/B verwenden. B ist auf dem Android-Testgerät angemeldet, alle drei Kategorien sind aktiv. Vor jedem Ereignis die App mit Home in den Hintergrund bringen (nicht per „Stopp erzwingen“ beenden). Den Browser von B ebenfalls nicht im Chat oder in Nachrichten/Einladungen offen lassen. Uhrzeit und IDs der Testereignisse notieren. Erst nach technischer Erfassung die Benachrichtigung antippen.

Test Aktion A / Voraussetzungen Backend-Nachweis Android-Vorschau und Ziel
1 Neue Freundschaftsanfrage an B, noch keine offene/bestätigte Verbindung friendships mit status='pending', Outbox friend_request „Neue Freundschaftsanfrage“ / „New friend request“; Tap führt zu A's Profil
2 B zu einer privaten Testveranstaltung einladen event_invitations mit B und viewed_at IS NULL, Outbox event_invitation „Neue Veranstaltungseinladung“ / „New event invitation“; Tap öffnet Veranstaltung
3 Nach Annahme der Freundschaft eine neue DM an B senden direct_messages mit B und read_at IS NULL, Outbox direct_message „Neue Nachricht Du hast eine neue Nachricht.“ / „New message You have a new message.“; Tap öffnet Chat mit A

Für die Backend-Prüfung können selektive SQL-Abfragen benutzt werden (IDs/Zeitpunkt eingrenzen; kein SELECT *):

sudo docker compose --env-file /home/kai/.config/metalcircle/preprod.env -f compose.yml -f compose.preprod.yml exec -T db \
  sh -c 'exec psql -U "$POSTGRES_USER" -d "$POSTGRES_DB"' <<'SQL'
SELECT user_id, platform, app_version, created_at, last_seen_at FROM push_devices ORDER BY last_seen_at DESC LIMIT 10;
SELECT user_id, language, friend_request, direct_message, event_invitation FROM notification_preferences ORDER BY user_id;
SELECT id, kind, object_id, state, attempts, created_at FROM push_notifications ORDER BY created_at DESC LIMIT 15;
SELECT id, requester_id, addressee_id, status FROM friendships ORDER BY id DESC LIMIT 5;
SELECT concert_id, user_id, viewed_at FROM event_invitations ORDER BY concert_id DESC LIMIT 5;
SELECT id, sender_id, recipient_id, read_at FROM direct_messages ORDER BY id DESC LIMIT 5;
SQL

Keiner dieser Befehle gibt Token, Session-Bindung, Schlüssel oder private Nachrichtentexte aus. state='sent' wird erst gesetzt, wenn firebase_admin.messaging.send erfolgreich zurückgekehrt ist. Dies belegt die FCM-Annahme, nicht die Zustellung. Der aktuelle Sender speichert die zurückgegebene Firebase-Message-ID nicht; stattdessen ist push_notifications.id als Android-Notification-Tag korrelierbar. Keine neuen SDK-Debuglogs einschalten.

Für einen technischen Nachweis direkt vor dem Tap auf dem ADB-Rechner die zur Outbox passende UUID im aktiven Notification-Manager-Bestand erfassen. Möglich ist die folgende Ausgabe mit ausschließlich UUID-Tags; keine vollständigen Notification-Dumps speichern oder teilen:

adb shell dumpsys notification | python3 -c '
import re, sys
active = False
for line in sys.stdin:
    if line.strip() == "Notification List:":
        active = True
        continue
    if active and line.strip() and len(line) - len(line.lstrip()) <= 2:
        active = False
    if active and "NotificationRecord(" in line and "pkg=de.pinguholic.concerts " in line:
        tag = re.search(r"\btag=([a-f0-9-]{36})\b", line)
        if tag:
            print("active MetalCircle notification_id=" + tag.group(1))
'

Hersteller/Android-Versionen können den Dump anders gliedern. Leere Ausgabe ist deshalb kein alleiniger Nachweis fehlender Zustellung. Alternativ über die verbundene WebView nur notifications.map(n => ({tag: n.tag, id: n.id})) des Capacitor-Aufrufs PushNotifications.getDeliveredNotifications() betrachten, nie dessen komplettes Objekt (data enthält Session-Metadaten). Notification-Tag mit der Outbox-ID vergleichen, generische Vorschau am Gerät prüfen, dann Tap/Ziel prüfen. Ohne belastbaren Gerätebeleg bleibt der Android-Empfang nicht verifiziert.

Alle drei Tests zusätzlich mit B in EN wiederholen; ursprüngliche Sprache/Präferenzen danach wiederherstellen. Eine Kategorie deaktivieren: Ereignis muss entstehen, ein neuer Versandauftrag darf nicht entstehen. Im Vordergrund separat prüfen: lokale Banner-Aktion und native Anzeige; Verhalten siehe Push Notifications. Testveranstaltung/DM/Anfragen anschließend gezielt entfernen bzw. ursprüngliche Verbindung wiederherstellen; keine fremden Daten pauschal löschen.

Troubleshooting

  • Preflight abgebrochen: credential_service_account_mismatch bedeutet falscher Account (z. B. Local-Key), credential_project_mismatch ein anderes Projekt. credential_permissions_too_broad verlangt engere Host-Rechte; credential_mount_not_read_only eine korrigierte Compose-Einbindung. Bei credential_missing_unreadable_or_invalid Dateiexistenz, Leserechte/UID-Mapping und Gültigkeit des neu erzeugten Schlüssels prüfen, keinen Inhalt ausgeben.
  • configuration/403 nach Preflight-PASS: Preflight prüft kein IAM. Exakte Account-Zuweisung von roles/firebasecloudmessaging.admin auf metalcircle-30d9b, API-Aktivierung, nicht widerrufenen Schlüssel und Netzwerkzugang zu Google prüfen. IAM-Propagation abwarten. Der Worker loggt bewusst nur Fehlerkategorien, keine volle Google-Antwort. Für die drei aktuellen Push-Arten nicht roles/firebasenotifications.admin mit der HTTP-v1-Rolle verwechseln.
  • Kein Outbox-Eintrag: PUSH_ENABLED, korrektes Backend in der APK, frische Registrierung von B, Kategorien, Selbstbenachrichtigung und tatsächliches neues Ereignis prüfen. Ein reines Backend-Update ändert die APK-Ziel-URL nicht.
  • dropped: Auftrag kann gelesen, abgelaufen, abgewählt, blockiert oder durch Session-/Tokenwechsel ungültig sein. Direktnachrichten vor dem Versand nicht öffnen; der Chat-GET setzt read_at. Bereits erledigte Ziele führen beim Tap absichtlich zur Übersicht.
  • sent, aber nichts sichtbar: FCM-Annahme getrennt vom Android-Empfang bewerten. Vor dem Tap erfassen; Android-App-/Kanalberechtigung, Benachrichtigungskanal, Energiesparen/Netzwerk, Force-Stop und Gerätewechsel prüfen. Identische Wiederholungs-Tags ersetzen dieselbe Meldung; neue Outbox-IDs sind verschieden. Die App löscht eigene Notifications bei Session-Wechsel.
  • Chat-Push im Vordergrund: Capacitor 6.0.5 erhält den Payload im nativen Callback; presentationOptions: ['sound', 'alert'] erlaubt eine native Anzeige, zusätzlich kommt der Web-Banner. Systemanzeige hängt weiterhin vom Android-Kanal ab. Es gibt keinen DM-spezifischen Kanal und keinen belegten DM-Payload-Fehler.
  • Keine Freigabe für Pre-Production ableiten: Lokale historische Gerätetests und simulierte Tests ersetzen die drei echten Pre-Production-Smoke-Tests nicht. Aktuell liegt noch kein solcher Pre-Production-Nachweis vor.

Lokal geprüfte Pre-Production-Vorbereitung

Die ursprüngliche Push-Vorbereitung wurde mit 64 erfolgreichen Backend-Tests (isolierte lokale PostgreSQL-Schemas, simuliertes Firebase) und 11 JavaScript-Tests für Push-Registrierung/-Navigation und Android-Zurück-Verhalten geprüft. Diese Prüfungen decken insbesondere falsche Service Accounts, Projekt-/Mount-/Dateirechte, relative authentifizierte Registrierungs-URLs, die gemeinsamen Payload-Eigenschaften aller drei Push-Arten und den Lesestatus von Direktnachrichten ab.

Die anschließende Absicherung des Deployments mit externer Environment-Datei wurde mit inzwischen 84 Backend-Tests, 22 Host-/Deployment-Tests und 11 JavaScript-Tests geprüft. Dazu gehören fehlende Gitea-Werte, Bot-Identität, API-Fehler, Rechte, fehlende Dateien, sichere Backup-Rotation und Abbruch vor dem Containerwechsel. Die Compose-Interpolation wurde mit synthetischen Werten auch gegen den echten lokalen Compose-Parser geprüft. Keine Pre-Production-Secrets und kein Serverdeployment waren dafür nötig.

Compose-Konfigurationen für Basis, Local-Push und Pre-Production wurden mit synthetischen Konfigurationswerten geprüft, einschließlich Abbruch bei fehlendem Projekt/Secret-Pfad. Das Backend-Image wurde lokal gebaut und auf Credential-Dateien/Schlüssel geprüft; der einzige Schlüssel-Marker war ein kurzer synthetischer Text im bestehenden Gitea-Redaktionstest. .env, Android-Firebase-Konfiguration und die vorgesehenen Schlüsseldateinamen sind Git-ignoriert. Diese Prüfungen verwenden keinen Pre-Production-Key und belegen weder Server-IAM noch Zustellung auf dem Pre-Production-Gerät. Native Android-Dateien wurden nicht geändert; ein neuer APK-Build war dafür nicht erforderlich.