From de443707105b8a2bab7c0e0b4335ee136db98a2e Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Daniel=20Calvi=C3=B1o=20S=C3=A1nchez?= <danxuliu@gmail.com>
Date: Thu, 26 Jan 2023 11:54:49 +0100
Subject: [PATCH] Add listener for "raiseHand" signaling message
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Daniel Calviño Sánchez <danxuliu@gmail.com>
---
 .../talk/activities/CallActivity.java         |  4 ++
 .../models/json/signaling/NCMessagePayload.kt |  8 ++-
 .../CallParticipantMessageNotifier.java       |  6 ++
 .../signaling/SignalingMessageReceiver.java   | 58 +++++++++++++++++++
 ...ingMessageReceiverCallParticipantTest.java | 22 +++++++
 5 files changed, 96 insertions(+), 2 deletions(-)

diff --git a/app/src/main/java/com/nextcloud/talk/activities/CallActivity.java b/app/src/main/java/com/nextcloud/talk/activities/CallActivity.java
index 642c5757e..b7759d4c9 100644
--- a/app/src/main/java/com/nextcloud/talk/activities/CallActivity.java
+++ b/app/src/main/java/com/nextcloud/talk/activities/CallActivity.java
@@ -2679,6 +2679,10 @@ public class CallActivity extends CallBaseActivity {
             this.sessionId = sessionId;
         }
 
