🔴 **Three separate defects, and calling could not work past any of them.**
Found by tapping the Call button, which is not covered by any test.
**1. The button did nothing.** `requestCall` sets `callPrivacyPrompt`, and the
only thing that renders it is `CallBanner` — which was composed inside
`SignedIn`, the HOME screen. The chat screen REPLACES that. So the only Call
button in the app set a flag nothing was on screen to show, and since the
prompt gates placing the call, calling could not be started at all. The button
looked simply dead.
That also means an incoming call arriving while somebody was reading a thread
displayed nothing — directly against the comment above it, which says an
incoming call must be visible whatever screen somebody is on. CallBanner is
now hoisted above the screen switch.
**2. Placing a call SIGABRTed the process.** The manifest never declared
ACCESS_NETWORK_STATE. WebRTC's NetworkMonitor calls
ConnectivityManager.getNetworkState() on its own network thread, that throws
without the permission, and the JNI layer immediately asserts
RTC_CHECK(!env->ExceptionCheck()) — turning a Java exception into abort().
No AndroidRuntime trace, nothing catchable, the process just vanishes:
Check failed: !env->ExceptionCheck()
Fatal signal 6 (SIGABRT) in tid network_thread
**3. Hanging up SIGABRTed it again, re-entrantly.** `onIceConnectionChange`
treated CLOSED as a reason to hang up — but CLOSED is exactly what our own
`close()` produces. So Hang up called hangUp, which closed the connection,
which fired CLOSED on the signaling thread, which re-entered hangUp, which did
a BLOCKING NETWORK SEND and disposed the connection it was running inside.
hangUp is now re-entrancy-guarded, CLOSED is a no-op (we caused it), FAILED
still hangs up, `dispose()` is guarded like the `close()` beside it already
was, and the courtesy "call ended" signal is best-effort rather than something
that can take the teardown down with it. The observer body cannot throw at all
now, because WebRTC treats any escaping exception as fatal.
Verified on the emulator: dialling brings up Mute/Hang up and the app survives
both dialling and hanging up.
⚠️ Still unproven: whether AUDIO actually flows. That needs two live endpoints
and a TURN server, and none is deployed. What is fixed is that calling is
reachable and no longer kills the app.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>