diff --git a/app/src/main/java/com/nextcloud/talk/activities/CallActivity.kt b/app/src/main/java/com/nextcloud/talk/activities/CallActivity.kt
index e8472e973..7a4fecb25 100644
--- a/app/src/main/java/com/nextcloud/talk/activities/CallActivity.kt
+++ b/app/src/main/java/com/nextcloud/talk/activities/CallActivity.kt
@@ -1590,6 +1590,8 @@ class CallActivity : CallBaseActivity() {
hasMCU = false
messageSender = MessageSenderNoMcu(
+ signalingMessageSender,
+ callParticipants.keys,
peerConnectionWrapperList
)
@@ -1895,11 +1897,15 @@ class CallActivity : CallBaseActivity() {
if (hasMCU) {
messageSender = MessageSenderMcu(
+ signalingMessageSender,
+ callParticipants.keys,
peerConnectionWrapperList,
webSocketClient!!.sessionId
)
} else {
messageSender = MessageSenderNoMcu(
+ signalingMessageSender,
+ callParticipants.keys,
peerConnectionWrapperList
)
}
@@ -1934,11 +1940,15 @@ class CallActivity : CallBaseActivity() {
if (hasMCU) {
messageSender = MessageSenderMcu(
+ signalingMessageSender,
+ callParticipants.keys,
peerConnectionWrapperList,
webSocketClient!!.sessionId
)
} else {
messageSender = MessageSenderNoMcu(
+ signalingMessageSender,
+ callParticipants.keys,
peerConnectionWrapperList
)
}
diff --git a/app/src/main/java/com/nextcloud/talk/call/MessageSender.java b/app/src/main/java/com/nextcloud/talk/call/MessageSender.java
index a868fc6b8..dd3eb149a 100644
--- a/app/src/main/java/com/nextcloud/talk/call/MessageSender.java
+++ b/app/src/main/java/com/nextcloud/talk/call/MessageSender.java
@@ -7,16 +7,22 @@
package com.nextcloud.talk.call;
import com.nextcloud.talk.models.json.signaling.DataChannelMessage;
+import com.nextcloud.talk.models.json.signaling.NCSignalingMessage;
+import com.nextcloud.talk.signaling.SignalingMessageSender;
import com.nextcloud.talk.webrtc.PeerConnectionWrapper;
import java.util.List;
+import java.util.Set;
/**
* Helper class to send messages to participants in a call.
*
* A specific subclass has to be created depending on whether an MCU is being used or not.
*
- * Note that, unlike signaling messages, data channel messages require a peer connection. Therefore data channel
+ * Note that recipients of signaling messages are not validated, so no error will be triggered if trying to send a
+ * message to a participant with a session ID that does not exist or is not in the call.
+ *
+ * Also note that, unlike signaling messages, data channel messages require a peer connection. Therefore data channel
* messages may not be received by a participant if there is no peer connection with that participant (for example, if
* neither the local and remote participants have publishing rights). Moreover, data channel messages are expected to
* be received only on peer connections with type "video", so data channel messages will not be sent on other peer
@@ -24,9 +30,17 @@ import java.util.List;
*/
public abstract class MessageSender {
+ private final SignalingMessageSender signalingMessageSender;
+
+ private final Set callParticipantSessionIds;
+
protected final List peerConnectionWrappers;
- public MessageSender(List peerConnectionWrappers) {
+ public MessageSender(SignalingMessageSender signalingMessageSender,
+ Set callParticipantSessionIds,
+ List peerConnectionWrappers) {
+ this.signalingMessageSender = signalingMessageSender;
+ this.callParticipantSessionIds = callParticipantSessionIds;
this.peerConnectionWrappers = peerConnectionWrappers;
}
@@ -37,6 +51,35 @@ public abstract class MessageSender {
*/
public abstract void sendToAll(DataChannelMessage dataChannelMessage);
+ /**
+ * Sends the given signaling message to the given session ID.
+ *
+ * Note that the signaling message will be modified to set the recipient in the "to" field.
+ *
+ * @param ncSignalingMessage the message to send
+ * @param sessionId the signaling session ID of the participant to send the message to
+ */
+ public void send(NCSignalingMessage ncSignalingMessage, String sessionId) {
+ ncSignalingMessage.setTo(sessionId);
+
+ signalingMessageSender.send(ncSignalingMessage);
+ }
+
+ /**
+ * Sends the given signaling message to all the participants in the call.
+ *
+ * Note that the signaling message will be modified to set each of the recipients in the "to" field.
+ *
+ * @param ncSignalingMessage the message to send
+ */
+ public void sendToAll(NCSignalingMessage ncSignalingMessage) {
+ for (String sessionId: callParticipantSessionIds) {
+ ncSignalingMessage.setTo(sessionId);
+
+ signalingMessageSender.send(ncSignalingMessage);
+ }
+ }
+
protected PeerConnectionWrapper getPeerConnectionWrapper(String sessionId) {
for (PeerConnectionWrapper peerConnectionWrapper: peerConnectionWrappers) {
if (peerConnectionWrapper.getSessionId().equals(sessionId)
diff --git a/app/src/main/java/com/nextcloud/talk/call/MessageSenderMcu.java b/app/src/main/java/com/nextcloud/talk/call/MessageSenderMcu.java
index f3278fe4c..0b7d3eaee 100644
--- a/app/src/main/java/com/nextcloud/talk/call/MessageSenderMcu.java
+++ b/app/src/main/java/com/nextcloud/talk/call/MessageSenderMcu.java
@@ -7,9 +7,11 @@
package com.nextcloud.talk.call;
import com.nextcloud.talk.models.json.signaling.DataChannelMessage;
+import com.nextcloud.talk.signaling.SignalingMessageSender;
import com.nextcloud.talk.webrtc.PeerConnectionWrapper;
import java.util.List;
+import java.util.Set;
/**
* Helper class to send messages to participants in a call when an MCU is used.
@@ -21,9 +23,11 @@ public class MessageSenderMcu extends MessageSender {
private final String ownSessionId;
- public MessageSenderMcu(List peerConnectionWrappers,
+ public MessageSenderMcu(SignalingMessageSender signalingMessageSender,
+ Set callParticipantSessionIds,
+ List peerConnectionWrappers,
String ownSessionId) {
- super(peerConnectionWrappers);
+ super(signalingMessageSender, callParticipantSessionIds, peerConnectionWrappers);
this.ownSessionId = ownSessionId;
}
diff --git a/app/src/main/java/com/nextcloud/talk/call/MessageSenderNoMcu.java b/app/src/main/java/com/nextcloud/talk/call/MessageSenderNoMcu.java
index b5b903503..d6c837bb7 100644
--- a/app/src/main/java/com/nextcloud/talk/call/MessageSenderNoMcu.java
+++ b/app/src/main/java/com/nextcloud/talk/call/MessageSenderNoMcu.java
@@ -7,17 +7,21 @@
package com.nextcloud.talk.call;
import com.nextcloud.talk.models.json.signaling.DataChannelMessage;
+import com.nextcloud.talk.signaling.SignalingMessageSender;
import com.nextcloud.talk.webrtc.PeerConnectionWrapper;
import java.util.List;
+import java.util.Set;
/**
* Helper class to send messages to participants in a call when an MCU is not used.
*/
public class MessageSenderNoMcu extends MessageSender {
- public MessageSenderNoMcu(List peerConnectionWrappers) {
- super(peerConnectionWrappers);
+ public MessageSenderNoMcu(SignalingMessageSender signalingMessageSender,
+ Set callParticipantSessionIds,
+ List peerConnectionWrappers) {
+ super(signalingMessageSender, callParticipantSessionIds, peerConnectionWrappers);
}
/**
diff --git a/app/src/test/java/com/nextcloud/talk/call/MessageSenderMcuTest.kt b/app/src/test/java/com/nextcloud/talk/call/MessageSenderMcuTest.kt
index 49ca9e864..9fd8d6289 100644
--- a/app/src/test/java/com/nextcloud/talk/call/MessageSenderMcuTest.kt
+++ b/app/src/test/java/com/nextcloud/talk/call/MessageSenderMcuTest.kt
@@ -7,6 +7,7 @@
package com.nextcloud.talk.call
import com.nextcloud.talk.models.json.signaling.DataChannelMessage
+import com.nextcloud.talk.signaling.SignalingMessageSender
import com.nextcloud.talk.webrtc.PeerConnectionWrapper
import org.junit.Before
import org.junit.Test
@@ -27,6 +28,10 @@ class MessageSenderMcuTest {
@Before
fun setUp() {
+ val signalingMessageSender = Mockito.mock(SignalingMessageSender::class.java)
+
+ val callParticipants = HashMap()
+
peerConnectionWrappers = ArrayList()
peerConnectionWrapper1 = Mockito.mock(PeerConnectionWrapper::class.java)
@@ -59,7 +64,12 @@ class MessageSenderMcuTest {
Mockito.`when`(ownPeerConnectionWrapperScreen!!.videoStreamType).thenReturn("screen")
peerConnectionWrappers!!.add(ownPeerConnectionWrapperScreen)
- messageSender = MessageSenderMcu(peerConnectionWrappers, "ownSessionId")
+ messageSender = MessageSenderMcu(
+ signalingMessageSender,
+ callParticipants.keys,
+ peerConnectionWrappers,
+ "ownSessionId"
+ )
}
@Test
diff --git a/app/src/test/java/com/nextcloud/talk/call/MessageSenderNoMcuTest.kt b/app/src/test/java/com/nextcloud/talk/call/MessageSenderNoMcuTest.kt
index 6784bcef3..303108ed9 100644
--- a/app/src/test/java/com/nextcloud/talk/call/MessageSenderNoMcuTest.kt
+++ b/app/src/test/java/com/nextcloud/talk/call/MessageSenderNoMcuTest.kt
@@ -7,6 +7,7 @@
package com.nextcloud.talk.call
import com.nextcloud.talk.models.json.signaling.DataChannelMessage
+import com.nextcloud.talk.signaling.SignalingMessageSender
import com.nextcloud.talk.webrtc.PeerConnectionWrapper
import org.junit.Before
import org.junit.Test
@@ -25,6 +26,10 @@ class MessageSenderNoMcuTest {
@Before
fun setUp() {
+ val signalingMessageSender = Mockito.mock(SignalingMessageSender::class.java)
+
+ val callParticipants = HashMap()
+
peerConnectionWrappers = ArrayList()
peerConnectionWrapper1 = Mockito.mock(PeerConnectionWrapper::class.java)
@@ -47,7 +52,7 @@ class MessageSenderNoMcuTest {
Mockito.`when`(peerConnectionWrapper4Screen!!.videoStreamType).thenReturn("screen")
peerConnectionWrappers!!.add(peerConnectionWrapper4Screen)
- messageSender = MessageSenderNoMcu(peerConnectionWrappers)
+ messageSender = MessageSenderNoMcu(signalingMessageSender, callParticipants.keys, peerConnectionWrappers)
}
@Test
diff --git a/app/src/test/java/com/nextcloud/talk/call/MessageSenderTest.kt b/app/src/test/java/com/nextcloud/talk/call/MessageSenderTest.kt
new file mode 100644
index 000000000..46915ef40
--- /dev/null
+++ b/app/src/test/java/com/nextcloud/talk/call/MessageSenderTest.kt
@@ -0,0 +1,134 @@
+/*
+ * Nextcloud Talk - Android Client
+ *
+ * SPDX-FileCopyrightText: 2024 Daniel Calviño Sánchez
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+package com.nextcloud.talk.call
+
+import com.nextcloud.talk.models.json.signaling.DataChannelMessage
+import com.nextcloud.talk.models.json.signaling.NCSignalingMessage
+import com.nextcloud.talk.signaling.SignalingMessageSender
+import com.nextcloud.talk.webrtc.PeerConnectionWrapper
+import org.junit.Assert.assertTrue
+import org.junit.Before
+import org.junit.Test
+import org.mockito.Mockito
+import org.mockito.Mockito.any
+import org.mockito.Mockito.doAnswer
+import org.mockito.Mockito.times
+import org.mockito.invocation.InvocationOnMock
+
+class MessageSenderTest {
+
+ private class MessageSender(
+ signalingMessageSender: SignalingMessageSender?,
+ callParticipantSessionIds: Set?,
+ peerConnectionWrappers: List?
+ ) : com.nextcloud.talk.call.MessageSender(
+ signalingMessageSender,
+ callParticipantSessionIds,
+ peerConnectionWrappers
+ ) {
+
+ override fun sendToAll(dataChannelMessage: DataChannelMessage?) {
+ // Not used in base class tests
+ }
+ }
+
+ private var signalingMessageSender: SignalingMessageSender? = null
+
+ private var callParticipants: MutableMap? = null
+
+ private var messageSender: MessageSender? = null
+
+ @Before
+ fun setUp() {
+ signalingMessageSender = Mockito.mock(SignalingMessageSender::class.java)
+
+ callParticipants = HashMap()
+
+ val callParticipant1: CallParticipant = Mockito.mock(CallParticipant::class.java)
+ callParticipants!!["theSessionId1"] = callParticipant1
+
+ val callParticipant2: CallParticipant = Mockito.mock(CallParticipant::class.java)
+ callParticipants!!["theSessionId2"] = callParticipant2
+
+ val callParticipant3: CallParticipant = Mockito.mock(CallParticipant::class.java)
+ callParticipants!!["theSessionId3"] = callParticipant3
+
+ val callParticipant4: CallParticipant = Mockito.mock(CallParticipant::class.java)
+ callParticipants!!["theSessionId4"] = callParticipant4
+
+ val peerConnectionWrappers = ArrayList()
+
+ messageSender = MessageSender(signalingMessageSender, callParticipants!!.keys, peerConnectionWrappers)
+ }
+
+ @Test
+ fun testSendSignalingMessage() {
+ val message: NCSignalingMessage = Mockito.mock(NCSignalingMessage::class.java)
+ messageSender!!.send(message, "theSessionId2")
+
+ Mockito.verify(message).to = "theSessionId2"
+ Mockito.verify(signalingMessageSender!!).send(message)
+ }
+
+ @Test
+ fun testSendSignalingMessageIfUnknownSessionId() {
+ val message: NCSignalingMessage = Mockito.mock(NCSignalingMessage::class.java)
+ messageSender!!.send(message, "unknownSessionId")
+
+ Mockito.verify(message).to = "unknownSessionId"
+ Mockito.verify(signalingMessageSender!!).send(message)
+ }
+
+ @Test
+ fun testSendSignalingMessageToAll() {
+ val sentTo: MutableList = ArrayList()
+ doAnswer { invocation: InvocationOnMock ->
+ val arguments = invocation.arguments
+ val message = (arguments[0] as NCSignalingMessage)
+
+ sentTo.add(message.to)
+ null
+ }.`when`(signalingMessageSender!!).send(any())
+
+ val message = NCSignalingMessage()
+ messageSender!!.sendToAll(message)
+
+ assertTrue(sentTo.contains("theSessionId1"))
+ assertTrue(sentTo.contains("theSessionId2"))
+ assertTrue(sentTo.contains("theSessionId3"))
+ assertTrue(sentTo.contains("theSessionId4"))
+ Mockito.verify(signalingMessageSender!!, times(4)).send(message)
+ Mockito.verifyNoMoreInteractions(signalingMessageSender)
+ }
+
+ @Test
+ fun testSendSignalingMessageToAllWhenParticipantsWereUpdated() {
+ val callParticipant5: CallParticipant = Mockito.mock(CallParticipant::class.java)
+ callParticipants!!["theSessionId5"] = callParticipant5
+
+ callParticipants!!.remove("theSessionId2")
+ callParticipants!!.remove("theSessionId3")
+
+ val sentTo: MutableList = ArrayList()
+ doAnswer { invocation: InvocationOnMock ->
+ val arguments = invocation.arguments
+ val message = (arguments[0] as NCSignalingMessage)
+
+ sentTo.add(message.to)
+ null
+ }.`when`(signalingMessageSender!!).send(any())
+
+ val message = NCSignalingMessage()
+ messageSender!!.sendToAll(message)
+
+ assertTrue(sentTo.contains("theSessionId1"))
+ assertTrue(sentTo.contains("theSessionId4"))
+ assertTrue(sentTo.contains("theSessionId5"))
+ Mockito.verify(signalingMessageSender!!, times(3)).send(message)
+ Mockito.verifyNoMoreInteractions(signalingMessageSender)
+ }
+}