Add helper class to keep track of the participants in a call

For now only the same signaling messages that were already handled are
still handled; in the future it could be extended to handle other
messages, like the one sent by the external signaling server when a
participant leaves the room (in some cases no participants update
message is sent if the participant leaves the call and room at the same
time, which causes the participants to still be seen as in call until a
new update is received).

Signed-off-by: Daniel Calviño Sánchez <danxuliu@gmail.com>
This commit is contained in:
Daniel Calviño Sánchez 2022-11-29 13:04:17 +01:00 committed by Marcel Hibbe (Rebase PR Action)
parent 0a3f515bb6
commit ab72db7a10
5 changed files with 1486 additions and 0 deletions

View file

@ -0,0 +1,164 @@
/*
* Nextcloud Talk application
*
* @author Daniel Calviño Sánchez
* Copyright (C) 2022 Daniel Calviño Sánchez <danxuliu@gmail.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.nextcloud.talk.call;
import com.nextcloud.talk.models.json.participants.Participant;
import com.nextcloud.talk.signaling.SignalingMessageReceiver;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* Helper class to keep track of the participants in a call based on the signaling messages.
*
* The CallParticipantList adds a listener for participant list messages as soon as it is created and starts tracking
* the call participants until destroyed. Notifications about the changes can be received by adding an observer to the
* CallParticipantList; note that no sorting is guaranteed on the participants.
*/
public class CallParticipantList {
public interface Observer {
void onCallParticipantsChanged(Collection<Participant> joined, Collection<Participant> updated,
Collection<Participant> left, Collection<Participant> unchanged);
void onCallEndedForAll();
}
private final SignalingMessageReceiver.ParticipantListMessageListener participantListMessageListener =
new SignalingMessageReceiver.ParticipantListMessageListener() {
private final Map<String, Participant> callParticipants = new HashMap<>();
@Override
public void onUsersInRoom(List<Participant> participants) {
processParticipantList(participants);
}
@Override
public void onParticipantsUpdate(List<Participant> participants) {
processParticipantList(participants);
}
private void processParticipantList(List<Participant> participants) {
Collection<Participant> joined = new ArrayList<>();
Collection<Participant> updated = new ArrayList<>();
Collection<Participant> left = new ArrayList<>();
Collection<Participant> unchanged = new ArrayList<>();
Collection<Participant> knownCallParticipantsNotFound = new ArrayList<>(callParticipants.values());
for (Participant participant : participants) {
String sessionId = participant.getSessionId();
Participant callParticipant = callParticipants.get(sessionId);
boolean knownCallParticipant = callParticipant != null;
if (!knownCallParticipant && participant.getInCall() != Participant.InCallFlags.DISCONNECTED) {
callParticipants.put(sessionId, copyParticipant(participant));
joined.add(copyParticipant(participant));
} else if (knownCallParticipant && participant.getInCall() == Participant.InCallFlags.DISCONNECTED) {
callParticipants.remove(sessionId);
// No need to copy it, as it will be no longer used.
callParticipant.setInCall(Participant.InCallFlags.DISCONNECTED);
left.add(callParticipant);
} else if (knownCallParticipant && callParticipant.getInCall() != participant.getInCall()) {
callParticipant.setInCall(participant.getInCall());
updated.add(copyParticipant(participant));
} else if (knownCallParticipant) {
unchanged.add(copyParticipant(participant));
}
if (knownCallParticipant) {
knownCallParticipantsNotFound.remove(callParticipant);
}
}
for (Participant callParticipant : knownCallParticipantsNotFound) {
callParticipants.remove(callParticipant.getSessionId());
// No need to copy it, as it will be no longer used.
callParticipant.setInCall(Participant.InCallFlags.DISCONNECTED);
left.add(callParticipant);
}
if (!joined.isEmpty() || !updated.isEmpty() || !left.isEmpty()) {
callParticipantListNotifier.notifyChanged(joined, updated, left, unchanged);
}
}
@Override
public void onAllParticipantsUpdate(long inCall) {
if (inCall != Participant.InCallFlags.DISCONNECTED) {
// Updating all participants is expected to happen only to disconnect them.
return;
}
callParticipantListNotifier.notifyCallEndedForAll();
Collection<Participant> joined = new ArrayList<>();
Collection<Participant> updated = new ArrayList<>();
Collection<Participant> left = new ArrayList<>(callParticipants.size());
Collection<Participant> unchanged = new ArrayList<>();
for (Participant callParticipant : callParticipants.values()) {
// No need to copy it, as it will be no longer used.
callParticipant.setInCall(Participant.InCallFlags.DISCONNECTED);
left.add(callParticipant);
}
callParticipants.clear();
if (!left.isEmpty()) {
callParticipantListNotifier.notifyChanged(joined, updated, left, unchanged);
}
}
private Participant copyParticipant(Participant participant) {
Participant copiedParticipant = new Participant();
copiedParticipant.setInCall(participant.getInCall());
copiedParticipant.setLastPing(participant.getLastPing());
copiedParticipant.setSessionId(participant.getSessionId());
copiedParticipant.setType(participant.getType());
copiedParticipant.setUserId(participant.getUserId());
return copiedParticipant;
}
};
private final CallParticipantListNotifier callParticipantListNotifier = new CallParticipantListNotifier();
private final SignalingMessageReceiver signalingMessageReceiver;
public CallParticipantList(SignalingMessageReceiver signalingMessageReceiver) {
this.signalingMessageReceiver = signalingMessageReceiver;
this.signalingMessageReceiver.addListener(participantListMessageListener);
}
public void destroy() {
signalingMessageReceiver.removeListener(participantListMessageListener);
}
public void addObserver(Observer observer) {
callParticipantListNotifier.addObserver(observer);
}
public void removeObserver(Observer observer) {
callParticipantListNotifier.removeObserver(observer);
}
}

View file

@ -0,0 +1,63 @@
/*
* Nextcloud Talk application
*
* @author Daniel Calviño Sánchez
* Copyright (C) 2022 Daniel Calviño Sánchez <danxuliu@gmail.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.nextcloud.talk.call;
import com.nextcloud.talk.models.json.participants.Participant;
import java.util.ArrayList;
import java.util.Collection;
import java.util.LinkedHashSet;
import java.util.Set;
/**
* Helper class to register and notify CallParticipantList.Observers.
*
* This class is only meant for internal use by CallParticipantList; listeners must register themselves against
* a CallParticipantList rather than against a CallParticipantListNotifier.
*/
class CallParticipantListNotifier {
private final Set<CallParticipantList.Observer> callParticipantListObservers = new LinkedHashSet<>();
public synchronized void addObserver(CallParticipantList.Observer observer) {
if (observer == null) {
throw new IllegalArgumentException("CallParticipantList.Observer can not be null");
}
callParticipantListObservers.add(observer);
}
public synchronized void removeObserver(CallParticipantList.Observer observer) {
callParticipantListObservers.remove(observer);
}
public synchronized void notifyChanged(Collection<Participant> joined, Collection<Participant> updated,
Collection<Participant> left, Collection<Participant> unchanged) {
for (CallParticipantList.Observer observer : new ArrayList<>(callParticipantListObservers)) {
observer.onCallParticipantsChanged(joined, updated, left, unchanged);
}
}
public synchronized void notifyCallEndedForAll() {
for (CallParticipantList.Observer observer : new ArrayList<>(callParticipantListObservers)) {
observer.onCallEndedForAll();
}
}
}

View file

