3cddda6ef9
Root cause: WebRTC call ringing continued forever after cancellation because ringing, WebRTC, and notifications were owned by CallController which lived in viewModelScope (account lifetime), not by CallActivity. Cleanup depended on a state collector observing Ended in time, which raced, dropped, and went stale. Architecture after fix: - CallManager (background, AccountViewModel scope): state machine only. No audio, no WebRTC, no notifications. Exposes state as StateFlow, renegotiation events as SharedFlow, and a 65-second ringing watchdog as a fail-safe. - CallSession (Activity-scoped, replaces CallController): owns all call resources. Created in onCreate, destroyed via close() in onDestroy. Implements AutoCloseable. No cleanedUp guard flag needed. - CallSessionBridge: slimmed down — holds only callManager + accountViewModel for cross-Activity reference sharing. No callController field. Key changes: - CallController renamed to CallSession, moved to ui/call/session/. - CallSession.close() replaces cleanup() — single-shot, no guard flags. - CallActivity creates and owns CallSession directly. - CallManager.onRenegotiationOfferReceived callback replaced with renegotiationEvents SharedFlow (buffered, survives Activity restarts). - CallManager gains ringing watchdog (65s) that forces Ended(TIMEOUT) if state stays in IncomingCall/Offering past the deadline. - CallNotifier extracted from NotificationUtils into dedicated class. - AccountViewModel.initCallController deleted; callManager wired to newNotesPreProcessor eagerly in init block. - ChatroomScreen uses CallActivity.launchForOutgoingCall() with intent extras instead of directly calling callController.initiateGroupCall(). - AndroidManifest adds navigation|fontScale|density to configChanges so no config change recreates CallActivity mid-call. Invariants: 1. CallActivity.onDestroy → session.close() → all resources released. 2. Ringing cannot outlive any code path: Activity close, state collector, and 65s watchdog provide three independent stop paths. 3. Config changes cannot kill the call (manifest prevents recreation). 4. No global mutable singleton for CallController. https://claude.ai/code/session_019yNnDjGKmJb19gadmojq54