1. Filter out the logged-in user from the Connecting state UI, matching
the existing behavior in IncomingCall state.
2. Change GroupCallNames default textColor from Color.Unspecified to
MaterialTheme.colorScheme.onSurface so names are visible in dark mode
(Box+background doesn't set LocalContentColor like Surface does).
https://claude.ai/code/session_016sDH1SL7P8aXhcyFaFtzYu
In group calls (e.g. Alice calls Bob and Charlie), when Bob accepts the
call, a CallAnswerEvent is gift-wrapped to all group members including
Charlie. Charlie's CallManager, still in IncomingCall state, was treating
ANY CallAnswerEvent as "answered elsewhere" (intended for multi-device
scenarios) and ending the call prematurely.
The fix checks whether the answering/rejecting peer is actually the
current user (signer.pubKey) before treating it as an "answered/rejected
elsewhere" event. If it's a different group member, we simply ignore it
and keep ringing.
https://claude.ai/code/session_01EYzWB93PZRqw15QuQadCf4
Adds debug logging across CallManager, CallController, and WebRtcCallSession
to trace the full call lifecycle: SDP offer/answer creation, remote description
set success/failure, ICE candidate exchange, ICE connection state transitions,
and signaling state changes. The noOpSdpObserver is replaced with a logging
observer so setRemoteDescription success/failure is visible in logcat.
Filter logcat with: CallManager|CallController|WebRtcCallSession
https://claude.ai/code/session_01M3yyyRyu8KWJZ5PjNt4V2H
Hardware bitmaps cannot be efficiently read for pixels, which is required
when parceling notifications. Adding allowHardware(false) to all image
requests used for notification bitmaps prevents the slow pixel readback.
https://claude.ai/code/session_018y75qshU48REuHXxLH3Tmb
The group factory methods work correctly with any number of members,
including single-peer calls. Remove the if/else branches that
duplicated P2P vs group logic in acceptCall, rejectCall, hangup,
sendRenegotiation, and sendRenegotiationAnswer.
https://claude.ai/code/session_013h2E7spwHDgSjunqumsYgp
Adds a security setting to hide posts with more than X hashtags (t tags),
defaulting to 5. Applied at the feed filter level via FilterByListParams.match()
and Account.isAcceptable() to cover all feeds. Configurable in Security Filters
settings, with 0 to disable.
https://claude.ai/code/session_01NMVypgGSU9EjQA7hZ9uvjD
Full-mesh WebRTC group calls degrade at 5+ participants (each
participant uploads N-1 streams). Hide the voice and video call
buttons in the chat header when the room has more than 5 members.
https://claude.ai/code/session_013h2E7spwHDgSjunqumsYgp
- Add recipientPubKeys() and groupMembers() helper methods to
CallAnswerEvent, CallHangupEvent, CallRejectEvent, and
CallRenegotiateEvent (matching the existing pattern in CallOfferEvent)
- Document in NIP-AC spec that group calls handle multi-device
self-notification implicitly by including the sender's own pubkey
in the gift-wrap recipient set
https://claude.ai/code/session_013h2E7spwHDgSjunqumsYgp
SDP payloads (offer, answer, renegotiate) are specific to individual
PeerConnections and cannot be shared across peers. This commit:
- Adds group-context overloads to createCallOffer, createCallAnswer,
and createRenegotiate that include all member p-tags but wrap to
a single target peer
- Removes createGroupRenegotiate (renegotiation is always per-peer)
- Updates sendRenegotiation/sendRenegotiationAnswer to take explicit
peerPubKey and use per-peer wrapping with group p-tags
- Updates invitePeer to include all existing group members + invitee
in p-tags so the new peer sees the full group composition
- Documents SDP per-peer vs sign-once distinction in NIP-AC spec
https://claude.ai/code/session_013h2E7spwHDgSjunqumsYgp
All signaling event types in group calls (answer, hangup, reject,
renegotiate) now include p-tags for every group member, matching the
pattern already used by CallOfferEvent. This allows signing the inner
event once and gift-wrapping it separately for each recipient.
- Add Set<HexKey> build() overloads to CallAnswerEvent, CallHangupEvent,
CallRejectEvent, and CallRenegotiateEvent
- Add createGroupCallAnswer, createGroupRenegotiate factory methods
- Fix createGroupHangup to sign once instead of per-peer
- Update createGroupReject to accept full member set
- Update CallManager to use group methods when in group call
- Document group call p-tag convention in NIP-AC spec
- Add tests for all group build overloads
https://claude.ai/code/session_013h2E7spwHDgSjunqumsYgp
- Include pending peers in hangup notification so ringing users get
immediate feedback instead of waiting for the 60s timeout
- Sign individual hangup events per peer with correct p-tag instead
of reusing a single event with the first peer's tag
- Stop treating ICE DISCONNECTED as terminal; only hang up on FAILED
since DISCONNECTED is often transient (network switch, packet loss)
- Send "busy" reject when receiving a call while already in one
https://claude.ai/code/session_01HpowdWMK77pA35ABn9XWpD
RingtoneManager.getRingtone() and audio mode switching do disk I/O,
causing StrictMode DiskWriteViolation when called from the state
collector on the main thread. Wrap startRinging() and
switchToCallAudioMode() in withContext(Dispatchers.IO).
https://claude.ai/code/session_01LR8NmFGdMoDTVcfKjL7HWR
Add ability to invite new users into an ongoing call:
- CallManager.invitePeer() sends a call offer to a new peer using
the current callId, adding them to pendingPeerPubKeys
- CallController.invitePeer() exposes this to the UI layer
- Add PersonAdd button to connected call controls
- Add AddParticipantDialog with user search (reuses UserSuggestionState)
- New strings: call_add_participant, call_search_users
Rewrite CallScreenPreviews to showcase all call states:
1. Offering P2P call (Calling...)
2. Offering group call (3 avatars)
3. Connecting
4. Incoming voice call
5. Incoming video call
6. Incoming group call (4 avatars)
7. Connected P2P voice call with controls
8. Connected muted + video + speaker
9. Connected group call with pending peers
10. Connected with Bluetooth audio
11. Call ended
12. Large group (5+ members, +N badge)
13. PiP calling
14. PiP connected
https://claude.ai/code/session_01LR8NmFGdMoDTVcfKjL7HWR
The call now starts immediately when the first peer answers instead
of waiting for all participants. Remaining peers are tracked as
pending and shown in the UI with a "Waiting for others to join..."
indicator. When a pending peer answers, they move to the connected
set. If a pending peer rejects or hangs up, they're silently removed.
Changes:
- Add pendingPeerPubKeys to Connecting and Connected states
- Add allPeerPubKeys helper on Connected for UI rendering
- Split peers into connected/pending on first answer in onCallAnswered
- Handle subsequent answers in Connecting and Connected states
- Carry pending peers through Connecting -> Connected transition
- Show all peers (connected + pending) in call UI avatars
- Display "Waiting for others to join..." when pending peers exist
https://claude.ai/code/session_01LR8NmFGdMoDTVcfKjL7HWR
When a peer hangs up or rejects in a group call, remove them from the
participant set instead of ending the entire call. The call only ends
when no peers remain (all others left). Similarly, if a peer rejects
during the Offering phase of a group call, remove them but continue
ringing remaining peers.
For incoming group calls, if the original caller hangs up the call
ends immediately. If another group member leaves, the call continues
as long as at least 2 members (including self) remain.
https://claude.ai/code/session_01LR8NmFGdMoDTVcfKjL7HWR
- Add GroupCallPictures composable showing up to 4 user avatars in
a grid layout (same pattern as NonClickableUserPictures in chat)
- When >4 members, show 3 avatars + a "+N" badge in the 4th slot
- Add GroupCallNames composable showing up to 2 names + "+N" remaining
- Update CallInProgressUI, IncomingCallUI, ConnectedCallUI, PipCallUI,
and PipConnectedCallUI to pass full peerPubKeys/groupMembers sets
instead of only .first()
https://claude.ai/code/session_01LR8NmFGdMoDTVcfKjL7HWR
- Add MODIFY_AUDIO_SETTINGS for audio routing (speaker, earpiece, BT SCO)
- Add BLUETOOTH permission for API 26-30 (maxSdkVersion=30)
- Request BLUETOOTH_CONNECT at runtime on API 31+ for BT audio
- Add FOREGROUND_SERVICE_CAMERA for video calls on API 34+
- Update foreground service type to microphone|camera for video calls
- Guard Bluetooth operations with permission check in CallAudioManager
- Add uses-feature for microphone with required=false
https://claude.ai/code/session_01LR8NmFGdMoDTVcfKjL7HWR
On SDK 36 starting a foreground service with type microphone requires
RECORD_AUDIO to be granted at the moment startForeground() is called.
When the permission isn't available (e.g. revoked between call start
and peer connection) the service now falls back to a generic foreground
type instead of crashing with a SecurityException.
https://claude.ai/code/session_01WafqMofFQfWCQA5TcLvHRb
Adds voice and video call buttons to the group DM top bar, matching
the existing 1-on-1 DM header. When tapped in a group chat, the
buttons invoke initiateGroupCall() which sends gift-wrapped offers
to every group member under a single call-id.
https://claude.ai/code/session_01WafqMofFQfWCQA5TcLvHRb
Extends the NIP-AC P2P call signaling to support group calls by
sending individual gift-wrapped offers to each member of the group,
all sharing the same call-id.
- CallOfferEvent: add multi-pubkey build overload, groupMembers(),
isGroupCall(), and recipientPubKeys() helpers
- WebRtcCallFactory: add GroupResult type, createGroupCallOffer(),
createGroupHangup(), and createGroupReject() methods
- CallState: change peerPubKey to peerPubKeys (Set<HexKey>) across
Offering, Connecting, Connected, and Ended states; add groupMembers
field to IncomingCall
- CallManager: add initiateGroupCall() that creates a single signed
offer with p tags for every callee and gift-wraps it individually;
hangup sends to all peers in a group call
- CallController: add initiateGroupCall() entry point
- Tests: add group call offer tests for p tags, call-id, call-type,
and expiration
https://claude.ai/code/session_01WafqMofFQfWCQA5TcLvHRb
The code implements two protocol features not previously documented in
the NIP: (1) renegotiation responses use CallAnswer (kind 25051) to
complete the SDP handshake, and (2) self-addressed answer/reject events
notify other devices of the same user to stop ringing.
https://claude.ai/code/session_01XSjhzuVn8N4Q2hZAYMRGqM