@ -0,0 +1,663 @@
/*
* Nextcloud Talk application
*
* @author Daniel Calviño Sánchez
* Copyright (C) 2022 Daniel Calviño Sánchez <danxuliu@gmail.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.nextcloud.talk.call;
import com.nextcloud.talk.models.json.participants.Participant;
import com.nextcloud.talk.signaling.SignalingMessageReceiver;
import org.junit.Before;
import org.junit.Test;
import org.mockito.ArgumentCaptor;
import org.mockito.ArgumentMatcher;
import org.mockito.InOrder;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import static com.nextcloud.talk.models.json.participants.Participant.InCallFlags.DISCONNECTED;
import static com.nextcloud.talk.models.json.participants.Participant.InCallFlags.IN_CALL;
import static com.nextcloud.talk.models.json.participants.Participant.InCallFlags.WITH_AUDIO;
import static com.nextcloud.talk.models.json.participants.Participant.InCallFlags.WITH_VIDEO;
import static com.nextcloud.talk.models.json.participants.Participant.ParticipantType.GUEST;
import static com.nextcloud.talk.models.json.participants.Participant.ParticipantType.GUEST_MODERATOR;
import static com.nextcloud.talk.models.json.participants.Participant.ParticipantType.MODERATOR;
import static com.nextcloud.talk.models.json.participants.Participant.ParticipantType.OWNER;
import static com.nextcloud.talk.models.json.participants.Participant.ParticipantType.USER;
import static org.mockito.ArgumentMatchers.argThat;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.inOrder;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.only;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoInteractions;
import static org.mockito.Mockito.verifyNoMoreInteractions;
public class CallParticipantListExternalSignalingTest {
private static class ParticipantsUpdateParticipantBuilder {
private Participant newUser(long inCall, long lastPing, String sessionId, Participant.ParticipantType type,
String userId) {
Participant participant = new Participant();
participant.setInCall(inCall);
participant.setLastPing(lastPing);
participant.setSessionId(sessionId);
participant.setType(type);
participant.setUserId(userId);
return participant;
}
private Participant newGuest(long inCall, long lastPing, String sessionId, Participant.ParticipantType type) {
Participant participant = new Participant();
participant.setInCall(inCall);
participant.setLastPing(lastPing);
participant.setSessionId(sessionId);
participant.setType(type);
return participant;
}
}
private final ParticipantsUpdateParticipantBuilder builder = new ParticipantsUpdateParticipantBuilder();
private CallParticipantList callParticipantList;
private SignalingMessageReceiver.ParticipantListMessageListener participantListMessageListener;
private CallParticipantList.Observer mockedCallParticipantListObserver;
private Collection<Participant> expectedJoined;
private Collection<Participant> expectedUpdated;
private Collection<Participant> expectedLeft;
private Collection<Participant> expectedUnchanged;
// The order of the left participants in some tests depends on how they are internally sorted by the map, so the
// list of left participants needs to be checked ignoring the sorting (or, rather, sorting by session ID as in
// expectedLeft).
// Other tests can just relay on the not guaranteed, but known internal sorting of the elements.
private final ArgumentMatcher<List<Participant>> matchesExpectedLeftIgnoringOrder = left -> {
Collections.sort(left, Comparator.comparing(Participant::getSessionId));
return expectedLeft.equals(left);
};
@Before
public void setUp() {
SignalingMessageReceiver mockedSignalingMessageReceiver = mock(SignalingMessageReceiver.class);
callParticipantList = new CallParticipantList(mockedSignalingMessageReceiver);
mockedCallParticipantListObserver = mock(CallParticipantList.Observer.class);
// Get internal ParticipantListMessageListener from callParticipantList set in the
// mockedSignalingMessageReceiver.
ArgumentCaptor<SignalingMessageReceiver.ParticipantListMessageListener> participantListMessageListenerArgumentCaptor =
ArgumentCaptor.forClass(SignalingMessageReceiver.ParticipantListMessageListener.class);
verify(mockedSignalingMessageReceiver).addListener(participantListMessageListenerArgumentCaptor.capture());
participantListMessageListener = participantListMessageListenerArgumentCaptor.getValue();
expectedJoined = new ArrayList<>();
expectedUpdated = new ArrayList<>();
expectedLeft = new ArrayList<>();
expectedUnchanged = new ArrayList<>();
}
@Test
public void testParticipantsUpdateJoinRoom() {
List<Participant> participants = new ArrayList<>();
participants.add(builder.newUser(DISCONNECTED, 1, "theSessionId1", MODERATOR, "theUserId1"));
callParticipantList.addObserver(mockedCallParticipantListObserver);
participantListMessageListener.onParticipantsUpdate(participants);
verifyNoInteractions(mockedCallParticipantListObserver);
}
@Test
public void testParticipantsUpdateJoinRoomSeveralParticipants() {
List<Participant> participants = new ArrayList<>();
participants.add(builder.newUser(DISCONNECTED, 1, "theSessionId1", MODERATOR, "theUserId1"));
participants.add(builder.newGuest(DISCONNECTED, 2, "theSessionId2", GUEST));
participants.add(builder.newUser(DISCONNECTED, 3, "theSessionId3", USER, "theUserId3"));
callParticipantList.addObserver(mockedCallParticipantListObserver);
participantListMessageListener.onParticipantsUpdate(participants);
participants = new ArrayList<>();
participants.add(builder.newUser(DISCONNECTED, 1, "theSessionId1", MODERATOR, "theUserId1"));
participants.add(builder.newGuest(DISCONNECTED, 2, "theSessionId2", GUEST));
participants.add(builder.newUser(DISCONNECTED, 3, "theSessionId3", USER, "theUserId3"));
participants.add(builder.newUser(DISCONNECTED, 4, "theSessionId4", USER, "theUserId4"));
participants.add(builder.newUser(DISCONNECTED, 5, "theSessionId5", OWNER, "theUserId5"));
participantListMessageListener.onParticipantsUpdate(participants);
verifyNoInteractions(mockedCallParticipantListObserver);
}
@Test
public void testParticipantsUpdateJoinRoomThenJoinCall() {
List<Participant> participants = new ArrayList<>();
participants.add(builder.newUser(DISCONNECTED, 1, "theSessionId1", MODERATOR, "theUserId1"));
participantListMessageListener.onParticipantsUpdate(participants);
participants = new ArrayList<>();
participants.add(builder.newUser(IN_CALL | WITH_AUDIO, 1, "theSessionId1", MODERATOR, "theUserId1"));
callParticipantList.addObserver(mockedCallParticipantListObserver);
participantListMessageListener.onParticipantsUpdate(participants);
expectedJoined.add(builder.newUser(IN_CALL | WITH_AUDIO, 1, "theSessionId1", MODERATOR, "theUserId1"));
verify(mockedCallParticipantListObserver, only()).onCallParticipantsChanged(expectedJoined, expectedUpdated,
expectedLeft, expectedUnchanged);
}
@Test
public void testParticipantsUpdateJoinRoomThenJoinCallSeveralParticipants() {
List<Participant> participants = new ArrayList<>();
participants.add(builder.newUser(DISCONNECTED, 1, "theSessionId1", MODERATOR, "theUserId1"));
participants.add(builder.newGuest(DISCONNECTED, 2, "theSessionId2", GUEST));
participants.add(builder.newUser(DISCONNECTED, 3, "theSessionId3", USER, "theUserId3"));
participants.add(builder.newUser(DISCONNECTED, 4, "theSessionId4", USER, "theUserId4"));
participantListMessageListener.onParticipantsUpdate(participants);
participants = new ArrayList<>();
participants.add(builder.newUser(DISCONNECTED, 1, "theSessionId1", MODERATOR, "theUserId1"));
participants.add(builder.newGuest(DISCONNECTED, 2, "theSessionId2", GUEST));
participants.add(builder.newUser(DISCONNECTED, 3, "theSessionId3", USER, "theUserId3"));
participants.add(builder.newUser(IN_CALL, 4, "theSessionId4", USER, "theUserId4"));
participantListMessageListener.onParticipantsUpdate(participants);
participants = new ArrayList<>();
participants.add(builder.newUser(IN_CALL | WITH_AUDIO, 1, "theSessionId1", MODERATOR, "theUserId1"));
participants.add(builder.newGuest(IN_CALL, 2, "theSessionId2", GUEST));
participants.add(builder.newUser(DISCONNECTED, 3, "theSessionId3", USER, "theUserId3"));
participants.add(builder.newUser(IN_CALL, 4, "theSessionId4", USER, "theUserId4"));
callParticipantList.addObserver(mockedCallParticipantListObserver);
participantListMessageListener.onParticipantsUpdate(participants);
expectedJoined.add(builder.newUser(IN_CALL | WITH_AUDIO, 1, "theSessionId1", MODERATOR, "theUserId1"));
expectedJoined.add(builder.newGuest(IN_CALL, 2, "theSessionId2", GUEST));
expectedUnchanged.add(builder.newUser(IN_CALL, 4, "theSessionId4", USER, "theUserId4"));
verify(mockedCallParticipantListObserver, only()).onCallParticipantsChanged(expectedJoined, expectedUpdated,
expectedLeft, expectedUnchanged);
}
@Test
public void testParticipantsUpdateJoinRoomAndCall() {
List<Participant> participants = new ArrayList<>();
participants.add(builder.newUser(IN_CALL | WITH_AUDIO, 1, "theSessionId1", MODERATOR, "theUserId1"));
callParticipantList.addObserver(mockedCallParticipantListObserver);
participantListMessageListener.onParticipantsUpdate(participants);
expectedJoined.add(builder.newUser(IN_CALL | WITH_AUDIO, 1, "theSessionId1", MODERATOR, "theUserId1"));
verify(mockedCallParticipantListObserver, only()).onCallParticipantsChanged(expectedJoined, expectedUpdated,
expectedLeft, expectedUnchanged);
}
@Test
public void testParticipantsUpdateJoinRoomAndCallSeveralParticipants() {
List<Participant> participants = new ArrayList<>();
participants.add(builder.newUser(DISCONNECTED, 3, "theSessionId3", USER, "theUserId3"));
participants.add(builder.newUser(IN_CALL, 4, "theSessionId4", USER, "theUserId4"));
participantListMessageListener.onParticipantsUpdate(participants);
callParticipantList.addObserver(mockedCallParticipantListObserver);
participants = new ArrayList<>();
participants.add(builder.newUser(IN_CALL | WITH_AUDIO, 1, "theSessionId1", MODERATOR, "theUserId1"));
participants.add(builder.newGuest(IN_CALL, 2, "theSessionId2", GUEST));
participants.add(builder.newUser(DISCONNECTED, 3, "theSessionId3", USER, "theUserId3"));
participants.add(builder.newUser(IN_CALL, 4, "theSessionId4", USER, "theUserId4"));
participantListMessageListener.onParticipantsUpdate(participants);
expectedJoined.add(builder.newUser(IN_CALL | WITH_AUDIO, 1, "theSessionId1", MODERATOR, "theUserId1"));
expectedJoined.add(builder.newGuest(IN_CALL, 2, "theSessionId2", GUEST));
expectedUnchanged.add(builder.newUser(IN_CALL, 4, "theSessionId4", USER, "theUserId4"));
verify(mockedCallParticipantListObserver, only()).onCallParticipantsChanged(expectedJoined, expectedUpdated,
expectedLeft, expectedUnchanged);
}
@Test
public void testParticipantsUpdateJoinRoomAndCallRepeated() {
List<Participant> participants = new ArrayList<>();
participants.add(builder.newUser(IN_CALL | WITH_AUDIO, 1, "theSessionId1", MODERATOR, "theUserId1"));
callParticipantList.addObserver(mockedCallParticipantListObserver);
participantListMessageListener.onParticipantsUpdate(participants);
participantListMessageListener.onParticipantsUpdate(participants);
participantListMessageListener.onParticipantsUpdate(participants);
expectedJoined.add(builder.newUser(IN_CALL | WITH_AUDIO, 1, "theSessionId1", MODERATOR, "theUserId1"));
verify(mockedCallParticipantListObserver, only()).onCallParticipantsChanged(expectedJoined, expectedUpdated,
expectedLeft, expectedUnchanged);
}
@Test
public void testParticipantsUpdateChangeCallFlags() {
List<Participant> participants = new ArrayList<>();
participants.add(builder.newUser(IN_CALL | WITH_AUDIO, 1, "theSessionId1", MODERATOR, "theUserId1"));
participantListMessageListener.onParticipantsUpdate(participants);
participants = new ArrayList<>();
participants.add(builder.newUser(IN_CALL | WITH_AUDIO | WITH_VIDEO, 1, "theSessionId1", MODERATOR, "theUserId1"));
callParticipantList.addObserver(mockedCallParticipantListObserver);
participantListMessageListener.onParticipantsUpdate(participants);
expectedUpdated.add(builder.newUser(IN_CALL | WITH_AUDIO | WITH_VIDEO, 1, "theSessionId1", MODERATOR, "theUserId1"));
verify(mockedCallParticipantListObserver, only()).onCallParticipantsChanged(expectedJoined, expectedUpdated,
expectedLeft, expectedUnchanged);
}
@Test
public void testParticipantsUpdateChangeCallFlagsSeveralParticipants() {
List<Participant> participants = new ArrayList<>();
participants.add(builder.newUser(IN_CALL | WITH_AUDIO, 1, "theSessionId1", MODERATOR, "theUserId1"));
participants.add(builder.newGuest(IN_CALL | WITH_AUDIO | WITH_VIDEO, 2, "theSessionId2", GUEST));
participants.add(builder.newUser(DISCONNECTED, 3, "theSessionId3", USER, "theUserId3"));
participants.add(builder.newUser(IN_CALL, 4, "theSessionId4", USER, "theUserId4"));
participantListMessageListener.onParticipantsUpdate(participants);
participants = new ArrayList<>();
participants.add(builder.newUser(IN_CALL, 1, "theSessionId1", MODERATOR, "theUserId1"));
participants.add(builder.newGuest(IN_CALL | WITH_AUDIO | WITH_VIDEO, 2, "theSessionId2", GUEST));
participants.add(builder.newUser(DISCONNECTED, 3, "theSessionId3", USER, "theUserId3"));
participants.add(builder.newUser(IN_CALL | WITH_VIDEO, 4, "theSessionId4", USER, "theUserId4"));
callParticipantList.addObserver(mockedCallParticipantListObserver);
participantListMessageListener.onParticipantsUpdate(participants);
expectedUpdated.add(builder.newUser(IN_CALL, 1, "theSessionId1", MODERATOR, "theUserId1"));
expectedUpdated.add(builder.newUser(IN_CALL | WITH_VIDEO, 4, "theSessionId4", USER, "theUserId4"));
expectedUnchanged.add(builder.newGuest(IN_CALL | WITH_AUDIO | WITH_VIDEO, 2, "theSessionId2", GUEST));
verify(mockedCallParticipantListObserver, only()).onCallParticipantsChanged(expectedJoined, expectedUpdated,
expectedLeft, expectedUnchanged);
}
@Test
public void testParticipantsUpdateChangeLastPing() {
List<Participant> participants = new ArrayList<>();
participants.add(builder.newUser(IN_CALL | WITH_AUDIO, 1, "theSessionId1", MODERATOR, "theUserId1"));
participantListMessageListener.onParticipantsUpdate(participants);
participants = new ArrayList<>();
participants.add(builder.newUser(IN_CALL | WITH_AUDIO, 42, "theSessionId1", MODERATOR, "theUserId1"));
callParticipantList.addObserver(mockedCallParticipantListObserver);
participantListMessageListener.onParticipantsUpdate(participants);
verifyNoInteractions(mockedCallParticipantListObserver);
}
@Test
public void testParticipantsUpdateChangeLastPingSeveralParticipants() {
List<Participant> participants = new ArrayList<>();
participants.add(builder.newUser(IN_CALL | WITH_AUDIO, 1, "theSessionId1", MODERATOR, "theUserId1"));
participants.add(builder.newGuest(IN_CALL | WITH_AUDIO, 2, "theSessionId2", GUEST));
participants.add(builder.newUser(IN_CALL | WITH_AUDIO, 3, "theSessionId3", USER, "theUserId3"));
participantListMessageListener.onParticipantsUpdate(participants);
participants = new ArrayList<>();
participants.add(builder.newUser(IN_CALL | WITH_AUDIO, 42, "theSessionId1", MODERATOR, "theUserId1"));
participants.add(builder.newGuest(IN_CALL | WITH_AUDIO, 108, "theSessionId2", GUEST));
participants.add(builder.newUser(IN_CALL | WITH_AUDIO, 815, "theSessionId3", USER, "theUserId3"));
callParticipantList.addObserver(mockedCallParticipantListObserver);
participantListMessageListener.onParticipantsUpdate(participants);
verifyNoInteractions(mockedCallParticipantListObserver);
}
@Test
public void testParticipantsUpdateChangeParticipantType() {
List<Participant> participants = new ArrayList<>();
participants.add(builder.newUser(IN_CALL | WITH_AUDIO, 1, "theSessionId1", MODERATOR, "theUserId1"));
participantListMessageListener.onParticipantsUpdate(participants);
participants = new ArrayList<>();
participants.add(builder.newUser(IN_CALL | WITH_AUDIO, 1, "theSessionId1", USER, "theUserId1"));
callParticipantList.addObserver(mockedCallParticipantListObserver);
participantListMessageListener.onParticipantsUpdate(participants);
verifyNoInteractions(mockedCallParticipantListObserver);
}
@Test
public void testParticipantsUpdateChangeParticipantTypeeSeveralParticipants() {
List<Participant> participants = new ArrayList<>();
participants.add(builder.newUser(IN_CALL | WITH_AUDIO, 1, "theSessionId1", MODERATOR, "theUserId1"));
participants.add(builder.newGuest(IN_CALL | WITH_AUDIO, 2, "theSessionId2", GUEST));
participants.add(builder.newUser(IN_CALL | WITH_AUDIO, 3, "theSessionId3", USER, "theUserId3"));
participantListMessageListener.onParticipantsUpdate(participants);
participants = new ArrayList<>();
participants.add(builder.newUser(IN_CALL | WITH_AUDIO, 1, "theSessionId1", USER, "theUserId1"));
participants.add(builder.newGuest(IN_CALL | WITH_AUDIO, 2, "theSessionId2", GUEST_MODERATOR));
participants.add(builder.newUser(IN_CALL | WITH_AUDIO, 3, "theSessionId3", MODERATOR, "theUserId3"));
callParticipantList.addObserver(mockedCallParticipantListObserver);
participantListMessageListener.onParticipantsUpdate(participants);
verifyNoInteractions(mockedCallParticipantListObserver);
}
@Test
public void testParticipantsUpdateLeaveCall() {
List<Participant> participants = new ArrayList<>();
participants.add(builder.newUser(IN_CALL | WITH_AUDIO, 1, "theSessionId1", MODERATOR, "theUserId1"));
participantListMessageListener.onParticipantsUpdate(participants);
callParticipantList.addObserver(mockedCallParticipantListObserver);
participants = new ArrayList<>();
participants.add(builder.newUser(DISCONNECTED, 1, "theSessionId1", MODERATOR, "theUserId1"));
participantListMessageListener.onParticipantsUpdate(participants);
expectedLeft.add(builder.newUser(DISCONNECTED, 1, "theSessionId1", MODERATOR, "theUserId1"));
verify(mockedCallParticipantListObserver, only()).onCallParticipantsChanged(expectedJoined, expectedUpdated,
expectedLeft, expectedUnchanged);
}
@Test
public void testParticipantsUpdateLeaveCallSeveralParticipants() {
List<Participant> participants = new ArrayList<>();
participants.add(builder.newUser(IN_CALL | WITH_AUDIO, 1, "theSessionId1", MODERATOR, "theUserId1"));
participants.add(builder.newGuest(IN_CALL, 2, "theSessionId2", GUEST));
participants.add(builder.newUser(DISCONNECTED, 3, "theSessionId3", USER, "theUserId3"));
participants.add(builder.newUser(IN_CALL, 4, "theSessionId4", USER, "theUserId4"));
participantListMessageListener.onParticipantsUpdate(participants);
callParticipantList.addObserver(mockedCallParticipantListObserver);
participants = new ArrayList<>();
participants.add(builder.newUser(DISCONNECTED, 1, "theSessionId1", MODERATOR, "theUserId1"));
participants.add(builder.newGuest(DISCONNECTED, 2, "theSessionId2", GUEST));
participants.add(builder.newUser(DISCONNECTED, 3, "theSessionId3", USER, "theUserId3"));
participants.add(builder.newUser(IN_CALL, 4, "theSessionId4", USER, "theUserId4"));
participantListMessageListener.onParticipantsUpdate(participants);
expectedLeft.add(builder.newUser(DISCONNECTED, 1, "theSessionId1", MODERATOR, "theUserId1"));
expectedLeft.add(builder.newGuest(DISCONNECTED, 2, "theSessionId2", GUEST));
expectedUnchanged.add(builder.newUser(IN_CALL, 4, "theSessionId4", USER, "theUserId4"));
verify(mockedCallParticipantListObserver, only()).onCallParticipantsChanged(expectedJoined, expectedUpdated,
expectedLeft, expectedUnchanged);
}
@Test
public void testParticipantsUpdateLeaveCallThenLeaveRoom() {
List<Participant> participants = new ArrayList<>();
participants.add(builder.newUser(IN_CALL | WITH_AUDIO, 1, "theSessionId1", MODERATOR, "theUserId1"));
participantListMessageListener.onParticipantsUpdate(participants);
participants = new ArrayList<>();
participants.add(builder.newUser(DISCONNECTED, 1, "theSessionId1", MODERATOR, "theUserId1"));
participantListMessageListener.onParticipantsUpdate(participants);
callParticipantList.addObserver(mockedCallParticipantListObserver);
participants = new ArrayList<>();
participantListMessageListener.onParticipantsUpdate(participants);
verifyNoInteractions(mockedCallParticipantListObserver);
}
@Test
public void testParticipantsUpdateLeaveCallThenLeaveRoomSeveralParticipants() {
List<Participant> participants = new ArrayList<>();
participants.add(builder.newUser(IN_CALL | WITH_AUDIO, 1, "theSessionId1", MODERATOR, "theUserId1"));
participants.add(builder.newGuest(IN_CALL, 2, "theSessionId2", GUEST));
participants.add(builder.newUser(DISCONNECTED, 3, "theSessionId3", USER, "theUserId3"));
participants.add(builder.newUser(IN_CALL, 4, "theSessionId4", USER, "theUserId4"));
participantListMessageListener.onParticipantsUpdate(participants);
participants = new ArrayList<>();
participants.add(builder.newUser(DISCONNECTED, 1, "theSessionId1", MODERATOR, "theUserId1"));
participants.add(builder.newGuest(DISCONNECTED, 2, "theSessionId2", GUEST));
participants.add(builder.newUser(DISCONNECTED, 3, "theSessionId3", USER, "theUserId3"));
participants.add(builder.newUser(IN_CALL, 4, "theSessionId4", USER, "theUserId4"));
participantListMessageListener.onParticipantsUpdate(participants);
callParticipantList.addObserver(mockedCallParticipantListObserver);
participants = new ArrayList<>();
participants.add(builder.newUser(DISCONNECTED, 3, "theSessionId3", USER, "theUserId3"));
participants.add(builder.newUser(IN_CALL, 4, "theSessionId4", USER, "theUserId4"));
participantListMessageListener.onParticipantsUpdate(participants);
verifyNoInteractions(mockedCallParticipantListObserver);
}
@Test
public void testParticipantsUpdateLeaveCallAndRoom() {
List<Participant> participants = new ArrayList<>();
participants.add(builder.newUser(IN_CALL | WITH_AUDIO, 1, "theSessionId1", MODERATOR, "theUserId1"));
participantListMessageListener.onParticipantsUpdate(participants);
callParticipantList.addObserver(mockedCallParticipantListObserver);
participants = new ArrayList<>();
participantListMessageListener.onParticipantsUpdate(participants);
expectedLeft.add(builder.newUser(DISCONNECTED, 1, "theSessionId1", MODERATOR, "theUserId1"));
verify(mockedCallParticipantListObserver, only()).onCallParticipantsChanged(expectedJoined, expectedUpdated,
expectedLeft, expectedUnchanged);
}
@Test
public void testParticipantsUpdateLeaveCallAndRoomSeveralParticipants() {
List<Participant> participants = new ArrayList<>();
participants.add(builder.newUser(IN_CALL | WITH_AUDIO, 1, "theSessionId1", MODERATOR, "theUserId1"));
participants.add(builder.newGuest(IN_CALL, 2, "theSessionId2", GUEST));
participants.add(builder.newUser(DISCONNECTED, 3, "theSessionId3", USER, "theUserId3"));
participants.add(builder.newUser(IN_CALL, 4, "theSessionId4", USER, "theUserId4"));
participantListMessageListener.onParticipantsUpdate(participants);
callParticipantList.addObserver(mockedCallParticipantListObserver);
participants = new ArrayList<>();
participants.add(builder.newUser(DISCONNECTED, 3, "theSessionId3", USER, "theUserId3"));
participants.add(builder.newUser(IN_CALL, 4, "theSessionId4", USER, "theUserId4"));
participantListMessageListener.onParticipantsUpdate(participants);
expectedLeft.add(builder.newUser(DISCONNECTED, 1, "theSessionId1", MODERATOR, "theUserId1"));
expectedLeft.add(builder.newGuest(DISCONNECTED, 2, "theSessionId2", GUEST));
expectedUnchanged.add(builder.newUser(IN_CALL, 4, "theSessionId4", USER, "theUserId4"));
verify(mockedCallParticipantListObserver).onCallParticipantsChanged(eq(expectedJoined), eq(expectedUpdated),
argThat(matchesExpectedLeftIgnoringOrder), eq(expectedUnchanged));
}
@Test
public void testParticipantsUpdateSeveralEventsSeveralParticipants() {
List<Participant> participants = new ArrayList<>();
participants.add(builder.newUser(IN_CALL | WITH_AUDIO, 1, "theSessionId1", MODERATOR, "theUserId1"));
participants.add(builder.newGuest(IN_CALL, 2, "theSessionId2", GUEST));
participants.add(builder.newUser(DISCONNECTED, 3, "theSessionId3", USER, "theUserId3"));
participants.add(builder.newUser(IN_CALL, 4, "theSessionId4", USER, "theUserId4"));
participants.add(builder.newUser(IN_CALL, 5, "theSessionId5", OWNER, "theUserId5"));
// theSessionId6 has not joined yet.
participants.add(builder.newGuest(IN_CALL | WITH_VIDEO, 7, "theSessionId7", GUEST));
participants.add(builder.newUser(DISCONNECTED, 8, "theSessionId8", USER, "theUserId8"));
participants.add(builder.newUser(IN_CALL | WITH_AUDIO, 9, "theSessionId9", MODERATOR, "theUserId9"));
participantListMessageListener.onParticipantsUpdate(participants);
callParticipantList.addObserver(mockedCallParticipantListObserver);
participants = new ArrayList<>();
// theSessionId1 is gone.
participants.add(builder.newGuest(DISCONNECTED, 2, "theSessionId2", GUEST));
participants.add(builder.newUser(DISCONNECTED, 3, "theSessionId3", USER, "theUserId3"));
participants.add(builder.newUser(IN_CALL, 4, "theSessionId4", USER, "theUserId4"));
participants.add(builder.newUser(IN_CALL | WITH_AUDIO | WITH_VIDEO, 5, "theSessionId5", OWNER, "theUserId5"));
participants.add(builder.newGuest(IN_CALL | WITH_AUDIO, 6, "theSessionId6", GUEST));
participants.add(builder.newGuest(IN_CALL, 7, "theSessionId7", GUEST));
participants.add(builder.newUser(IN_CALL, 8, "theSessionId8", USER, "theUserId8"));
participants.add(builder.newUser(IN_CALL | WITH_AUDIO, 42, "theSessionId9", USER, "theUserId9"));
participantListMessageListener.onParticipantsUpdate(participants);
expectedJoined.add(builder.newGuest(IN_CALL | WITH_AUDIO, 6, "theSessionId6", GUEST));
expectedJoined.add(builder.newUser(IN_CALL, 8, "theSessionId8", USER, "theUserId8"));
expectedUpdated.add(builder.newUser(IN_CALL | WITH_AUDIO | WITH_VIDEO, 5, "theSessionId5", OWNER, "theUserId5"));
expectedUpdated.add(builder.newGuest(IN_CALL, 7, "theSessionId7", GUEST));
expectedLeft.add(builder.newUser(DISCONNECTED, 1, "theSessionId1", MODERATOR, "theUserId1"));
expectedLeft.add(builder.newGuest(DISCONNECTED, 2, "theSessionId2", GUEST));
expectedUnchanged.add(builder.newUser(IN_CALL, 4, "theSessionId4", USER, "theUserId4"));
// Last ping and participant type are not seen as changed, even if they did.
expectedUnchanged.add(builder.newUser(IN_CALL | WITH_AUDIO, 42, "theSessionId9", USER, "theUserId9"));
verify(mockedCallParticipantListObserver).onCallParticipantsChanged(eq(expectedJoined), eq(expectedUpdated),
argThat(matchesExpectedLeftIgnoringOrder), eq(expectedUnchanged));
}
@Test
public void testAllParticipantsUpdateDisconnected() {
List<Participant> participants = new ArrayList<>();
participants.add(builder.newUser(IN_CALL, 1, "theSessionId1", MODERATOR, "theUserId1"));
participantListMessageListener.onParticipantsUpdate(participants);
callParticipantList.addObserver(mockedCallParticipantListObserver);
participantListMessageListener.onAllParticipantsUpdate(DISCONNECTED);
expectedLeft.add(builder.newUser(DISCONNECTED, 1, "theSessionId1", MODERATOR, "theUserId1"));
InOrder inOrder = inOrder(mockedCallParticipantListObserver);
inOrder.verify(mockedCallParticipantListObserver).onCallEndedForAll();
inOrder.verify(mockedCallParticipantListObserver).onCallParticipantsChanged(expectedJoined, expectedUpdated,
expectedLeft, expectedUnchanged);
}
@Test
public void testAllParticipantsUpdateDisconnectedWithSeveralParticipants() {
List<Participant> participants = new ArrayList<>();
participants.add(builder.newUser(IN_CALL, 1, "theSessionId1", MODERATOR, "theUserId1"));
participants.add(builder.newUser(DISCONNECTED, 2, "theSessionId2", USER, "theUserId2"));
participants.add(builder.newUser(IN_CALL | WITH_AUDIO, 3, "theSessionId3", USER, "theUserId3"));
participants.add(builder.newGuest(IN_CALL | WITH_AUDIO | WITH_VIDEO, 4, "theSessionId4", GUEST));
participantListMessageListener.onParticipantsUpdate(participants);
callParticipantList.addObserver(mockedCallParticipantListObserver);
participantListMessageListener.onAllParticipantsUpdate(DISCONNECTED);
expectedLeft.add(builder.newUser(DISCONNECTED, 1, "theSessionId1", MODERATOR, "theUserId1"));
expectedLeft.add(builder.newUser(DISCONNECTED, 3, "theSessionId3", USER, "theUserId3"));
expectedLeft.add(builder.newGuest(DISCONNECTED, 4, "theSessionId4", GUEST));
InOrder inOrder = inOrder(mockedCallParticipantListObserver);
inOrder.verify(mockedCallParticipantListObserver).onCallEndedForAll();
inOrder.verify(mockedCallParticipantListObserver).onCallParticipantsChanged(eq(expectedJoined), eq(expectedUpdated),
argThat(matchesExpectedLeftIgnoringOrder), eq(expectedUnchanged));
}
@Test
public void testAllParticipantsUpdateDisconnectedNoOneInCall() {
callParticipantList.addObserver(mockedCallParticipantListObserver);
participantListMessageListener.onAllParticipantsUpdate(DISCONNECTED);
InOrder inOrder = inOrder(mockedCallParticipantListObserver);
inOrder.verify(mockedCallParticipantListObserver).onCallEndedForAll();
verifyNoMoreInteractions(mockedCallParticipantListObserver);
}
@Test
public void testAllParticipantsUpdateDisconnectedThenJoinCallAgain() {
List<Participant> participants = new ArrayList<>();
participants.add(builder.newUser(IN_CALL, 1, "theSessionId1", MODERATOR, "theUserId1"));
participantListMessageListener.onParticipantsUpdate(participants);
participantListMessageListener.onAllParticipantsUpdate(DISCONNECTED);
callParticipantList.addObserver(mockedCallParticipantListObserver);
participants = new ArrayList<>();
participants.add(builder.newUser(IN_CALL, 1, "theSessionId1", MODERATOR, "theUserId1"));
participantListMessageListener.onParticipantsUpdate(participants);
expectedJoined.add(builder.newUser(IN_CALL, 1, "theSessionId1", MODERATOR, "theUserId1"));
verify(mockedCallParticipantListObserver, only()).onCallParticipantsChanged(expectedJoined, expectedUpdated,
expectedLeft, expectedUnchanged);
}
}

View file

@ -0,0 +1,535 @@
/*
* Nextcloud Talk application
*
* @author Daniel Calviño Sánchez
* Copyright (C) 2022 Daniel Calviño Sánchez <danxuliu@gmail.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.nextcloud.talk.call;
import com.nextcloud.talk.models.json.participants.Participant;
import com.nextcloud.talk.signaling.SignalingMessageReceiver;
import org.junit.Before;
import org.junit.Test;
import org.mockito.ArgumentCaptor;
import org.mockito.ArgumentMatcher;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import static com.nextcloud.talk.models.json.participants.Participant.InCallFlags.DISCONNECTED;
import static com.nextcloud.talk.models.json.participants.Participant.InCallFlags.IN_CALL;
import static com.nextcloud.talk.models.json.participants.Participant.InCallFlags.WITH_AUDIO;
import static com.nextcloud.talk.models.json.participants.Participant.InCallFlags.WITH_VIDEO;
import static org.mockito.ArgumentMatchers.argThat;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.only;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoInteractions;
public class CallParticipantListInternalSignalingTest {
private static class UsersInRoomParticipantBuilder {
private Participant newUser(long inCall, long lastPing, String sessionId, String userId) {
Participant participant = new Participant();
participant.setInCall(inCall);
participant.setLastPing(lastPing);
participant.setSessionId(sessionId);
participant.setUserId(userId);
return participant;
}
private Participant newGuest(long inCall, long lastPing, String sessionId) {
Participant participant = new Participant();
participant.setInCall(inCall);
participant.setLastPing(lastPing);
participant.setSessionId(sessionId);
return participant;
}
}
private final UsersInRoomParticipantBuilder builder = new UsersInRoomParticipantBuilder();
private CallParticipantList callParticipantList;
private SignalingMessageReceiver.ParticipantListMessageListener participantListMessageListener;
private CallParticipantList.Observer mockedCallParticipantListObserver;
private Collection<Participant> expectedJoined;
private Collection<Participant> expectedUpdated;
private Collection<Participant> expectedLeft;
private Collection<Participant> expectedUnchanged;
// The order of the left participants in some tests depends on how they are internally sorted by the map, so the
// list of left participants needs to be checked ignoring the sorting (or, rather, sorting by session ID as in
// expectedLeft).
// Other tests can just relay on the not guaranteed, but known internal sorting of the elements.
private final ArgumentMatcher<List<Participant>> matchesExpectedLeftIgnoringOrder = left -> {
Collections.sort(left, Comparator.comparing(Participant::getSessionId));
return expectedLeft.equals(left);
};
@Before
public void setUp() {
SignalingMessageReceiver mockedSignalingMessageReceiver = mock(SignalingMessageReceiver.class);
callParticipantList = new CallParticipantList(mockedSignalingMessageReceiver);
mockedCallParticipantListObserver = mock(CallParticipantList.Observer.class);
// Get internal ParticipantListMessageListener from callParticipantList set in the
// mockedSignalingMessageReceiver.
ArgumentCaptor<SignalingMessageReceiver.ParticipantListMessageListener> participantListMessageListenerArgumentCaptor =
ArgumentCaptor.forClass(SignalingMessageReceiver.ParticipantListMessageListener.class);
verify(mockedSignalingMessageReceiver).addListener(participantListMessageListenerArgumentCaptor.capture());
participantListMessageListener = participantListMessageListenerArgumentCaptor.getValue();
expectedJoined = new ArrayList<>();
expectedUpdated = new ArrayList<>();
expectedLeft = new ArrayList<>();
expectedUnchanged = new ArrayList<>();
}
@Test
public void testUsersInRoomJoinRoom() {
List<Participant> participants = new ArrayList<>();
participants.add(builder.newUser(DISCONNECTED, 1, "theSessionId1", "theUserId1"));
callParticipantList.addObserver(mockedCallParticipantListObserver);
participantListMessageListener.onUsersInRoom(participants);
verifyNoInteractions(mockedCallParticipantListObserver);
}
@Test
public void testUsersInRoomJoinRoomSeveralParticipants() {
List<Participant> participants = new ArrayList<>();
participants.add(builder.newUser(DISCONNECTED, 1, "theSessionId1", "theUserId1"));
participants.add(builder.newGuest(DISCONNECTED, 2, "theSessionId2"));
participants.add(builder.newUser(DISCONNECTED, 3, "theSessionId3", "theUserId3"));
callParticipantList.addObserver(mockedCallParticipantListObserver);
participantListMessageListener.onUsersInRoom(participants);
participants = new ArrayList<>();
participants.add(builder.newUser(DISCONNECTED, 1, "theSessionId1", "theUserId1"));
participants.add(builder.newGuest(DISCONNECTED, 2, "theSessionId2"));
participants.add(builder.newUser(DISCONNECTED, 3, "theSessionId3", "theUserId3"));
participants.add(builder.newUser(DISCONNECTED, 4, "theSessionId4", "theUserId4"));
participants.add(builder.newUser(DISCONNECTED, 5, "theSessionId5", "theUserId5"));
participantListMessageListener.onUsersInRoom(participants);
verifyNoInteractions(mockedCallParticipantListObserver);
}
@Test
public void testUsersInRoomJoinRoomThenJoinCall() {
List<Participant> participants = new ArrayList<>();
participants.add(builder.newUser(DISCONNECTED, 1, "theSessionId1", "theUserId1"));
participantListMessageListener.onUsersInRoom(participants);
participants = new ArrayList<>();
participants.add(builder.newUser(IN_CALL | WITH_AUDIO, 1, "theSessionId1", "theUserId1"));
callParticipantList.addObserver(mockedCallParticipantListObserver);
participantListMessageListener.onUsersInRoom(participants);
expectedJoined.add(builder.newUser(IN_CALL | WITH_AUDIO, 1, "theSessionId1", "theUserId1"));
verify(mockedCallParticipantListObserver, only()).onCallParticipantsChanged(expectedJoined, expectedUpdated,
expectedLeft, expectedUnchanged);
}
@Test
public void testUsersInRoomJoinRoomThenJoinCallSeveralParticipants() {
List<Participant> participants = new ArrayList<>();
participants.add(builder.newUser(DISCONNECTED, 1, "theSessionId1", "theUserId1"));
participants.add(builder.newGuest(DISCONNECTED, 2, "theSessionId2"));
participants.add(builder.newUser(DISCONNECTED, 3, "theSessionId3", "theUserId3"));
participants.add(builder.newUser(DISCONNECTED, 4, "theSessionId4", "theUserId4"));
participantListMessageListener.onUsersInRoom(participants);
participants = new ArrayList<>();
participants.add(builder.newUser(DISCONNECTED, 1, "theSessionId1", "theUserId1"));
participants.add(builder.newGuest(DISCONNECTED, 2, "theSessionId2"));
participants.add(builder.newUser(DISCONNECTED, 3, "theSessionId3", "theUserId3"));
participants.add(builder.newUser(IN_CALL, 4, "theSessionId4", "theUserId4"));
participantListMessageListener.onUsersInRoom(participants);
participants = new ArrayList<>();
participants.add(builder.newUser(IN_CALL | WITH_AUDIO, 1, "theSessionId1", "theUserId1"));
participants.add(builder.newGuest(IN_CALL, 2, "theSessionId2"));
participants.add(builder.newUser(DISCONNECTED, 3, "theSessionId3", "theUserId3"));
participants.add(builder.newUser(IN_CALL, 4, "theSessionId4", "theUserId4"));
callParticipantList.addObserver(mockedCallParticipantListObserver);
participantListMessageListener.onUsersInRoom(participants);
expectedJoined.add(builder.newUser(IN_CALL | WITH_AUDIO, 1, "theSessionId1", "theUserId1"));
expectedJoined.add(builder.newGuest(IN_CALL, 2, "theSessionId2"));
expectedUnchanged.add(builder.newUser(IN_CALL, 4, "theSessionId4", "theUserId4"));
verify(mockedCallParticipantListObserver, only()).onCallParticipantsChanged(expectedJoined, expectedUpdated,
expectedLeft, expectedUnchanged);
}
@Test
public void testUsersInRoomJoinRoomAndCall() {
List<Participant> participants = new ArrayList<>();
participants.add(builder.newUser(IN_CALL | WITH_AUDIO, 1, "theSessionId1", "theUserId1"));
callParticipantList.addObserver(mockedCallParticipantListObserver);
participantListMessageListener.onUsersInRoom(participants);
expectedJoined.add(builder.newUser(IN_CALL | WITH_AUDIO, 1, "theSessionId1", "theUserId1"));
verify(mockedCallParticipantListObserver, only()).onCallParticipantsChanged(expectedJoined, expectedUpdated,
expectedLeft, expectedUnchanged);
}
@Test
public void testUsersInRoomJoinRoomAndCallSeveralParticipants() {
List<Participant> participants = new ArrayList<>();
participants.add(builder.newUser(DISCONNECTED, 3, "theSessionId3", "theUserId3"));
participants.add(builder.newUser(IN_CALL, 4, "theSessionId4", "theUserId4"));
participantListMessageListener.onUsersInRoom(participants);
callParticipantList.addObserver(mockedCallParticipantListObserver);
participants = new ArrayList<>();
participants.add(builder.newUser(IN_CALL | WITH_AUDIO, 1, "theSessionId1", "theUserId1"));
participants.add(builder.newGuest(IN_CALL, 2, "theSessionId2"));
participants.add(builder.newUser(DISCONNECTED, 3, "theSessionId3", "theUserId3"));
participants.add(builder.newUser(IN_CALL, 4, "theSessionId4", "theUserId4"));
participantListMessageListener.onUsersInRoom(participants);
expectedJoined.add(builder.newUser(IN_CALL | WITH_AUDIO, 1, "theSessionId1", "theUserId1"));
expectedJoined.add(builder.newGuest(IN_CALL, 2, "theSessionId2"));
expectedUnchanged.add(builder.newUser(IN_CALL, 4, "theSessionId4", "theUserId4"));
verify(mockedCallParticipantListObserver, only()).onCallParticipantsChanged(expectedJoined, expectedUpdated,
expectedLeft, expectedUnchanged);
}
@Test
public void testUsersInRoomJoinRoomAndCallRepeated() {
List<Participant> participants = new ArrayList<>();
participants.add(builder.newUser(IN_CALL | WITH_AUDIO, 1, "theSessionId1", "theUserId1"));
callParticipantList.addObserver(mockedCallParticipantListObserver);
participantListMessageListener.onUsersInRoom(participants);
participantListMessageListener.onUsersInRoom(participants);
participantListMessageListener.onUsersInRoom(participants);
expectedJoined.add(builder.newUser(IN_CALL | WITH_AUDIO, 1, "theSessionId1", "theUserId1"));
verify(mockedCallParticipantListObserver, only()).onCallParticipantsChanged(expectedJoined, expectedUpdated,
expectedLeft, expectedUnchanged);
}
@Test
public void testUsersInRoomChangeCallFlags() {
List<Participant> participants = new ArrayList<>();
participants.add(builder.newUser(IN_CALL | WITH_AUDIO, 1, "theSessionId1", "theUserId1"));
participantListMessageListener.onUsersInRoom(participants);
participants = new ArrayList<>();
participants.add(builder.newUser(IN_CALL | WITH_AUDIO | WITH_VIDEO, 1, "theSessionId1", "theUserId1"));
callParticipantList.addObserver(mockedCallParticipantListObserver);
participantListMessageListener.onUsersInRoom(participants);
expectedUpdated.add(builder.newUser(IN_CALL | WITH_AUDIO | WITH_VIDEO, 1, "theSessionId1", "theUserId1"));
verify(mockedCallParticipantListObserver, only()).onCallParticipantsChanged(expectedJoined, expectedUpdated,
expectedLeft, expectedUnchanged);
}
@Test
public void testUsersInRoomChangeCallFlagsSeveralParticipants() {
List<Participant> participants = new ArrayList<>();
participants.add(builder.newUser(IN_CALL | WITH_AUDIO, 1, "theSessionId1", "theUserId1"));
participants.add(builder.newGuest(IN_CALL | WITH_AUDIO | WITH_VIDEO, 2, "theSessionId2"));
participants.add(builder.newUser(DISCONNECTED, 3, "theSessionId3", "theUserId3"));
participants.add(builder.newUser(IN_CALL, 4, "theSessionId4", "theUserId4"));
participantListMessageListener.onUsersInRoom(participants);
participants = new ArrayList<>();
participants.add(builder.newUser(IN_CALL, 1, "theSessionId1", "theUserId1"));
participants.add(builder.newGuest(IN_CALL | WITH_AUDIO | WITH_VIDEO, 2, "theSessionId2"));
participants.add(builder.newUser(DISCONNECTED, 3, "theSessionId3", "theUserId3"));
participants.add(builder.newUser(IN_CALL | WITH_VIDEO, 4, "theSessionId4", "theUserId4"));
callParticipantList.addObserver(mockedCallParticipantListObserver);
participantListMessageListener.onUsersInRoom(participants);
expectedUpdated.add(builder.newUser(IN_CALL, 1, "theSessionId1", "theUserId1"));
expectedUpdated.add(builder.newUser(IN_CALL | WITH_VIDEO, 4, "theSessionId4", "theUserId4"));
expectedUnchanged.add(builder.newGuest(IN_CALL | WITH_AUDIO | WITH_VIDEO, 2, "theSessionId2"));
verify(mockedCallParticipantListObserver, only()).onCallParticipantsChanged(expectedJoined, expectedUpdated,
expectedLeft, expectedUnchanged);
}
@Test
public void testUsersInRoomChangeLastPing() {
List<Participant> participants = new ArrayList<>();
participants.add(builder.newUser(IN_CALL | WITH_AUDIO, 1, "theSessionId1", "theUserId1"));
participantListMessageListener.onUsersInRoom(participants);
participants = new ArrayList<>();
participants.add(builder.newUser(IN_CALL | WITH_AUDIO, 42, "theSessionId1", "theUserId1"));
callParticipantList.addObserver(mockedCallParticipantListObserver);
participantListMessageListener.onUsersInRoom(participants);
verifyNoInteractions(mockedCallParticipantListObserver);
}
@Test
public void testUsersInRoomChangeLastPingSeveralParticipants() {
List<Participant> participants = new ArrayList<>();
participants.add(builder.newUser(IN_CALL | WITH_AUDIO, 1, "theSessionId1", "theUserId1"));
participants.add(builder.newGuest(IN_CALL | WITH_AUDIO, 2, "theSessionId2"));
participants.add(builder.newUser(IN_CALL | WITH_AUDIO, 3, "theSessionId3", "theUserId3"));
participantListMessageListener.onUsersInRoom(participants);
participants = new ArrayList<>();
participants.add(builder.newUser(IN_CALL | WITH_AUDIO, 42, "theSessionId1", "theUserId1"));
participants.add(builder.newGuest(IN_CALL | WITH_AUDIO, 108, "theSessionId2"));
participants.add(builder.newUser(IN_CALL | WITH_AUDIO, 815, "theSessionId3", "theUserId3"));
callParticipantList.addObserver(mockedCallParticipantListObserver);
participantListMessageListener.onUsersInRoom(participants);
verifyNoInteractions(mockedCallParticipantListObserver);
}
@Test
public void testUsersInRoomLeaveCall() {
List<Participant> participants = new ArrayList<>();
participants.add(builder.newUser(IN_CALL | WITH_AUDIO, 1, "theSessionId1", "theUserId1"));
participantListMessageListener.onUsersInRoom(participants);
callParticipantList.addObserver(mockedCallParticipantListObserver);
participants = new ArrayList<>();
participants.add(builder.newUser(DISCONNECTED, 1, "theSessionId1", "theUserId1"));
participantListMessageListener.onUsersInRoom(participants);
expectedLeft.add(builder.newUser(DISCONNECTED, 1, "theSessionId1", "theUserId1"));
verify(mockedCallParticipantListObserver, only()).onCallParticipantsChanged(expectedJoined, expectedUpdated,
expectedLeft, expectedUnchanged);
}
@Test
public void testUsersInRoomLeaveCallSeveralParticipants() {
List<Participant> participants = new ArrayList<>();
participants.add(builder.newUser(IN_CALL | WITH_AUDIO, 1, "theSessionId1", "theUserId1"));
participants.add(builder.newGuest(IN_CALL, 2, "theSessionId2"));
participants.add(builder.newUser(DISCONNECTED, 3, "theSessionId3", "theUserId3"));
participants.add(builder.newUser(IN_CALL, 4, "theSessionId4", "theUserId4"));
participantListMessageListener.onUsersInRoom(participants);
callParticipantList.addObserver(mockedCallParticipantListObserver);
participants = new ArrayList<>();
participants.add(builder.newUser(DISCONNECTED, 1, "theSessionId1", "theUserId1"));
participants.add(builder.newGuest(DISCONNECTED, 2, "theSessionId2"));
participants.add(builder.newUser(DISCONNECTED, 3, "theSessionId3", "theUserId3"));
participants.add(builder.newUser(IN_CALL, 4, "theSessionId4", "theUserId4"));
participantListMessageListener.onUsersInRoom(participants);
expectedLeft.add(builder.newUser(DISCONNECTED, 1, "theSessionId1", "theUserId1"));
expectedLeft.add(builder.newGuest(DISCONNECTED, 2, "theSessionId2"));
expectedUnchanged.add(builder.newUser(IN_CALL, 4, "theSessionId4", "theUserId4"));
verify(mockedCallParticipantListObserver, only()).onCallParticipantsChanged(expectedJoined, expectedUpdated,
expectedLeft, expectedUnchanged);
}
@Test
public void testUsersInRoomLeaveCallThenLeaveRoom() {
List<Participant> participants = new ArrayList<>();
participants.add(builder.newUser(IN_CALL | WITH_AUDIO, 1, "theSessionId1", "theUserId1"));
participantListMessageListener.onUsersInRoom(participants);
participants = new ArrayList<>();
participants.add(builder.newUser(DISCONNECTED, 1, "theSessionId1", "theUserId1"));
participantListMessageListener.onUsersInRoom(participants);
callParticipantList.addObserver(mockedCallParticipantListObserver);
participants = new ArrayList<>();
participantListMessageListener.onUsersInRoom(participants);
verifyNoInteractions(mockedCallParticipantListObserver);
}
@Test
public void testUsersInRoomLeaveCallThenLeaveRoomSeveralParticipants() {
List<Participant> participants = new ArrayList<>();
participants.add(builder.newUser(IN_CALL | WITH_AUDIO, 1, "theSessionId1", "theUserId1"));
participants.add(builder.newGuest(IN_CALL, 2, "theSessionId2"));
participants.add(builder.newUser(DISCONNECTED, 3, "theSessionId3", "theUserId3"));
participants.add(builder.newUser(IN_CALL, 4, "theSessionId4", "theUserId4"));
participantListMessageListener.onUsersInRoom(participants);
participants = new ArrayList<>();
participants.add(builder.newUser(DISCONNECTED, 1, "theSessionId1", "theUserId1"));
participants.add(builder.newGuest(DISCONNECTED, 2, "theSessionId2"));
participants.add(builder.newUser(DISCONNECTED, 3, "theSessionId3", "theUserId3"));
participants.add(builder.newUser(IN_CALL, 4, "theSessionId4", "theUserId4"));
participantListMessageListener.onUsersInRoom(participants);
callParticipantList.addObserver(mockedCallParticipantListObserver);
participants = new ArrayList<>();
participants.add(builder.newUser(DISCONNECTED, 3, "theSessionId3", "theUserId3"));
participants.add(builder.newUser(IN_CALL, 4, "theSessionId4", "theUserId4"));
participantListMessageListener.onUsersInRoom(participants);
verifyNoInteractions(mockedCallParticipantListObserver);
}
@Test
public void testUsersInRoomLeaveCallAndRoom() {
List<Participant> participants = new ArrayList<>();
participants.add(builder.newUser(IN_CALL | WITH_AUDIO, 1, "theSessionId1", "theUserId1"));
participantListMessageListener.onUsersInRoom(participants);
callParticipantList.addObserver(mockedCallParticipantListObserver);
participants = new ArrayList<>();
participantListMessageListener.onUsersInRoom(participants);
expectedLeft.add(builder.newUser(DISCONNECTED, 1, "theSessionId1", "theUserId1"));
verify(mockedCallParticipantListObserver, only()).onCallParticipantsChanged(expectedJoined, expectedUpdated,
expectedLeft, expectedUnchanged);
}
@Test
public void testUsersInRoomLeaveCallAndRoomSeveralParticipants() {
List<Participant> participants = new ArrayList<>();
participants.add(builder.newUser(IN_CALL | WITH_AUDIO, 1, "theSessionId1", "theUserId1"));
participants.add(builder.newGuest(IN_CALL, 2, "theSessionId2"));
participants.add(builder.newUser(DISCONNECTED, 3, "theSessionId3", "theUserId3"));
participants.add(builder.newUser(IN_CALL, 4, "theSessionId4", "theUserId4"));
participantListMessageListener.onUsersInRoom(participants);
callParticipantList.addObserver(mockedCallParticipantListObserver);
participants = new ArrayList<>();
participants.add(builder.newUser(DISCONNECTED, 3, "theSessionId3", "theUserId3"));
participants.add(builder.newUser(IN_CALL, 4, "theSessionId4", "theUserId4"));
participantListMessageListener.onUsersInRoom(participants);
expectedLeft.add(builder.newUser(DISCONNECTED, 1, "theSessionId1", "theUserId1"));
expectedLeft.add(builder.newGuest(DISCONNECTED, 2, "theSessionId2"));
expectedUnchanged.add(builder.newUser(IN_CALL, 4, "theSessionId4", "theUserId4"));
verify(mockedCallParticipantListObserver).onCallParticipantsChanged(eq(expectedJoined), eq(expectedUpdated),
argThat(matchesExpectedLeftIgnoringOrder), eq(expectedUnchanged));
}
@Test
public void testUsersInRoomSeveralEventsSeveralParticipants() {
List<Participant> participants = new ArrayList<>();
participants.add(builder.newUser(IN_CALL | WITH_AUDIO, 1, "theSessionId1", "theUserId1"));
participants.add(builder.newGuest(IN_CALL, 2, "theSessionId2"));
participants.add(builder.newUser(DISCONNECTED, 3, "theSessionId3", "theUserId3"));
participants.add(builder.newUser(IN_CALL, 4, "theSessionId4", "theUserId4"));
participants.add(builder.newUser(IN_CALL, 5, "theSessionId5", "theUserId5"));
// theSessionId6 has not joined yet.
participants.add(builder.newGuest(IN_CALL | WITH_VIDEO, 7, "theSessionId7"));
participants.add(builder.newUser(DISCONNECTED, 8, "theSessionId8", "theUserId8"));
participants.add(builder.newUser(IN_CALL | WITH_AUDIO, 9, "theSessionId9", "theUserId9"));
participantListMessageListener.onUsersInRoom(participants);
callParticipantList.addObserver(mockedCallParticipantListObserver);
participants = new ArrayList<>();
// theSessionId1 is gone.
participants.add(builder.newGuest(DISCONNECTED, 2, "theSessionId2"));
participants.add(builder.newUser(DISCONNECTED, 3, "theSessionId3", "theUserId3"));
participants.add(builder.newUser(IN_CALL, 4, "theSessionId4", "theUserId4"));
participants.add(builder.newUser(IN_CALL | WITH_AUDIO | WITH_VIDEO, 5, "theSessionId5", "theUserId5"));
participants.add(builder.newGuest(IN_CALL | WITH_AUDIO, 6, "theSessionId6"));
participants.add(builder.newGuest(IN_CALL, 7, "theSessionId7"));
participants.add(builder.newUser(IN_CALL, 8, "theSessionId8", "theUserId8"));
participants.add(builder.newUser(IN_CALL | WITH_AUDIO, 42, "theSessionId9", "theUserId9"));
participantListMessageListener.onUsersInRoom(participants);
expectedJoined.add(builder.newGuest(IN_CALL | WITH_AUDIO, 6, "theSessionId6"));
expectedJoined.add(builder.newUser(IN_CALL, 8, "theSessionId8", "theUserId8"));
expectedUpdated.add(builder.newUser(IN_CALL | WITH_AUDIO | WITH_VIDEO, 5, "theSessionId5", "theUserId5"));
expectedUpdated.add(builder.newGuest(IN_CALL, 7, "theSessionId7"));
expectedLeft.add(builder.newUser(DISCONNECTED, 1, "theSessionId1", "theUserId1"));
expectedLeft.add(builder.newGuest(DISCONNECTED, 2, "theSessionId2"));
expectedUnchanged.add(builder.newUser(IN_CALL, 4, "theSessionId4", "theUserId4"));
// Last ping is not seen as changed, even if it did.
expectedUnchanged.add(builder.newUser(IN_CALL | WITH_AUDIO, 42, "theSessionId9", "theUserId9"));
verify(mockedCallParticipantListObserver).onCallParticipantsChanged(eq(expectedJoined), eq(expectedUpdated),
argThat(matchesExpectedLeftIgnoringOrder), eq(expectedUnchanged));
}
}

View file

@ -0,0 +1,61 @@
/*
* Nextcloud Talk application
*
* @author Daniel Calviño Sánchez
* Copyright (C) 2022 Daniel Calviño Sánchez <danxuliu@gmail.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.nextcloud.talk.call;
import com.nextcloud.talk.signaling.SignalingMessageReceiver;
import org.junit.Before;
import org.junit.Test;
import org.mockito.ArgumentCaptor;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoMoreInteractions;
public class CallParticipantListTest {
private SignalingMessageReceiver mockedSignalingMessageReceiver;
private CallParticipantList callParticipantList;
private SignalingMessageReceiver.ParticipantListMessageListener participantListMessageListener;
@Before
public void setUp() {
mockedSignalingMessageReceiver = mock(SignalingMessageReceiver.class);
callParticipantList = new CallParticipantList(mockedSignalingMessageReceiver);
// Get internal ParticipantListMessageListener from callParticipantList set in the
// mockedSignalingMessageReceiver.
ArgumentCaptor<SignalingMessageReceiver.ParticipantListMessageListener> participantListMessageListenerArgumentCaptor =
ArgumentCaptor.forClass(SignalingMessageReceiver.ParticipantListMessageListener.class);
verify(mockedSignalingMessageReceiver).addListener(participantListMessageListenerArgumentCaptor.capture());
participantListMessageListener = participantListMessageListenerArgumentCaptor.getValue();
}
@Test
public void testDestroy() {
callParticipantList.destroy();
verify(mockedSignalingMessageReceiver).removeListener(participantListMessageListener);
verifyNoMoreInteractions(mockedSignalingMessageReceiver);
}
}