From 971761dcc4d03097ab24bc70ff6fbed3040a13dc Mon Sep 17 00:00:00 2001 From: ganfra Date: Fri, 31 Jul 2026 15:42:37 +0200 Subject: [PATCH] feat(base): add Room::is_device_in_active_room_call --- crates/matrix-sdk-base/src/room/call.rs | 40 ++++++++++++++++++++ crates/matrix-sdk-base/src/room/room_info.rs | 16 ++++++++ 2 files changed, 56 insertions(+) diff --git a/crates/matrix-sdk-base/src/room/call.rs b/crates/matrix-sdk-base/src/room/call.rs index ffe46e13c..cadc1bc20 100644 --- a/crates/matrix-sdk-base/src/room/call.rs +++ b/crates/matrix-sdk-base/src/room/call.rs @@ -59,6 +59,17 @@ impl Room { self.info.read().active_room_call_participants() } + /// See [`RoomInfo::is_device_in_active_room_call`]. + /// + /// [`RoomInfo::is_device_in_active_room_call`]: crate::RoomInfo::is_device_in_active_room_call + pub fn is_device_in_active_room_call( + &self, + user_id: &ruma::UserId, + device_id: &ruma::DeviceId, + ) -> bool { + self.info.read().is_device_in_active_room_call(user_id, device_id) + } + /// Get the consensus call intent for the current call, based on what /// members are advertising. pub fn active_room_call_consensus_intent(&self) -> CallIntentConsensus { @@ -451,4 +462,33 @@ mod tests { assert_eq!(expected, consensus_intent, "Failed case: {}", description); } } + + #[test] + fn is_device_in_active_room_call_matches_own_device_only() { + let (_, room) = make_room_test_helper(RoomState::Joined); + + let alice_a = session_member_state_event( + event_id!("$alice_a"), + &ALICE, + Some(InitData { device_id: device_id!("DEVICE_A"), minutes_ago: 1 }), + ); + let alice_b = session_member_state_event( + event_id!("$alice_b"), + &ALICE, + Some(InitData { device_id: device_id!("DEVICE_B"), minutes_ago: 1 }), + ); + let bob_a = session_member_state_event( + event_id!("$bob_a"), + &BOB, + Some(InitData { device_id: device_id!("DEVICE_A"), minutes_ago: 1 }), + ); + + receive_state_events(&room, vec![alice_a, alice_b, bob_a]); + + assert!(room.is_device_in_active_room_call(&ALICE, device_id!("DEVICE_A"))); + assert!(room.is_device_in_active_room_call(&ALICE, device_id!("DEVICE_B"))); + assert!(!room.is_device_in_active_room_call(&ALICE, device_id!("DEVICE_C"))); + assert!(room.is_device_in_active_room_call(&BOB, device_id!("DEVICE_A"))); + assert!(!room.is_device_in_active_room_call(&BOB, device_id!("DEVICE_B"))); + } } diff --git a/crates/matrix-sdk-base/src/room/room_info.rs b/crates/matrix-sdk-base/src/room/room_info.rs index 8cf6b83d3..32adf759a 100644 --- a/crates/matrix-sdk-base/src/room/room_info.rs +++ b/crates/matrix-sdk-base/src/room/room_info.rs @@ -1137,6 +1137,22 @@ impl RoomInfo { !self.active_room_call_memberships().is_empty() } + /// Whether the given `(user_id, device_id)` tuple is currently a + /// participant in this room's active MatrixRTC call. + /// + /// Distinct from [`Self::active_room_call_participants`] which returns + /// only user IDs. Callers that must not conflate multiple devices of + /// the same user (e.g. profile-field mirroring) should use this. + pub fn is_device_in_active_room_call( + &self, + user_id: &ruma::UserId, + device_id: &ruma::DeviceId, + ) -> bool { + self.active_room_call_memberships().iter().any(|(state_key, membership)| { + state_key.user_id() == user_id && membership.device_id() == device_id + }) + } + /// Get the call intent consensus for the current call, based on what /// members are advertising. ///