Add listener for participant list messages

For now only the same participant list messages that were already
handled are taken into account, but at a later point further messages,
like participants joining or leaving the conversation, could be added
too.

Signed-off-by: Daniel Calviño Sánchez <danxuliu@gmail.com>
This commit is contained in:
Daniel Calviño Sánchez 2022-10-21 19:28:11 +02:00
parent c8e77c3d3b
commit e0c676bb35
4 changed files with 802 additions and 1 deletions

View file

@ -0,0 +1,68 @@
/*
* 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.signaling;
import com.nextcloud.talk.models.json.participants.Participant;
import java.util.ArrayList;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
/**
* Helper class to register and notify ParticipantListMessageListeners.
*
* This class is only meant for internal use by SignalingMessageReceiver; listeners must register themselves against
* a SignalingMessageReceiver rather than against a ParticipantListMessageNotifier.
*/
class ParticipantListMessageNotifier {
private final Set<SignalingMessageReceiver.ParticipantListMessageListener> participantListMessageListeners = new LinkedHashSet<>();
public synchronized void addListener(SignalingMessageReceiver.ParticipantListMessageListener listener) {
if (listener == null) {
throw new IllegalArgumentException("participantListMessageListeners can not be null");
}
participantListMessageListeners.add(listener);
}
public synchronized void removeListener(SignalingMessageReceiver.ParticipantListMessageListener listener) {
participantListMessageListeners.remove(listener);
}
public synchronized void notifyUsersInRoom(List<Participant> participants) {
for (SignalingMessageReceiver.ParticipantListMessageListener listener : new ArrayList<>(participantListMessageListeners)) {
listener.onUsersInRoom(participants);
}
}
public synchronized void notifyParticipantsUpdate(List<Participant> participants) {
for (SignalingMessageReceiver.ParticipantListMessageListener listener : new ArrayList<>(participantListMessageListeners)) {
listener.onParticipantsUpdate(participants);
}
}
public synchronized void notifyAllParticipantsUpdate(long inCall) {
for (SignalingMessageReceiver.ParticipantListMessageListener listener : new ArrayList<>(participantListMessageListeners)) {
listener.onAllParticipantsUpdate(inCall);
}
}
}

View file

