rebreak-monorepo/apps/rebreak-native/CALLS_DEBUG_STATE.md
chahinebrini 63fae25531 fix(android-protection): explicit specialUse FGS type — Samsung/Android 16 crash loop
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>
2026-06-10 22:33:28 +02:00

4.9 KiB

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>useIncomingCallsreceiveIncoming() + router.push('/call'). Greift nur wenn die App lebt + Realtime subscribed (FG, oder Android kurz nach BG).
  2. Push POST /api/calls/ringsendCallRingPush:
    • iOS + voipToken → VoIP-PushKit → AppDelegate.didReceiveIncomingPushRNCallKeep.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≠activeonEnd-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 → receiveIncomingdisplayIncomingCall → 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.