+        @Override
+        public void onRaiseHand(boolean state, long timestamp) {
+        }
+
         @Override
         public void onUnshareScreen() {
             endPeerConnection(sessionId, "screen");
diff --git a/app/src/main/java/com/nextcloud/talk/models/json/signaling/NCMessagePayload.kt b/app/src/main/java/com/nextcloud/talk/models/json/signaling/NCMessagePayload.kt
index 6387649f3..ce078f11e 100644
--- a/app/src/main/java/com/nextcloud/talk/models/json/signaling/NCMessagePayload.kt
+++ b/app/src/main/java/com/nextcloud/talk/models/json/signaling/NCMessagePayload.kt
@@ -38,8 +38,12 @@ data class NCMessagePayload(
     @JsonField(name = ["candidate"])
     var iceCandidate: NCIceCandidate? = null,
     @JsonField(name = ["name"])
-    var name: String? = null
+    var name: String? = null,
+    @JsonField(name = ["state"])
+    var state: Boolean? = null,
+    @JsonField(name = ["timestamp"])
+    var timestamp: Long? = null
 ) : Parcelable {
     // This constructor is added to work with the 'com.bluelinelabs.logansquare.annotation.JsonObject'
-    constructor() : this(null, null, null, null, null)
+    constructor() : this(null, null, null, null, null, null, null)
 }
diff --git a/app/src/main/java/com/nextcloud/talk/signaling/CallParticipantMessageNotifier.java b/app/src/main/java/com/nextcloud/talk/signaling/CallParticipantMessageNotifier.java
index f06e72629..f1cf54e9b 100644
--- a/app/src/main/java/com/nextcloud/talk/signaling/CallParticipantMessageNotifier.java
+++ b/app/src/main/java/com/nextcloud/talk/signaling/CallParticipantMessageNotifier.java
@@ -87,6 +87,12 @@ class CallParticipantMessageNotifier {
         return callParticipantMessageListeners;
     }
 
+    public synchronized void notifyRaiseHand(String sessionId, boolean state, long timestamp) {
+        for (SignalingMessageReceiver.CallParticipantMessageListener listener : getListenersFor(sessionId)) {
+            listener.onRaiseHand(state, timestamp);
+        }
+    }
+
     public synchronized void notifyUnshareScreen(String sessionId) {
         for (SignalingMessageReceiver.CallParticipantMessageListener listener : getListenersFor(sessionId)) {
             listener.onUnshareScreen();
diff --git a/app/src/main/java/com/nextcloud/talk/signaling/SignalingMessageReceiver.java b/app/src/main/java/com/nextcloud/talk/signaling/SignalingMessageReceiver.java
index 161dae555..47dd83ca9 100644
--- a/app/src/main/java/com/nextcloud/talk/signaling/SignalingMessageReceiver.java
+++ b/app/src/main/java/com/nextcloud/talk/signaling/SignalingMessageReceiver.java
@@ -128,6 +128,7 @@ public abstract class SignalingMessageReceiver {
      * message on the call participant.
      */
     public interface CallParticipantMessageListener {
+        void onRaiseHand(boolean state, long timestamp);
         void onUnshareScreen();
     }
 
@@ -415,6 +416,63 @@ public abstract class SignalingMessageReceiver {
         String sessionId = signalingMessage.getFrom();
         String roomType = signalingMessage.getRoomType();
 
+        if ("raiseHand".equals(type)) {
+            // Message schema (external signaling server):
+            // {
+            //     "type": "message",
+            //     "message": {
+            //         "sender": {
+            //             ...
+            //         },
+            //         "data": {
+            //             "to": #STRING#,
+            //             "sid": #STRING#,
+            //             "roomType": "video",
+            //             "type": "raiseHand",
+            //             "payload": {
+            //                 "state": #BOOLEAN#,
+            //                 "timestamp": #LONG#,
+            //             },
+            //             "from": #STRING#,
+            //         },
+            //     },
+            // }
+            //
+            // Message schema (internal signaling server):
+            // {
+            //     "type": "message",
+            //     "data": {
+            //         "to": #STRING#,
+            //         "sid": #STRING#,
+            //         "roomType": "video",
+            //         "type": "raiseHand",
+            //         "payload": {
+            //             "state": #BOOLEAN#,
+            //             "timestamp": #LONG#,
+            //         },
+            //         "from": #STRING#,
+            //     },
+            // }
+
+            NCMessagePayload payload = signalingMessage.getPayload();
+            if (payload == null) {
+                // Broken message, this should not happen.
+                return;
+            }
+
+            Boolean state = payload.getState();
+            Long timestamp = payload.getTimestamp();
+
+            if (state == null || timestamp == null) {
+                // Broken message, this should not happen.
+                return;
+            }
+
+            callParticipantMessageNotifier.notifyRaiseHand(sessionId, state, timestamp);
+
+            return;
+        }
+
         // "unshareScreen" messages are directly sent to the screen peer connection when the internal signaling
         // server is used, and to the room when the external signaling server is used. However, the (relevant) data
         // of the received message ("from" and "type") is the same in both cases.
diff --git a/app/src/test/java/com/nextcloud/talk/signaling/SignalingMessageReceiverCallParticipantTest.java b/app/src/test/java/com/nextcloud/talk/signaling/SignalingMessageReceiverCallParticipantTest.java
index 01963682e..96d90b23d 100644
--- a/app/src/test/java/com/nextcloud/talk/signaling/SignalingMessageReceiverCallParticipantTest.java
+++ b/app/src/test/java/com/nextcloud/talk/signaling/SignalingMessageReceiverCallParticipantTest.java
@@ -19,6 +19,7 @@
  */
 package com.nextcloud.talk.signaling;
 
+import com.nextcloud.talk.models.json.signaling.NCMessagePayload;
 import com.nextcloud.talk.models.json.signaling.NCSignalingMessage;
 
 import org.junit.Assert;
@@ -62,6 +63,27 @@ public class SignalingMessageReceiverCallParticipantTest {
         });
     }
 
+    @Test
+    public void testCallParticipantMessageRaiseHand() {
+        SignalingMessageReceiver.CallParticipantMessageListener mockedCallParticipantMessageListener =
+            mock(SignalingMessageReceiver.CallParticipantMessageListener.class);
+
+        signalingMessageReceiver.addListener(mockedCallParticipantMessageListener, "theSessionId");
+
+        NCSignalingMessage signalingMessage = new NCSignalingMessage();
+        signalingMessage.setFrom("theSessionId");
+        signalingMessage.setType("raiseHand");
+        signalingMessage.setRoomType("theRoomType");
+        NCMessagePayload messagePayload = new NCMessagePayload();
+        messagePayload.setType("raiseHand");
+        messagePayload.setState(Boolean.TRUE);
+        messagePayload.setTimestamp(4815162342L);
+        signalingMessage.setPayload(messagePayload);
+        signalingMessageReceiver.processSignalingMessage(signalingMessage);
+
+        verify(mockedCallParticipantMessageListener, only()).onRaiseHand(true, 4815162342L);
+    }
+
     @Test
     public void testCallParticipantMessageUnshareScreen() {
         SignalingMessageReceiver.CallParticipantMessageListener mockedCallParticipantMessageListener =