@ -19,10 +19,16 @@
*/
package com.nextcloud.talk.signaling;
import com.nextcloud.talk.models.json.converters.EnumParticipantTypeConverter;
import com.nextcloud.talk.models.json.participants.Participant;
import com.nextcloud.talk.models.json.signaling.NCIceCandidate;
import com.nextcloud.talk.models.json.signaling.NCMessagePayload;
import com.nextcloud.talk.models.json.signaling.NCSignalingMessage;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
/**
* Hub to register listeners for signaling messages of different kinds.
*
@ -44,6 +50,74 @@ import com.nextcloud.talk.models.json.signaling.NCSignalingMessage;
*/
public abstract class SignalingMessageReceiver {
/**
* Listener for participant list messages.
*
* The messages are implicitly bound to the room currently joined in the signaling server; listeners are expected
* to know the current room.
*/
public interface ParticipantListMessageListener {
/**
* List of all the participants in the room.
*
* This message is received only when the internal signaling server is used.
*
* The message is received periodically, and the participants may not have been modified since the last message.
*
* Only the following participant properties are set:
* - inCall
* - lastPing
* - sessionId
* - userId (if the participant is not a guest)
*
* "participantPermissions" is provided in the message (since Talk 13), but not currently set in the
* participant. "publishingPermissions" was provided instead in Talk 12, but it was not used anywhere, so it is
* ignored.
*
* @param participants all the participants (users and guests) in the room
*/
void onUsersInRoom(List<Participant> participants);
/**
* List of all the participants in the call or the room (depending on what triggered the event).
*
* This message is received only when the external signaling server is used.
*
* The message is received when any participant changed, although what changed is not provided and should be
* derived from the difference with previous messages. The list of participants may include only the
* participants in the call (including those that just left it and thus triggered the event) or all the
* participants currently in the room (participants in the room but not currently active, that is, without a
* session, are not included).
*
* Only the following participant properties are set:
* - inCall
* - lastPing
* - sessionId
* - type
* - userId (if the participant is not a guest)
*
* "nextcloudSessionId" is provided in the message (when the "inCall" property of any participant changed), but
* not currently set in the participant.
*
* "participantPermissions" is provided in the message (since Talk 13), but not currently set in the
* participant. "publishingPermissions" was provided instead in Talk 12, but it was not used anywhere, so it is
* ignored.
*
* @param participants all the participants (users and guests) in the room
*/
void onParticipantsUpdate(List<Participant> participants);
/**
* Update of the properties of all the participants in the room.
*
* This message is received only when the external signaling server is used.
*
* @param inCall the new value of the inCall property
*/
void onAllParticipantsUpdate(long inCall);
}
/**
* Listener for call participant messages.
*
@ -83,12 +157,29 @@ public abstract class SignalingMessageReceiver {
void onEndOfCandidates();
}
private final ParticipantListMessageNotifier participantListMessageNotifier = new ParticipantListMessageNotifier();
private final CallParticipantMessageNotifier callParticipantMessageNotifier = new CallParticipantMessageNotifier();
private final OfferMessageNotifier offerMessageNotifier = new OfferMessageNotifier();
private final WebRtcMessageNotifier webRtcMessageNotifier = new WebRtcMessageNotifier();
/**
* Adds a listener for participant list messages.
*
* A listener is expected to be added only once. If the same listener is added again it will be notified just once.
*
* @param listener the ParticipantListMessageListener
*/
public void addListener(ParticipantListMessageListener listener) {
participantListMessageNotifier.addListener(listener);
}
public void removeListener(ParticipantListMessageListener listener) {
participantListMessageNotifier.removeListener(listener);
}
/**
* Adds a listener for call participant messages.
*
@ -139,6 +230,182 @@ public abstract class SignalingMessageReceiver {
webRtcMessageNotifier.removeListener(listener);
}
protected void processEvent(Map<String, Object> eventMap) {
if (!"update".equals(eventMap.get("type")) || !"participants".equals(eventMap.get("target"))) {
return;
}
Map<String, Object> updateMap;
try {
updateMap = (Map<String, Object>) eventMap.get("update");
} catch (RuntimeException e) {
// Broken message, this should not happen.
return;
}
if (updateMap == null) {
// Broken message, this should not happen.
return;
}
if (updateMap.get("all") != null && Boolean.parseBoolean(updateMap.get("all").toString())) {
processAllParticipantsUpdate(updateMap);
return;
}
if (updateMap.get("users") != null) {
processParticipantsUpdate(updateMap);
return;
}
}
private void processAllParticipantsUpdate(Map<String, Object> updateMap) {
// Message schema:
// {
// "type": "event",
// "event": {
// "target": "participants",
// "type": "update",
// "update": {
// "roomid": #STRING#,
// "incall": 0,
// "all": true,
// },
// },
// }
long inCall;
try {
inCall = Long.parseLong(updateMap.get("inCall").toString());
} catch (RuntimeException e) {
// Broken message, this should not happen.
return;
}
participantListMessageNotifier.notifyAllParticipantsUpdate(inCall);
}
private void processParticipantsUpdate(Map<String, Object> updateMap) {
// Message schema:
// {
// "type": "event",
// "event": {
// "target": "participants",
// "type": "update",
// "update": {
// "roomid": #INTEGER#,
// "users": [
// {
// "inCall": #INTEGER#,
// "lastPing": #INTEGER#,
// "sessionId": #STRING#,
// "participantType": #INTEGER#,
// "userId": #STRING#, // Optional
// "nextcloudSessionId": #STRING#, // Optional
// "participantPermissions": #INTEGER#, // Talk >= 13
// },
// ...
// ],
// },
// },
// }
//
// Note that "userId" in participants->update comes from the Nextcloud server, so it is "userId"; in other
// messages, like room->join, it comes directly from the external signaling server, so it is "userid" instead.
List<Map<String, Object>> users;
try {
users = (List<Map<String, Object>>) updateMap.get("users");
} catch (RuntimeException e) {
// Broken message, this should not happen.
return;
}
if (users == null) {
// Broken message, this should not happen.
return;
}
List<Participant> participants = new ArrayList<>(users.size());
for (Map<String, Object> user: users) {
try {
participants.add(getParticipantFromMessageMap(user));
} catch (RuntimeException e) {
// Broken message, this should not happen.
return;
}
}
participantListMessageNotifier.notifyParticipantsUpdate(participants);
}
protected void processUsersInRoom(List<Map<String, Object>> users) {
// Message schema:
// {
// "type": "usersInRoom",
// "data": [
// {
// "inCall": #INTEGER#,
// "lastPing": #INTEGER#,
// "roomId": #INTEGER#,
// "sessionId": #STRING#,
// "userId": #STRING#, // Always included, although it can be empty
// "participantPermissions": #INTEGER#, // Talk >= 13
// },
// ...
// ],
// }
List<Participant> participants = new ArrayList<>(users.size());
for (Map<String, Object> user: users) {
try {
participants.add(getParticipantFromMessageMap(user));
} catch (RuntimeException e) {
// Broken message, this should not happen.
return;
}
}
participantListMessageNotifier.notifyUsersInRoom(participants);
}
/**
* Creates and initializes a Participant from the data in the given map.
*
* Maps from internal and external signaling server messages can be used. Nevertheless, besides the differences
* between the messages and the optional properties, it is expected that the message is correct and the given data
* is parseable. Broken messages (for example, a string instead of an integer for "inCall" or a missing
* "sessionId") may cause a RuntimeException to be thrown.
*
* @param participantMap the map with the participant data
* @return the Participant
*/
private Participant getParticipantFromMessageMap(Map<String, Object> participantMap) {
Participant participant = new Participant();
participant.setInCall(Long.parseLong(participantMap.get("inCall").toString()));
participant.setLastPing(Long.parseLong(participantMap.get("lastPing").toString()));
participant.setSessionId(participantMap.get("sessionId").toString());
if (participantMap.get("userId") != null && !participantMap.get("userId").toString().isEmpty()) {
participant.setUserId(participantMap.get("userId").toString());
}
// Only in external signaling messages
if (participantMap.get("participantType") != null) {
int participantTypeInt = Integer.parseInt(participantMap.get("participantType").toString());
EnumParticipantTypeConverter converter = new EnumParticipantTypeConverter();
participant.setType(converter.getFromInt(participantTypeInt));
}
return participant;
}
protected void processSignalingMessage(NCSignalingMessage signalingMessage) {
// Note that in the internal signaling server message "data" is the String representation of a JSON
// object, although it is already decoded when used here.

View file

@ -49,7 +49,7 @@ public class SignalingMessageReceiverOfferTest {
@Test
public void testAddOfferMessageListenerWithNullListener() {
Assert.assertThrows(IllegalArgumentException.class, () -> {
signalingMessageReceiver.addListener(null);
signalingMessageReceiver.addListener((SignalingMessageReceiver.OfferMessageListener) null);
});
}

View file

@ -0,0 +1,466 @@
/*
* 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.signaling;
import com.nextcloud.talk.models.json.participants.Participant;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.mockito.InOrder;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import static org.mockito.Mockito.doAnswer;
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;
public class SignalingMessageReceiverParticipantListTest {
private SignalingMessageReceiver signalingMessageReceiver;
@Before
public void setUp() {
// SignalingMessageReceiver is abstract to prevent direct instantiation without calling the appropriate
// protected methods.
signalingMessageReceiver = new SignalingMessageReceiver() {
};
}
@Test
public void testAddParticipantListMessageListenerWithNullListener() {
Assert.assertThrows(IllegalArgumentException.class, () -> {
signalingMessageReceiver.addListener((SignalingMessageReceiver.ParticipantListMessageListener) null);
});
}
@Test
public void testInternalSignalingParticipantListMessageUsersInRoom() {
SignalingMessageReceiver.ParticipantListMessageListener mockedParticipantListMessageListener =
mock(SignalingMessageReceiver.ParticipantListMessageListener.class);
signalingMessageReceiver.addListener(mockedParticipantListMessageListener);
List<Map<String, Object>> users = new ArrayList<>(2);
Map<String, Object> user1 = new HashMap<>();
user1.put("inCall", 7);
user1.put("lastPing", 4815);
user1.put("roomId", 108);
user1.put("sessionId", "theSessionId1");
user1.put("userId", "theUserId");
// If "participantPermissions" is set in any of the participants all the other participants in the message
// would have it too. But for test simplicity, and as it is not relevant for the processing, in this test it
// is included only in one of the participants.
user1.put("participantPermissions", 42);
users.add(user1);
Map<String, Object> user2 = new HashMap<>();
user2.put("inCall", 0);
user2.put("lastPing", 162342);
user2.put("roomId", 108);
user2.put("sessionId", "theSessionId2");
user2.put("userId", "");
users.add(user2);
signalingMessageReceiver.processUsersInRoom(users);
List<Participant> expectedParticipantList = new ArrayList<>();
Participant expectedParticipant1 = new Participant();
expectedParticipant1.setInCall(Participant.InCallFlags.IN_CALL | Participant.InCallFlags.WITH_AUDIO | Participant.InCallFlags.WITH_VIDEO);
expectedParticipant1.setLastPing(4815);
expectedParticipant1.setSessionId("theSessionId1");
expectedParticipant1.setUserId("theUserId");
expectedParticipantList.add(expectedParticipant1);
Participant expectedParticipant2 = new Participant();
expectedParticipant2.setInCall(Participant.InCallFlags.DISCONNECTED);
expectedParticipant2.setLastPing(162342);
expectedParticipant2.setSessionId("theSessionId2");
expectedParticipantList.add(expectedParticipant2);
verify(mockedParticipantListMessageListener, only()).onUsersInRoom(expectedParticipantList);
}
@Test
public void testInternalSignalingParticipantListMessageAfterRemovingListener() {
SignalingMessageReceiver.ParticipantListMessageListener mockedParticipantListMessageListener =
mock(SignalingMessageReceiver.ParticipantListMessageListener.class);
signalingMessageReceiver.addListener(mockedParticipantListMessageListener);
signalingMessageReceiver.removeListener(mockedParticipantListMessageListener);
List<Map<String, Object>> users = new ArrayList<>(1);
Map<String, Object> user = new HashMap<>();
user.put("inCall", 0);
user.put("lastPing", 4815);
user.put("roomId", 108);
user.put("sessionId", "theSessionId");
user.put("userId", "");
users.add(user);
signalingMessageReceiver.processUsersInRoom(users);
verifyNoInteractions(mockedParticipantListMessageListener);
}
@Test
public void testInternalSignalingParticipantListMessageAfterRemovingSingleListenerOfSeveral() {
SignalingMessageReceiver.ParticipantListMessageListener mockedParticipantListMessageListener1 =
mock(SignalingMessageReceiver.ParticipantListMessageListener.class);
SignalingMessageReceiver.ParticipantListMessageListener mockedParticipantListMessageListener2 =
mock(SignalingMessageReceiver.ParticipantListMessageListener.class);
SignalingMessageReceiver.ParticipantListMessageListener mockedParticipantListMessageListener3 =
mock(SignalingMessageReceiver.ParticipantListMessageListener.class);
signalingMessageReceiver.addListener(mockedParticipantListMessageListener1);
signalingMessageReceiver.addListener(mockedParticipantListMessageListener2);
signalingMessageReceiver.addListener(mockedParticipantListMessageListener3);
signalingMessageReceiver.removeListener(mockedParticipantListMessageListener2);
List<Map<String, Object>> users = new ArrayList<>(1);
Map<String, Object> user = new HashMap<>();
user.put("inCall", 0);
user.put("lastPing", 4815);
user.put("roomId", 108);
user.put("sessionId", "theSessionId");
user.put("userId", "");
users.add(user);
signalingMessageReceiver.processUsersInRoom(users);
List<Participant> expectedParticipantList = new ArrayList<>();
Participant expectedParticipant = new Participant();
expectedParticipant.setInCall(Participant.InCallFlags.DISCONNECTED);
expectedParticipant.setLastPing(4815);
expectedParticipant.setSessionId("theSessionId");
expectedParticipantList.add(expectedParticipant);
verify(mockedParticipantListMessageListener1, only()).onUsersInRoom(expectedParticipantList);
verify(mockedParticipantListMessageListener3, only()).onUsersInRoom(expectedParticipantList);
verifyNoInteractions(mockedParticipantListMessageListener2);
}
@Test
public void testInternalSignalingParticipantListMessageAfterAddingListenerAgain() {
SignalingMessageReceiver.ParticipantListMessageListener mockedParticipantListMessageListener =
mock(SignalingMessageReceiver.ParticipantListMessageListener.class);
signalingMessageReceiver.addListener(mockedParticipantListMessageListener);
signalingMessageReceiver.addListener(mockedParticipantListMessageListener);
List<Map<String, Object>> users = new ArrayList<>(1);
Map<String, Object> user = new HashMap<>();
user.put("inCall", 0);
user.put("lastPing", 4815);
user.put("roomId", 108);
user.put("sessionId", "theSessionId");
user.put("userId", "");
users.add(user);
signalingMessageReceiver.processUsersInRoom(users);
List<Participant> expectedParticipantList = new ArrayList<>();
Participant expectedParticipant = new Participant();
expectedParticipant.setInCall(Participant.InCallFlags.DISCONNECTED);
expectedParticipant.setLastPing(4815);
expectedParticipant.setSessionId("theSessionId");
expectedParticipantList.add(expectedParticipant);
verify(mockedParticipantListMessageListener, only()).onUsersInRoom(expectedParticipantList);
}
@Test
public void testAddParticipantListMessageListenerWhenHandlingInternalSignalingParticipantListMessage() {
SignalingMessageReceiver.ParticipantListMessageListener mockedParticipantListMessageListener1 =
mock(SignalingMessageReceiver.ParticipantListMessageListener.class);
SignalingMessageReceiver.ParticipantListMessageListener mockedParticipantListMessageListener2 =
mock(SignalingMessageReceiver.ParticipantListMessageListener.class);
List<Participant> expectedParticipantList = new ArrayList<>();
Participant expectedParticipant = new Participant();
expectedParticipant.setInCall(Participant.InCallFlags.DISCONNECTED);
expectedParticipant.setLastPing(4815);
expectedParticipant.setSessionId("theSessionId");
expectedParticipantList.add(expectedParticipant);
doAnswer((invocation) -> {
signalingMessageReceiver.addListener(mockedParticipantListMessageListener2);
return null;
}).when(mockedParticipantListMessageListener1).onUsersInRoom(expectedParticipantList);
signalingMessageReceiver.addListener(mockedParticipantListMessageListener1);
List<Map<String, Object>> users = new ArrayList<>(1);
Map<String, Object> user = new HashMap<>();
user.put("inCall", 0);
user.put("lastPing", 4815);
user.put("roomId", 108);
user.put("sessionId", "theSessionId");
user.put("userId", "");
users.add(user);
signalingMessageReceiver.processUsersInRoom(users);
verify(mockedParticipantListMessageListener1, only()).onUsersInRoom(expectedParticipantList);
verifyNoInteractions(mockedParticipantListMessageListener2);
}
@Test
public void testRemoveParticipantListMessageListenerWhenHandlingInternalSignalingParticipantListMessage() {
SignalingMessageReceiver.ParticipantListMessageListener mockedParticipantListMessageListener1 =
mock(SignalingMessageReceiver.ParticipantListMessageListener.class);
SignalingMessageReceiver.ParticipantListMessageListener mockedParticipantListMessageListener2 =
mock(SignalingMessageReceiver.ParticipantListMessageListener.class);
List<Participant> expectedParticipantList = new ArrayList<>();
Participant expectedParticipant = new Participant();
expectedParticipant.setInCall(Participant.InCallFlags.DISCONNECTED);
expectedParticipant.setLastPing(4815);
expectedParticipant.setSessionId("theSessionId");
expectedParticipantList.add(expectedParticipant);
doAnswer((invocation) -> {
signalingMessageReceiver.removeListener(mockedParticipantListMessageListener2);
return null;
}).when(mockedParticipantListMessageListener1).onUsersInRoom(expectedParticipantList);
signalingMessageReceiver.addListener(mockedParticipantListMessageListener1);
signalingMessageReceiver.addListener(mockedParticipantListMessageListener2);
List<Map<String, Object>> users = new ArrayList<>(1);
Map<String, Object> user = new HashMap<>();
user.put("inCall", 0);
user.put("lastPing", 4815);
user.put("roomId", 108);
user.put("sessionId", "theSessionId");
user.put("userId", "");
users.add(user);
signalingMessageReceiver.processUsersInRoom(users);
InOrder inOrder = inOrder(mockedParticipantListMessageListener1, mockedParticipantListMessageListener2);
inOrder.verify(mockedParticipantListMessageListener1).onUsersInRoom(expectedParticipantList);
inOrder.verify(mockedParticipantListMessageListener2).onUsersInRoom(expectedParticipantList);
}
@Test
public void testExternalSignalingParticipantListMessageParticipantsUpdate() {
SignalingMessageReceiver.ParticipantListMessageListener mockedParticipantListMessageListener =
mock(SignalingMessageReceiver.ParticipantListMessageListener.class);
signalingMessageReceiver.addListener(mockedParticipantListMessageListener);
Map<String, Object> eventMap = new HashMap<>();
eventMap.put("type", "update");
eventMap.put("target", "participants");
Map<String, Object> updateMap = new HashMap<>();
updateMap.put("roomId", 108);
List<Map<String, Object>> users = new ArrayList<>(2);
Map<String, Object> user1 = new HashMap<>();
user1.put("inCall", 7);
user1.put("lastPing", 4815);
user1.put("sessionId", "theSessionId1");
user1.put("participantType", 3);
user1.put("userId", "theUserId");
// If "nextcloudSessionId" or "participantPermissions" is set in any of the participants all the other
// participants in the message would have them too. But for test simplicity, and as it is not relevant for
// the processing, in this test they are included only in one of the participants.
user1.put("nextcloudSessionId", "theNextcloudSessionId");
user1.put("participantPermissions", 42);
users.add(user1);
Map<String, Object> user2 = new HashMap<>();
user2.put("inCall", 0);
user2.put("lastPing", 162342);
user2.put("sessionId", "theSessionId2");
user2.put("participantType", 4);
users.add(user2);
updateMap.put("users", users);
eventMap.put("update", updateMap);
signalingMessageReceiver.processEvent(eventMap);
List<Participant> expectedParticipantList = new ArrayList<>(2);
Participant expectedParticipant1 = new Participant();
expectedParticipant1.setInCall(Participant.InCallFlags.IN_CALL | Participant.InCallFlags.WITH_AUDIO | Participant.InCallFlags.WITH_VIDEO);
expectedParticipant1.setLastPing(4815);
expectedParticipant1.setSessionId("theSessionId1");
expectedParticipant1.setType(Participant.ParticipantType.USER);
expectedParticipant1.setUserId("theUserId");
expectedParticipantList.add(expectedParticipant1);
Participant expectedParticipant2 = new Participant();
expectedParticipant2.setInCall(Participant.InCallFlags.DISCONNECTED);
expectedParticipant2.setLastPing(162342);
expectedParticipant2.setSessionId("theSessionId2");
expectedParticipant2.setType(Participant.ParticipantType.GUEST);
expectedParticipantList.add(expectedParticipant2);
verify(mockedParticipantListMessageListener, only()).onParticipantsUpdate(expectedParticipantList);
}
@Test
public void testExternalSignalingParticipantListMessageAllParticipantsUpdate() {
SignalingMessageReceiver.ParticipantListMessageListener mockedParticipantListMessageListener =
mock(SignalingMessageReceiver.ParticipantListMessageListener.class);
signalingMessageReceiver.addListener(mockedParticipantListMessageListener);
Map<String, Object> eventMap = new HashMap<>();
eventMap.put("type", "update");
eventMap.put("target", "participants");
Map<String, Object> updateMap = new HashMap<>();
updateMap.put("roomId", 108);
updateMap.put("all", true);
updateMap.put("inCall", 0);
eventMap.put("update", updateMap);
signalingMessageReceiver.processEvent(eventMap);
verify(mockedParticipantListMessageListener, only()).onAllParticipantsUpdate(Participant.InCallFlags.DISCONNECTED);
}
@Test
public void testExternalSignalingParticipantListMessageAfterRemovingListener() {
SignalingMessageReceiver.ParticipantListMessageListener mockedParticipantListMessageListener =
mock(SignalingMessageReceiver.ParticipantListMessageListener.class);
signalingMessageReceiver.addListener(mockedParticipantListMessageListener);
signalingMessageReceiver.removeListener(mockedParticipantListMessageListener);
Map<String, Object> eventMap = new HashMap<>();
eventMap.put("type", "update");
eventMap.put("target", "participants");
HashMap<String, Object> updateMap = new HashMap<>();
updateMap.put("roomId", 108);
updateMap.put("all", true);
updateMap.put("inCall", 0);
eventMap.put("update", updateMap);
signalingMessageReceiver.processEvent(eventMap);
verifyNoInteractions(mockedParticipantListMessageListener);
}
@Test
public void testExternalSignalingParticipantListMessageAfterRemovingSingleListenerOfSeveral() {
SignalingMessageReceiver.ParticipantListMessageListener mockedParticipantListMessageListener1 =
mock(SignalingMessageReceiver.ParticipantListMessageListener.class);
SignalingMessageReceiver.ParticipantListMessageListener mockedParticipantListMessageListener2 =
mock(SignalingMessageReceiver.ParticipantListMessageListener.class);
SignalingMessageReceiver.ParticipantListMessageListener mockedParticipantListMessageListener3 =
mock(SignalingMessageReceiver.ParticipantListMessageListener.class);
signalingMessageReceiver.addListener(mockedParticipantListMessageListener1);
signalingMessageReceiver.addListener(mockedParticipantListMessageListener2);
signalingMessageReceiver.addListener(mockedParticipantListMessageListener3);
signalingMessageReceiver.removeListener(mockedParticipantListMessageListener2);
Map<String, Object> eventMap = new HashMap<>();
eventMap.put("type", "update");
eventMap.put("target", "participants");
HashMap<String, Object> updateMap = new HashMap<>();
updateMap.put("roomId", 108);
updateMap.put("all", true);
updateMap.put("inCall", 0);
eventMap.put("update", updateMap);
signalingMessageReceiver.processEvent(eventMap);
verify(mockedParticipantListMessageListener1, only()).onAllParticipantsUpdate(Participant.InCallFlags.DISCONNECTED);
verify(mockedParticipantListMessageListener3, only()).onAllParticipantsUpdate(Participant.InCallFlags.DISCONNECTED);
verifyNoInteractions(mockedParticipantListMessageListener2);
}
@Test
public void testExternalSignalingParticipantListMessageAfterAddingListenerAgain() {
SignalingMessageReceiver.ParticipantListMessageListener mockedParticipantListMessageListener =
mock(SignalingMessageReceiver.ParticipantListMessageListener.class);
signalingMessageReceiver.addListener(mockedParticipantListMessageListener);
signalingMessageReceiver.addListener(mockedParticipantListMessageListener);
Map<String, Object> eventMap = new HashMap<>();
eventMap.put("type", "update");
eventMap.put("target", "participants");
HashMap<String, Object> updateMap = new HashMap<>();
updateMap.put("roomId", 108);
updateMap.put("all", true);
updateMap.put("inCall", 0);
eventMap.put("update", updateMap);
signalingMessageReceiver.processEvent(eventMap);
verify(mockedParticipantListMessageListener, only()).onAllParticipantsUpdate(Participant.InCallFlags.DISCONNECTED);
}
@Test
public void testAddParticipantListMessageListenerWhenHandlingExternalSignalingParticipantListMessage() {
SignalingMessageReceiver.ParticipantListMessageListener mockedParticipantListMessageListener1 =
mock(SignalingMessageReceiver.ParticipantListMessageListener.class);
SignalingMessageReceiver.ParticipantListMessageListener mockedParticipantListMessageListener2 =
mock(SignalingMessageReceiver.ParticipantListMessageListener.class);
doAnswer((invocation) -> {
signalingMessageReceiver.addListener(mockedParticipantListMessageListener2);
return null;
}).when(mockedParticipantListMessageListener1).onAllParticipantsUpdate(Participant.InCallFlags.DISCONNECTED);
signalingMessageReceiver.addListener(mockedParticipantListMessageListener1);
Map<String, Object> eventMap = new HashMap<>();
eventMap.put("type", "update");
eventMap.put("target", "participants");
HashMap<String, Object> updateMap = new HashMap<>();
updateMap.put("roomId", 108);
updateMap.put("all", true);
updateMap.put("inCall", 0);
eventMap.put("update", updateMap);
signalingMessageReceiver.processEvent(eventMap);
verify(mockedParticipantListMessageListener1, only()).onAllParticipantsUpdate(Participant.InCallFlags.DISCONNECTED);
verifyNoInteractions(mockedParticipantListMessageListener2);
}
@Test
public void testRemoveParticipantListMessageListenerWhenHandlingExternalSignalingParticipantListMessage() {
SignalingMessageReceiver.ParticipantListMessageListener mockedParticipantListMessageListener1 =
mock(SignalingMessageReceiver.ParticipantListMessageListener.class);
SignalingMessageReceiver.ParticipantListMessageListener mockedParticipantListMessageListener2 =
mock(SignalingMessageReceiver.ParticipantListMessageListener.class);
doAnswer((invocation) -> {
signalingMessageReceiver.removeListener(mockedParticipantListMessageListener2);
return null;
}).when(mockedParticipantListMessageListener1).onAllParticipantsUpdate(Participant.InCallFlags.DISCONNECTED);
signalingMessageReceiver.addListener(mockedParticipantListMessageListener1);
signalingMessageReceiver.addListener(mockedParticipantListMessageListener2);
Map<String, Object> eventMap = new HashMap<>();
eventMap.put("type", "update");
eventMap.put("target", "participants");
HashMap<String, Object> updateMap = new HashMap<>();
updateMap.put("roomId", 108);
updateMap.put("all", true);
updateMap.put("inCall", 0);
eventMap.put("update", updateMap);
signalingMessageReceiver.processEvent(eventMap);
InOrder inOrder = inOrder(mockedParticipantListMessageListener1, mockedParticipantListMessageListener2);
inOrder.verify(mockedParticipantListMessageListener1).onAllParticipantsUpdate(Participant.InCallFlags.DISCONNECTED);
inOrder.verify(mockedParticipantListMessageListener2).onAllParticipantsUpdate(Participant.InCallFlags.DISCONNECTED);
}
}