RebreakVpnService.onStartCommand crashed with SecurityException because Android 16's validateForegroundServiceType rejects the implicit 2-arg startForeground(). Now passes FOREGROUND_SERVICE_TYPE_SPECIAL_USE explicitly (Google's documented best practice) and guards the call so a failed foreground promotion stops the service cleanly instead of crashing the app. Verified vs reported Galaxy A54 / Android 16 signature (97% of crash events, 1-user crash loop). Bundles pending working-tree work across native/marketing/locales/mac + graphify-out rebuild. gitignore: google-services.json + /screenshots/. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
66 lines
4.9 KiB
Markdown
66 lines
4.9 KiB
Markdown
# Voice-Calls — Debug-Zustand (2026-06-05)
|
|
|
|
Momentaufnahme beim Live-Debugging mit 2 Geräten (iOS + Android, beide über Metro).
|
|
Ziel: Kinderkrankheiten der Call-Funktion per Teile-und-Herrsche fixen.
|
|
|
|
## Architektur (Ist-Zustand)
|
|
|
|
Zwei Zustell-Pfade zum Callee — **beide feuern IMMER parallel** (Caller macht beides):
|
|
|
|
1. **Realtime** `call-ring:<calleeId>` → `useIncomingCalls` → `receiveIncoming()` + `router.push('/call')`.
|
|
Greift nur wenn die App lebt + Realtime subscribed (FG, oder Android kurz nach BG).
|
|
2. **Push** `POST /api/calls/ring` → `sendCallRingPush`:
|
|
- **iOS + voipToken** → VoIP-PushKit → `AppDelegate.didReceiveIncomingPush` → `RNCallKeep.reportNewIncomingCall` (CallKit) → JS-Forward via `useCallKeepEvents`.
|
|
- **sonst** (Android, iOS ohne voipToken) → Expo-Push (channel `calls`, `data.type='call'`). Reagiert **nur auf Tap** (`addNotificationResponseReceivedListener`).
|
|
|
|
Kern-Spannung: im FG entsteht Doppel-UI (in-app `/call` **und** CallKit/ConnectionService).
|
|
iOS-BG ist durch Apple auf CallKit festgenagelt (PushKit-Pflicht). Android-BG ist frei wählbar.
|
|
|
|
## Verhaltens-Matrix
|
|
|
|
| # | Anruf | Callee-State | Erwartet | Beobachtet | Hypothese (Root Cause) |
|
|
|---|-------|--------------|----------|------------|------------------------|
|
|
| 1 | Android→iOS | FG | `/call` bleibt, klingelt | `/call` erscheint, **verschwindet nach ~Sek** | VoIP-Push feuert auch im FG → CallKit übernimmt → App→`inactive` → CallKit-`endCall` feuert mit `appState≠active` → `onEnd`-Guard (`appState==='active'`) greift NICHT → `declineCall()` schließt `/call`. **(Logs nötig)** |
|
|
| 2 | iOS→Android | FG | korrekt | **korrekt** | — |
|
|
| 3 | Android→iOS | BG/locked | CallKit-Incoming (Apple) | **nichts**; nach Auflegen „verpasster Anruf"-Push; Telefon-App zeigt kein ReBreak-Audio | VoIP-Push erreicht Gerät nicht / CallKit nicht reported. Verdacht A: `voipToken=null` in DB → Fallback Expo-Push (im BG silent). Verdacht B: VoIP gesendet, aber APNs-Fehler (env/topic/cert). „Verpasster Anruf"-Push = Chat-DM aus `logCallToChat`. **(Logs nötig)** |
|
|
| 4 | iOS→Android | BG (App lebt) | ReBreak `/call` | **Android Telecom-native Incoming-UI** | Realtime im BG → `receiveIncoming` → `displayIncomingCall` → ConnectionService = System-Telecom-UI. „Working as coded", aber Design-Mismatch: gewünscht ist Custom-`/call` (Full-Screen-Intent). |
|
|
|
|
## Divide & Conquer — Phasen
|
|
|
|
- **Phase 0 — Diagnose** (non-destruktiv): 4 Szenarien durchspielen + Logs einsammeln
|
|
(`[call-ring] voip=?`, `[voip-push]`, `[VoIP] didReceiveIncomingPush`, `[callkeep] end … appState=`).
|
|
Bestätigt #1 und #3.
|
|
- **Phase 1 — iOS FG (#1)**: `/call` autoritativ machen; CallKit im FG unterdrücken / sauber
|
|
programmatisch beenden mit „programmatic-end"-Flag; `onEnd`-Guard reparieren (`inactive` = FG).
|
|
- **Phase 2 — iOS BG (#3)**: VoIP-Pfad fixen — voipToken-Registrierung + APNs env/topic/cert
|
|
verifizieren, bis CallKit im BG zuverlässig erscheint.
|
|
- **Phase 3 — Android BG (#4)**: Entscheidung Custom-Full-Screen-Intent vs Telecom-UI; ggf. umbauen.
|
|
- **Phase 4 — Aufräumen**: Doppel-Push/Doppel-Log, Missed-Call-Handling, einheitlicher Teardown.
|
|
|
|
## ✅ BESTÄTIGTE Root Cause #3 (2026-06-05, via idevicesyslog + git)
|
|
|
|
Build 76 wurde aus dem **alten** `with-voip-pushkit-ios.js` kompiliert, wo die PushKit-Registry
|
|
eine **lokale Variable** war (`let voipRegistry = PKPushRegistry(queue: nil)`) → deallociert
|
|
sofort nach `didFinishLaunching` → eingehende VoIP-Pushes erreichen `didReceiveIncomingPush` nie
|
|
→ kein `reportNewIncomingCall` → kein CallKit-Wake. Token-Registrierung klappt trotzdem
|
|
(Sync-Callback), daher `voip=1` am Server, aber Pushes verpuffen.
|
|
|
|
**Beweise (idevicesyslog iPhone Air, Build 76):**
|
|
- `[voip] register event fired, token: 33899c2b…` ✅ (Token-Reg ok)
|
|
- `[voip-push] sent env=sandbox` am Server ✅ (Push verlässt Server)
|
|
- **`[VoIP] didReceiveIncomingPush` / `reportNewIncomingCall` FEHLEN komplett** ❌
|
|
- CallKit-Fehler `requesttransaction Code=4` (unknown UUID) beim End-Versuch = Call war CallKit nie bekannt
|
|
- „verpasster Anruf"-Push = Chat-DM aus `logCallToChat` beim späteren FG-Realtime-Replay, NICHT der Ring
|
|
|
|
**Fix:** bereits geschrieben (uncommitted) — Registry als `self.voipRegistry` retainen.
|
|
Liegt im `ios/ReBreak/AppDelegate.swift` (Jun 4 20:35), aber **nie in einen Build kompiliert**.
|
|
→ Dev-Rebuild (`expo run:ios --device`) kompiliert den Fix. Danach verifizieren dass
|
|
`[VoIP] didReceiveIncomingPush` feuert. Offen bleibt sekundärer Verdacht: Doppel-Registry
|
|
durch JS `registerVoipToken()` (Library erzeugt 2. unretained Registry) — empirisch nach Rebuild prüfen.
|
|
|
|
## Apple-Constraint (kein Bug)
|
|
|
|
Auf iOS **Background/locked** ist die Incoming-UI zwingend CallKit (PushKit verlangt
|
|
`reportNewIncomingCall` in derselben Run-Loop, sonst killt iOS die App). Der Custom-`/call`-Screen
|
|
kann dort erst NACH „Annehmen" erscheinen — nicht als Klingel-UI.
|