1use ruma::OwnedUserId;
16
17use super::Room;
18
19impl Room {
20 pub fn has_active_room_call(&self) -> bool {
23 self.info.read().has_active_room_call()
24 }
25
26 pub fn active_room_call_participants(&self) -> Vec<OwnedUserId> {
35 self.info.read().active_room_call_participants()
36 }
37}
38
39#[cfg(test)]
40mod tests {
41 use std::{ops::Sub, sync::Arc, time::Duration};
42
43 use assign::assign;
44 use matrix_sdk_test::{ALICE, BOB, CAROL, event_factory::EventFactory};
45 use ruma::{
46 DeviceId, EventId, MilliSecondsSinceUnixEpoch, OwnedUserId, UserId, device_id, event_id,
47 events::{
48 AnySyncStateEvent,
49 call::member::{
50 ActiveFocus, ActiveLivekitFocus, Application, CallApplicationContent,
51 CallMemberEventContent, CallMemberStateKey, Focus, LegacyMembershipData,
52 LegacyMembershipDataInit, LivekitFocus,
53 },
54 },
55 room_id,
56 serde::Raw,
57 time::SystemTime,
58 user_id,
59 };
60 use similar_asserts::assert_eq;
61
62 use super::super::{Room, RoomState};
63 use crate::{store::MemoryStore, utils::RawSyncStateEventWithKeys};
64
65 fn make_room_test_helper(room_type: RoomState) -> (Arc<MemoryStore>, Room) {
66 let store = Arc::new(MemoryStore::new());
67 let user_id = user_id!("@me:example.org");
68 let room_id = room_id!("!test:localhost");
69 let (sender, _receiver) = tokio::sync::broadcast::channel(1);
70
71 (store.clone(), Room::new(user_id, store, room_id, room_type, sender))
72 }
73
74 fn timestamp(minutes_ago: u32) -> MilliSecondsSinceUnixEpoch {
75 MilliSecondsSinceUnixEpoch::from_system_time(
76 SystemTime::now().sub(Duration::from_secs((60 * minutes_ago).into())),
77 )
78 .expect("date out of range")
79 }
80
81 fn legacy_membership_for_my_call(
82 device_id: &DeviceId,
83 membership_id: &str,
84 minutes_ago: u32,
85 ) -> LegacyMembershipData {
86 let (application, foci) = foci_and_application();
87 assign!(
88 LegacyMembershipData::from(LegacyMembershipDataInit {
89 application,
90 device_id: device_id.to_owned(),
91 expires: Duration::from_millis(3_600_000),
92 foci_active: foci,
93 membership_id: membership_id.to_owned(),
94 }),
95 { created_ts: Some(timestamp(minutes_ago)) }
96 )
97 }
98
99 fn legacy_member_state_event(
100 memberships: Vec<LegacyMembershipData>,
101 ev_id: &EventId,
102 user_id: &UserId,
103 ) -> Raw<AnySyncStateEvent> {
104 let content = CallMemberEventContent::new_legacy(memberships);
105 EventFactory::new()
106 .sender(user_id)
107 .event(content)
108 .state_key(CallMemberStateKey::new(user_id.to_owned(), None, false).as_ref())
109 .event_id(ev_id)
110 .server_ts(timestamp(0))
113 .into()
114 }
115
116 struct InitData<'a> {
117 device_id: &'a DeviceId,
118 minutes_ago: u32,
119 }
120
121 fn session_member_state_event(
122 ev_id: &EventId,
123 user_id: &UserId,
124 init_data: Option<InitData<'_>>,
125 ) -> Raw<AnySyncStateEvent> {
126 let application = Application::Call(CallApplicationContent::new(
127 "my_call_id_1".to_owned(),
128 ruma::events::call::member::CallScope::Room,
129 ));
130 let foci_preferred = vec![Focus::Livekit(LivekitFocus::new(
131 "my_call_foci_alias".to_owned(),
132 "https://lk.org".to_owned(),
133 ))];
134 let focus_active = ActiveFocus::Livekit(ActiveLivekitFocus::new());
135
136 let (content, state_key) = match init_data {
137 Some(InitData { device_id, minutes_ago }) => {
138 let member_id = format!("{device_id}_m.call");
139 (
140 CallMemberEventContent::new(
141 application,
142 device_id.to_owned(),
143 focus_active,
144 foci_preferred,
145 Some(timestamp(minutes_ago)),
146 None,
147 ),
148 CallMemberStateKey::new(user_id.to_owned(), Some(member_id), false),
149 )
150 }
151
152 None => (
153 CallMemberEventContent::new_empty(None),
154 CallMemberStateKey::new(user_id.to_owned(), None, false),
155 ),
156 };
157
158 EventFactory::new()
159 .sender(user_id)
160 .event(content)
161 .state_key(state_key.as_ref())
162 .event_id(ev_id)
163 .server_ts(timestamp(0))
166 .into()
167 }
168
169 fn foci_and_application() -> (Application, Vec<Focus>) {
170 (
171 Application::Call(CallApplicationContent::new(
172 "my_call_id_1".to_owned(),
173 ruma::events::call::member::CallScope::Room,
174 )),
175 vec![Focus::Livekit(LivekitFocus::new(
176 "my_call_foci_alias".to_owned(),
177 "https://lk.org".to_owned(),
178 ))],
179 )
180 }
181
182 fn receive_state_events(room: &Room, events: Vec<Raw<AnySyncStateEvent>>) {
183 room.info.update_if(|info| {
184 let mut res = false;
185 for ev in events {
186 res |= info.handle_state_event(
187 &mut RawSyncStateEventWithKeys::try_from_raw_state_event(ev)
188 .expect("generated state event should be valid"),
189 );
190 }
191 res
192 });
193 }
194
195 fn legacy_create_call_with_member_events_for_user(a: &UserId, b: &UserId, c: &UserId) -> Room {
199 let (_, room) = make_room_test_helper(RoomState::Joined);
200
201 let a_empty = legacy_member_state_event(Vec::new(), event_id!("$1234"), a);
202
203 let m_init_b = legacy_membership_for_my_call(device_id!("DEVICE_0"), "0", 1);
205 let b_one = legacy_member_state_event(vec![m_init_b], event_id!("$12345"), b);
206
207 let m_init_c1 = legacy_membership_for_my_call(device_id!("DEVICE_0"), "0", 10);
209 let m_init_c2 = legacy_membership_for_my_call(device_id!("DEVICE_1"), "0", 20);
211 let c_two = legacy_member_state_event(vec![m_init_c1, m_init_c2], event_id!("$123456"), c);
212
213 receive_state_events(&room, vec![c_two, a_empty, b_one]);
215
216 room
217 }
218
219 fn session_create_call_with_member_events_for_user(a: &UserId, b: &UserId, c: &UserId) -> Room {
223 let (_, room) = make_room_test_helper(RoomState::Joined);
224
225 let a_empty = session_member_state_event(event_id!("$1234"), a, None);
226
227 let b_one = session_member_state_event(
229 event_id!("$12345"),
230 b,
231 Some(InitData { device_id: "DEVICE_0".into(), minutes_ago: 1 }),
232 );
233
234 let m_c1 = session_member_state_event(
235 event_id!("$123456_0"),
236 c,
237 Some(InitData { device_id: "DEVICE_0".into(), minutes_ago: 10 }),
238 );
239 let m_c2 = session_member_state_event(
240 event_id!("$123456_1"),
241 c,
242 Some(InitData { device_id: "DEVICE_1".into(), minutes_ago: 20 }),
243 );
244 receive_state_events(&room, vec![m_c1, m_c2, a_empty, b_one]);
246
247 room
248 }
249
250 #[test]
251 fn test_show_correct_active_call_state() {
252 let room_legacy = legacy_create_call_with_member_events_for_user(&ALICE, &BOB, &CAROL);
253
254 assert_eq!(
258 vec![CAROL.to_owned(), CAROL.to_owned(), BOB.to_owned()],
259 room_legacy.active_room_call_participants()
260 );
261 assert!(room_legacy.has_active_room_call());
262
263 let room_session = session_create_call_with_member_events_for_user(&ALICE, &BOB, &CAROL);
264 assert_eq!(
265 vec![CAROL.to_owned(), CAROL.to_owned(), BOB.to_owned()],
266 room_session.active_room_call_participants()
267 );
268 assert!(room_session.has_active_room_call());
269 }
270
271 #[test]
272 fn test_active_call_is_false_when_everyone_left() {
273 let room = legacy_create_call_with_member_events_for_user(&ALICE, &BOB, &CAROL);
274
275 let b_empty_membership = legacy_member_state_event(Vec::new(), event_id!("$1234_1"), &BOB);
276 let c_empty_membership =
277 legacy_member_state_event(Vec::new(), event_id!("$12345_1"), &CAROL);
278
279 receive_state_events(&room, vec![b_empty_membership, c_empty_membership]);
280
281 assert_eq!(Vec::<OwnedUserId>::new(), room.active_room_call_participants());
283 assert!(!room.has_active_room_call());
284 }
285}