瀏覽代碼

Merge pull request #2543 from nextcloud/add-interface-for-sending-signaling-messages

Add interface for sending signaling messages
Andy Scherzinger 2 年之前
父節點
當前提交
e4e9a51889

+ 101 - 85
app/src/main/java/com/nextcloud/talk/activities/CallActivity.java

@@ -66,7 +66,6 @@ import com.nextcloud.talk.events.ConfigurationChangeEvent;
 import com.nextcloud.talk.events.MediaStreamEvent;
 import com.nextcloud.talk.events.NetworkEvent;
 import com.nextcloud.talk.events.PeerConnectionEvent;
-import com.nextcloud.talk.events.SessionDescriptionSendEvent;
 import com.nextcloud.talk.events.WebSocketCommunicationEvent;
 import com.nextcloud.talk.models.ExternalSignalingServer;
 import com.nextcloud.talk.models.json.capabilities.CapabilitiesOverall;
@@ -79,13 +78,13 @@ import com.nextcloud.talk.models.json.participants.ParticipantsOverall;
 import com.nextcloud.talk.models.json.signaling.DataChannelMessage;
 import com.nextcloud.talk.models.json.signaling.DataChannelMessageNick;
 import com.nextcloud.talk.models.json.signaling.NCMessagePayload;
-import com.nextcloud.talk.models.json.signaling.NCMessageWrapper;
 import com.nextcloud.talk.models.json.signaling.NCSignalingMessage;
 import com.nextcloud.talk.models.json.signaling.Signaling;
 import com.nextcloud.talk.models.json.signaling.SignalingOverall;
 import com.nextcloud.talk.models.json.signaling.settings.IceServer;
 import com.nextcloud.talk.models.json.signaling.settings.SignalingSettingsOverall;
 import com.nextcloud.talk.signaling.SignalingMessageReceiver;
+import com.nextcloud.talk.signaling.SignalingMessageSender;
 import com.nextcloud.talk.ui.dialog.AudioOutputDialog;
 import com.nextcloud.talk.users.UserManager;
 import com.nextcloud.talk.utils.ApiUtils;
@@ -262,6 +261,9 @@ public class CallActivity extends CallBaseActivity {
     private InternalSignalingMessageReceiver internalSignalingMessageReceiver = new InternalSignalingMessageReceiver();
     private SignalingMessageReceiver signalingMessageReceiver;
 
+    private InternalSignalingMessageSender internalSignalingMessageSender = new InternalSignalingMessageSender();
+    private SignalingMessageSender signalingMessageSender;
+
     private Map<String, SignalingMessageReceiver.CallParticipantMessageListener> callParticipantMessageListeners =
         new HashMap<>();
 
@@ -1369,6 +1371,7 @@ public class CallActivity extends CallBaseActivity {
                         signalingMessageReceiver = internalSignalingMessageReceiver;
                         signalingMessageReceiver.addListener(participantListMessageListener);
                         signalingMessageReceiver.addListener(offerMessageListener);
+                        signalingMessageSender = internalSignalingMessageSender;
                         joinRoomAndCall();
                     }
                 }
@@ -1572,6 +1575,7 @@ public class CallActivity extends CallBaseActivity {
             signalingMessageReceiver = webSocketClient.getSignalingMessageReceiver();
             signalingMessageReceiver.addListener(participantListMessageListener);
             signalingMessageReceiver.addListener(offerMessageListener);
+            signalingMessageSender = webSocketClient.getSignalingMessageSender();
         } else {
             if (webSocketClient.isConnected() && currentCallStatus == CallStatus.PUBLISHER_FAILED) {
                 webSocketClient.restartWebSocket();
@@ -1615,11 +1619,6 @@ public class CallActivity extends CallBaseActivity {
                     performCall();
                 }
                 break;
-            case "peerReadyForRequestingOffer":
-                Log.d(TAG, "onMessageEvent 'peerReadyForRequestingOffer'");
-                webSocketClient.requestOfferForSessionIdWithType(
-                    webSocketCommunicationEvent.getHashMap().get("sessionId"), "video");
-                break;
         }
     }
 
@@ -1955,7 +1954,8 @@ public class CallActivity extends CallBaseActivity {
                                                                   true,
                                                                   true,
                                                                   type,
-                                                                  signalingMessageReceiver);
+                                                                  signalingMessageReceiver,
+                                                                  signalingMessageSender);
 
             } else if (hasMCU) {
                 peerConnectionWrapper = new PeerConnectionWrapper(peerConnectionFactory,
@@ -1967,7 +1967,8 @@ public class CallActivity extends CallBaseActivity {
                                                                   false,
                                                                   true,
                                                                   type,
-                                                                  signalingMessageReceiver);
+                                                                  signalingMessageReceiver,
+                                                                  signalingMessageSender);
             } else {
                 if (!"screen".equals(type)) {
                     peerConnectionWrapper = new PeerConnectionWrapper(peerConnectionFactory,
@@ -1979,7 +1980,8 @@ public class CallActivity extends CallBaseActivity {
                                                                       false,
                                                                       false,
                                                                       type,
-                                                                      signalingMessageReceiver);
+                                                                      signalingMessageReceiver,
+                                                                      signalingMessageSender);
                 } else {
                     peerConnectionWrapper = new PeerConnectionWrapper(peerConnectionFactory,
                                                                       iceServers,
@@ -1990,7 +1992,8 @@ public class CallActivity extends CallBaseActivity {
                                                                       false,
                                                                       false,
                                                                       type,
-                                                                      signalingMessageReceiver);
+                                                                      signalingMessageReceiver,
+                                                                      signalingMessageSender);
                 }
             }
 
@@ -2234,80 +2237,6 @@ public class CallActivity extends CallBaseActivity {
         }
     }
 
-    @Subscribe(threadMode = ThreadMode.BACKGROUND)
-    public void onMessageEvent(SessionDescriptionSendEvent sessionDescriptionSend) throws IOException {
-        NCMessageWrapper ncMessageWrapper = new NCMessageWrapper();
-        ncMessageWrapper.setEv("message");
-        ncMessageWrapper.setSessionId(callSession);
-        NCSignalingMessage ncSignalingMessage = new NCSignalingMessage();
-        ncSignalingMessage.setTo(sessionDescriptionSend.getPeerId());
-        ncSignalingMessage.setRoomType(sessionDescriptionSend.getVideoStreamType());
-        ncSignalingMessage.setType(sessionDescriptionSend.getType());
-        NCMessagePayload ncMessagePayload = new NCMessagePayload();
-        ncMessagePayload.setType(sessionDescriptionSend.getType());
-
-        if (!"candidate".equals(sessionDescriptionSend.getType())) {
-            ncMessagePayload.setSdp(sessionDescriptionSend.getSessionDescription().description);
-            ncMessagePayload.setNick(conversationUser.getDisplayName());
-        } else {
-            ncMessagePayload.setIceCandidate(sessionDescriptionSend.getNcIceCandidate());
-        }
-
-
-        // Set all we need
-        ncSignalingMessage.setPayload(ncMessagePayload);
-        ncMessageWrapper.setSignalingMessage(ncSignalingMessage);
-
-
-        if (!hasExternalSignalingServer) {
-            StringBuilder stringBuilder = new StringBuilder();
-            stringBuilder.append("{")
-                .append("\"fn\":\"")
-                .append(StringEscapeUtils.escapeJson(LoganSquare.serialize(ncMessageWrapper.getSignalingMessage())))
-                .append("\"")
-                .append(",")
-                .append("\"sessionId\":")
-                .append("\"").append(StringEscapeUtils.escapeJson(callSession)).append("\"")
-                .append(",")
-                .append("\"ev\":\"message\"")
-                .append("}");
-
-            List<String> strings = new ArrayList<>();
-            String stringToSend = stringBuilder.toString();
-            strings.add(stringToSend);
-
-            int apiVersion = ApiUtils.getSignalingApiVersion(conversationUser, new int[]{ApiUtils.APIv3, 2, 1});
-
-            ncApi.sendSignalingMessages(credentials, ApiUtils.getUrlForSignaling(apiVersion, baseUrl, roomToken),
-                                        strings.toString())
-                .retry(3)
-                .subscribeOn(Schedulers.io())
-                .subscribe(new Observer<SignalingOverall>() {
-                    @Override
-                    public void onSubscribe(@io.reactivex.annotations.NonNull Disposable d) {
-                        // unused atm
-                    }
-
-                    @Override
-                    public void onNext(@io.reactivex.annotations.NonNull SignalingOverall signalingOverall) {
-                        receivedSignalingMessages(signalingOverall.getOcs().getSignalings());
-                    }
-
-                    @Override
-                    public void onError(@io.reactivex.annotations.NonNull Throwable e) {
-                        Log.e(TAG, "", e);
-                    }
-
-                    @Override
-                    public void onComplete() {
-                        // unused atm
-                    }
-                });
-        } else {
-            webSocketClient.sendCallMessage(ncMessageWrapper);
-        }
-    }
-
     @Override
     public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions,
                                            @NonNull int[] grantResults) {
@@ -2644,6 +2573,93 @@ public class CallActivity extends CallBaseActivity {
         }
     }
 
+    private class InternalSignalingMessageSender implements SignalingMessageSender {
+
+        @Override
+        public void send(NCSignalingMessage ncSignalingMessage) {
+            addLocalParticipantNickIfNeeded(ncSignalingMessage);
+
+            String serializedNcSignalingMessage;
+            try {
+                serializedNcSignalingMessage = LoganSquare.serialize(ncSignalingMessage);
+            } catch (IOException e) {
+                Log.e(TAG, "Failed to serialize signaling message", e);
+                return;
+            }
+
+            // The message wrapper can not be defined in a JSON model to be directly serialized, as sent messages
+            // need to be serialized twice; first the signaling message, and then the wrapper as a whole. Received
+            // messages, on the other hand, just need to be deserialized once.
+            StringBuilder stringBuilder = new StringBuilder();
+            stringBuilder.append('{')
+                .append("\"fn\":\"")
+                .append(StringEscapeUtils.escapeJson(serializedNcSignalingMessage))
+                .append('\"')
+                .append(',')
+                .append("\"sessionId\":")
+                .append('\"').append(StringEscapeUtils.escapeJson(callSession)).append('\"')
+                .append(',')
+                .append("\"ev\":\"message\"")
+                .append('}');
+
+            List<String> strings = new ArrayList<>();
+            String stringToSend = stringBuilder.toString();
+            strings.add(stringToSend);
+
+            int apiVersion = ApiUtils.getSignalingApiVersion(conversationUser, new int[]{ApiUtils.APIv3, 2, 1});
+
+            ncApi.sendSignalingMessages(credentials, ApiUtils.getUrlForSignaling(apiVersion, baseUrl, roomToken),
+                                        strings.toString())
+                .retry(3)
+                .subscribeOn(Schedulers.io())
+                .subscribe(new Observer<SignalingOverall>() {
+                    @Override
+                    public void onSubscribe(@io.reactivex.annotations.NonNull Disposable d) {
+                    }
+
+                    @Override
+                    public void onNext(@io.reactivex.annotations.NonNull SignalingOverall signalingOverall) {
+                        // When sending messages to the internal signaling server the response has been empty since
+                        // Talk v2.9.0, so it is not really needed to process it, but there is no harm either in
+                        // doing that, as technically messages could be returned.
+                        receivedSignalingMessages(signalingOverall.getOcs().getSignalings());
+                    }
+
+                    @Override
+                    public void onError(@io.reactivex.annotations.NonNull Throwable e) {
+                        Log.e(TAG, "", e);
+                    }
+
+                    @Override
+                    public void onComplete() {
+                    }
+                });
+        }
+
+        /**
+         * Adds the local participant nick to offers and answers.
+         *
+         * For legacy reasons the offers and answers sent when the internal signaling server is used are expected to
+         * provide the nick of the local participant.
+         *
+         * @param ncSignalingMessage the message to add the nick to
+         */
+        private void addLocalParticipantNickIfNeeded(NCSignalingMessage ncSignalingMessage) {
+            String type = ncSignalingMessage.getType();
+            if (!"offer".equals(type) && !"answer".equals(type)) {
+                return;
+            }
+
+            NCMessagePayload payload = ncSignalingMessage.getPayload();
+            if (payload == null) {
+                // Broken message, this should not happen
+                return;
+            }
+
+            payload.setNick(conversationUser.getDisplayName());
+        }
+    }
+
     private class MicrophoneButtonTouchListener implements View.OnTouchListener {
 
         @SuppressLint("ClickableViewAccessibility")

+ 0 - 129
app/src/main/java/com/nextcloud/talk/events/SessionDescriptionSendEvent.java

@@ -1,129 +0,0 @@
-/*
- * Nextcloud Talk application
- *
- * @author Mario Danic
- * Copyright (C) 2017 Mario Danic <mario@lovelyhq.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.events;
-
-import com.nextcloud.talk.models.json.signaling.NCIceCandidate;
-
-import org.webrtc.SessionDescription;
-
-import androidx.annotation.Nullable;
-
-public class SessionDescriptionSendEvent {
-    @Nullable
-    private final SessionDescription sessionDescription;
-    private final String peerId;
-    private final String type;
-    @Nullable
-    private final NCIceCandidate ncIceCandidate;
-    private final String videoStreamType;
-
-    public SessionDescriptionSendEvent(@Nullable SessionDescription sessionDescription, String peerId, String type,
-                                       @Nullable NCIceCandidate ncIceCandidate, @Nullable String videoStreamType) {
-        this.sessionDescription = sessionDescription;
-        this.peerId = peerId;
-        this.type = type;
-        this.ncIceCandidate = ncIceCandidate;
-        this.videoStreamType = videoStreamType;
-    }
-
-    @Nullable
-    public SessionDescription getSessionDescription() {
-        return this.sessionDescription;
-    }
-
-    public String getPeerId() {
-        return this.peerId;
-    }
-
-    public String getType() {
-        return this.type;
-    }
-
-    @Nullable
-    public NCIceCandidate getNcIceCandidate() {
-        return this.ncIceCandidate;
-    }
-
-    public String getVideoStreamType() {
-        return this.videoStreamType;
-    }
-
-    public boolean equals(final Object o) {
-        if (o == this) {
-            return true;
-        }
-        if (!(o instanceof SessionDescriptionSendEvent)) {
-            return false;
-        }
-        final SessionDescriptionSendEvent other = (SessionDescriptionSendEvent) o;
-        if (!other.canEqual((Object) this)) {
-            return false;
-        }
-        final Object this$sessionDescription = this.getSessionDescription();
-        final Object other$sessionDescription = other.getSessionDescription();
-        if (this$sessionDescription == null ? other$sessionDescription != null : !this$sessionDescription.equals(other$sessionDescription)) {
-            return false;
-        }
-        final Object this$peerId = this.getPeerId();
-        final Object other$peerId = other.getPeerId();
-        if (this$peerId == null ? other$peerId != null : !this$peerId.equals(other$peerId)) {
-            return false;
-        }
-        final Object this$type = this.getType();
-        final Object other$type = other.getType();
-        if (this$type == null ? other$type != null : !this$type.equals(other$type)) {
-            return false;
-        }
-        final Object this$ncIceCandidate = this.getNcIceCandidate();
-        final Object other$ncIceCandidate = other.getNcIceCandidate();
-        if (this$ncIceCandidate == null ? other$ncIceCandidate != null : !this$ncIceCandidate.equals(other$ncIceCandidate)) {
-            return false;
-        }
-        final Object this$videoStreamType = this.getVideoStreamType();
-        final Object other$videoStreamType = other.getVideoStreamType();
-
-        return this$videoStreamType == null ? other$videoStreamType == null : this$videoStreamType.equals(other$videoStreamType);
-    }
-
-    protected boolean canEqual(final Object other) {
-        return other instanceof SessionDescriptionSendEvent;
-    }
-
-    public int hashCode() {
-        final int PRIME = 59;
-        int result = 1;
-        final Object $sessionDescription = this.getSessionDescription();
-        result = result * PRIME + ($sessionDescription == null ? 43 : $sessionDescription.hashCode());
-        final Object $peerId = this.getPeerId();
-        result = result * PRIME + ($peerId == null ? 43 : $peerId.hashCode());
-        final Object $type = this.getType();
-        result = result * PRIME + ($type == null ? 43 : $type.hashCode());
-        final Object $ncIceCandidate = this.getNcIceCandidate();
-        result = result * PRIME + ($ncIceCandidate == null ? 43 : $ncIceCandidate.hashCode());
-        final Object $videoStreamType = this.getVideoStreamType();
-        result = result * PRIME + ($videoStreamType == null ? 43 : $videoStreamType.hashCode());
-        return result;
-    }
-
-    public String toString() {
-        return "SessionDescriptionSendEvent(sessionDescription=" + this.getSessionDescription() + ", peerId=" + this.getPeerId() + ", type=" + this.getType() + ", ncIceCandidate=" + this.getNcIceCandidate() + ", videoStreamType=" + this.getVideoStreamType() + ")";
-    }
-}

+ 0 - 42
app/src/main/java/com/nextcloud/talk/models/json/signaling/NCMessageWrapper.kt

@@ -1,42 +0,0 @@
-/*
- * Nextcloud Talk application
- *
- * @author Mario Danic
- * @author Andy Scherzinger
- * Copyright (C) 2022 Andy Scherzinger <info@andy-scherzinger.de>
- * Copyright (C) 2017 Mario Danic <mario@lovelyhq.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.models.json.signaling
-
-import android.os.Parcelable
-import com.bluelinelabs.logansquare.annotation.JsonField
-import com.bluelinelabs.logansquare.annotation.JsonObject
-import kotlinx.android.parcel.Parcelize
-
-@Parcelize
-@JsonObject
-data class NCMessageWrapper(
-    @JsonField(name = ["fn"])
-    var signalingMessage: NCSignalingMessage? = null,
-    /** always a "message" */
-    @JsonField(name = ["ev"])
-    var ev: String? = null,
-    @JsonField(name = ["sessionId"])
-    var sessionId: String? = null
-) : Parcelable {
-    // This constructor is added to work with the 'com.bluelinelabs.logansquare.annotation.JsonObject'
-    constructor() : this(null, null, null)
-}

+ 1 - 1
app/src/main/java/com/nextcloud/talk/models/json/signaling/Signaling.kt

@@ -34,7 +34,7 @@ import kotlinx.android.parcel.TypeParceler
 data class Signaling(
     @JsonField(name = ["type"])
     var type: String? = null,
-    /** can be NCMessageWrapper or List<HashMap<String,String>> */
+    /** can be NCSignalingMessage (encoded as a String) or List<Map<String, Object>> */
     @JsonField(name = ["data"])
     var messageWrapper: Any? = null
 ) : Parcelable {

+ 0 - 39
app/src/main/java/com/nextcloud/talk/models/json/websocket/RequestOfferOverallWebSocketMessage.kt

@@ -1,39 +0,0 @@
-/*
- * Nextcloud Talk application
- *
- * @author Mario Danic
- * @author Andy Scherzinger
- * Copyright (C) 2022 Andy Scherzinger <info@andy-scherzinger.de>
- * Copyright (C) 2017-2018 Mario Danic <mario@lovelyhq.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.models.json.websocket
-
-import android.os.Parcelable
-import com.bluelinelabs.logansquare.annotation.JsonField
-import com.bluelinelabs.logansquare.annotation.JsonObject
-import kotlinx.android.parcel.Parcelize
-
-@Parcelize
-@JsonObject
-data class RequestOfferOverallWebSocketMessage(
-    @JsonField(name = ["type"])
-    var type: String? = null,
-    @JsonField(name = ["message"])
-    var requestOfferOverallWebSocketMessage: RequestOfferSignalingMessage? = null
-) : Parcelable {
-    // This constructor is added to work with the 'com.bluelinelabs.logansquare.annotation.JsonObject'
-    constructor() : this(null, null)
-}

+ 0 - 39
app/src/main/java/com/nextcloud/talk/models/json/websocket/RequestOfferSignalingMessage.kt

@@ -1,39 +0,0 @@
-/*
- * Nextcloud Talk application
- *
- * @author Mario Danic
- * @author Andy Scherzinger
- * Copyright (C) 2022 Andy Scherzinger <info@andy-scherzinger.de>
- * Copyright (C) 2017-2018 Mario Danic <mario@lovelyhq.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.models.json.websocket
-
-import android.os.Parcelable
-import com.bluelinelabs.logansquare.annotation.JsonField
-import com.bluelinelabs.logansquare.annotation.JsonObject
-import kotlinx.android.parcel.Parcelize
-
-@Parcelize
-@JsonObject
-class RequestOfferSignalingMessage(
-    @JsonField(name = ["recipient"])
-    var actorWebSocketMessage: ActorWebSocketMessage? = null,
-    @JsonField(name = ["data"])
-    var signalingDataWebSocketMessageForOffer: SignalingDataWebSocketMessageForOffer? = null
-) : Parcelable {
-    // This constructor is added to work with the 'com.bluelinelabs.logansquare.annotation.JsonObject'
-    constructor() : this(null, null)
-}

+ 0 - 39
app/src/main/java/com/nextcloud/talk/models/json/websocket/SignalingDataWebSocketMessageForOffer.kt

@@ -1,39 +0,0 @@
-/*
- * Nextcloud Talk application
- *
- * @author Mario Danic
- * @author Andy Scherzinger
- * Copyright (C) 2022 Andy Scherzinger <info@andy-scherzinger.de>
- * Copyright (C) 2017-2018 Mario Danic <mario@lovelyhq.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.models.json.websocket
-
-import android.os.Parcelable
-import com.bluelinelabs.logansquare.annotation.JsonField
-import com.bluelinelabs.logansquare.annotation.JsonObject
-import kotlinx.android.parcel.Parcelize
-
-@Parcelize
-@JsonObject
-class SignalingDataWebSocketMessageForOffer(
-    @JsonField(name = ["type"])
-    var type: String? = null,
-    @JsonField(name = ["roomType"])
-    var roomType: String? = null
-) : Parcelable {
-    // This constructor is added to work with the 'com.bluelinelabs.logansquare.annotation.JsonObject'
-    constructor() : this(null, null)
-}

+ 36 - 0
app/src/main/java/com/nextcloud/talk/signaling/SignalingMessageSender.java

@@ -0,0 +1,36 @@
+/*
+ * 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.signaling.NCSignalingMessage;
+
+/**
+ * Interface to send signaling messages.
+ */
+public interface SignalingMessageSender {
+
+    /**
+     * Sends the given signaling message.
+     *
+     * @param ncSignalingMessage the message to send
+     */
+    void send(NCSignalingMessage ncSignalingMessage);
+
+}

+ 16 - 16
app/src/main/java/com/nextcloud/talk/webrtc/MagicWebSocketInstance.java

@@ -31,7 +31,6 @@ import com.nextcloud.talk.data.user.model.User;
 import com.nextcloud.talk.events.NetworkEvent;
 import com.nextcloud.talk.events.WebSocketCommunicationEvent;
 import com.nextcloud.talk.models.json.participants.Participant;
-import com.nextcloud.talk.models.json.signaling.NCMessageWrapper;
 import com.nextcloud.talk.models.json.signaling.NCSignalingMessage;
 import com.nextcloud.talk.models.json.websocket.BaseWebSocketMessage;
 import com.nextcloud.talk.models.json.websocket.ByeWebSocketMessage;
@@ -41,6 +40,7 @@ import com.nextcloud.talk.models.json.websocket.EventOverallWebSocketMessage;
 import com.nextcloud.talk.models.json.websocket.HelloResponseOverallWebSocketMessage;
 import com.nextcloud.talk.models.json.websocket.JoinedRoomOverallWebSocketMessage;
 import com.nextcloud.talk.signaling.SignalingMessageReceiver;
+import com.nextcloud.talk.signaling.SignalingMessageSender;
 import com.nextcloud.talk.utils.bundle.BundleKeys;
 
 import org.greenrobot.eventbus.EventBus;
@@ -102,6 +102,8 @@ public class MagicWebSocketInstance extends WebSocketListener {
 
     private final ExternalSignalingMessageReceiver signalingMessageReceiver = new ExternalSignalingMessageReceiver();
 
+    private final ExternalSignalingMessageSender signalingMessageSender = new ExternalSignalingMessageSender();
+
     MagicWebSocketInstance(User conversationUser, String connectionUrl, String webSocketTicket) {
         NextcloudTalkApplication.Companion.getSharedApplication().getComponentApplication().inject(this);
 
@@ -344,9 +346,9 @@ public class MagicWebSocketInstance extends WebSocketListener {
         }
     }
 
-    public void sendCallMessage(NCMessageWrapper ncMessageWrapper) {
+    private void sendCallMessage(NCSignalingMessage ncSignalingMessage) {
         try {
-            String message = LoganSquare.serialize(webSocketConnectionHelper.getAssembledCallMessageModel(ncMessageWrapper));
+            String message = LoganSquare.serialize(webSocketConnectionHelper.getAssembledCallMessageModel(ncSignalingMessage));
             if (!connected || reconnecting) {
                 messagesQueue.add(message);
             } else {
@@ -357,19 +359,6 @@ public class MagicWebSocketInstance extends WebSocketListener {
         }
     }
 
-    public void requestOfferForSessionIdWithType(String sessionIdParam, String roomType) {
-        try {
-            String message = LoganSquare.serialize(webSocketConnectionHelper.getAssembledRequestOfferModel(sessionIdParam, roomType));
-            if (!connected || reconnecting) {
-                messagesQueue.add(message);
-            } else {
-                internalWebSocket.send(message);
-            }
-        } catch (IOException e) {
-            Log.e(TAG, "Failed to offer request. sessionIdParam: " + sessionIdParam + " roomType:" + roomType, e);
-        }
-    }
-
     void sendBye() {
         if (connected) {
             try {
@@ -420,6 +409,10 @@ public class MagicWebSocketInstance extends WebSocketListener {
         return signalingMessageReceiver;
     }
 
+    public SignalingMessageSender getSignalingMessageSender() {
+        return signalingMessageSender;
+    }
+
     /**
      * Temporary implementation of SignalingMessageReceiver until signaling related code is extracted to a Signaling
      * class.
@@ -436,4 +429,11 @@ public class MagicWebSocketInstance extends WebSocketListener {
             processSignalingMessage(message);
         }
     }
+
+    private class ExternalSignalingMessageSender implements SignalingMessageSender {
+        @Override
+        public void send(NCSignalingMessage ncSignalingMessage) {
+            sendCallMessage(ncSignalingMessage);
+        }
+    }
 }

+ 41 - 10
app/src/main/java/com/nextcloud/talk/webrtc/PeerConnectionWrapper.java

@@ -32,12 +32,13 @@ import com.nextcloud.talk.R;
 import com.nextcloud.talk.application.NextcloudTalkApplication;
 import com.nextcloud.talk.events.MediaStreamEvent;
 import com.nextcloud.talk.events.PeerConnectionEvent;
-import com.nextcloud.talk.events.SessionDescriptionSendEvent;
-import com.nextcloud.talk.events.WebSocketCommunicationEvent;
 import com.nextcloud.talk.models.json.signaling.DataChannelMessage;
 import com.nextcloud.talk.models.json.signaling.DataChannelMessageNick;
 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 com.nextcloud.talk.signaling.SignalingMessageReceiver;
+import com.nextcloud.talk.signaling.SignalingMessageSender;
 
 import org.greenrobot.eventbus.EventBus;
 import org.webrtc.AudioTrack;
@@ -78,6 +79,8 @@ public class PeerConnectionWrapper {
     private final SignalingMessageReceiver signalingMessageReceiver;
     private final WebRtcMessageListener webRtcMessageListener = new WebRtcMessageListener();
 
+    private final SignalingMessageSender signalingMessageSender;
+
     private List<IceCandidate> iceCandidates = new ArrayList<>();
     private PeerConnection peerConnection;
     private String sessionId;
@@ -101,7 +104,8 @@ public class PeerConnectionWrapper {
                                  MediaConstraints mediaConstraints,
                                  String sessionId, String localSession, @Nullable MediaStream localStream,
                                  boolean isMCUPublisher, boolean hasMCU, String videoStreamType,
-                                 SignalingMessageReceiver signalingMessageReceiver) {
+                                 SignalingMessageReceiver signalingMessageReceiver,
+                                 SignalingMessageSender signalingMessageSender) {
 
         Objects.requireNonNull(NextcloudTalkApplication.Companion.getSharedApplication()).getComponentApplication().inject(this);
 
@@ -122,6 +126,8 @@ public class PeerConnectionWrapper {
         this.signalingMessageReceiver = signalingMessageReceiver;
         this.signalingMessageReceiver.addListener(webRtcMessageListener, sessionId, videoStreamType);
 
+        this.signalingMessageSender = signalingMessageSender;
+
         if (peerConnection != null) {
             if (this.localStream != null) {
                 List<String> localStreamIds = Collections.singletonList(this.localStream.getId());
@@ -143,9 +149,10 @@ public class PeerConnectionWrapper {
                 } else if (hasMCU && this.videoStreamType.equals("video")) {
                     // If the connection type is "screen" the client sharing the screen will send an
                     // offer; offers should be requested only for videos.
-                    HashMap<String, String> hashMap = new HashMap<>();
-                    hashMap.put("sessionId", sessionId);
-                    EventBus.getDefault().post(new WebSocketCommunicationEvent("peerReadyForRequestingOffer", hashMap));
+                    // "to" property is not actually needed in the "requestoffer" signaling message, but it is used to
+                    // set the recipient session ID in the assembled call message.
+                    NCSignalingMessage ncSignalingMessage = createBaseSignalingMessage("requestoffer");
+                    signalingMessageSender.send(ncSignalingMessage);
                 } else if (!hasMCU && hasInitiated) {
                     peerConnection.createOffer(magicSdpObserver, mediaConstraints);
                 }
@@ -269,6 +276,15 @@ public class PeerConnectionWrapper {
         return false;
     }
 
+    private NCSignalingMessage createBaseSignalingMessage(String type) {
+        NCSignalingMessage ncSignalingMessage = new NCSignalingMessage();
+        ncSignalingMessage.setTo(sessionId);
+        ncSignalingMessage.setRoomType(videoStreamType);
+        ncSignalingMessage.setType(type);
+
+        return ncSignalingMessage;
+    }
+
     private class WebRtcMessageListener implements SignalingMessageReceiver.WebRtcMessageListener {
 
         public void onOffer(String sdp, String nick) {
@@ -425,12 +441,19 @@ public class PeerConnectionWrapper {
 
         @Override
         public void onIceCandidate(IceCandidate iceCandidate) {
+            NCSignalingMessage ncSignalingMessage = createBaseSignalingMessage("candidate");
+            NCMessagePayload ncMessagePayload = new NCMessagePayload();
+            ncMessagePayload.setType("candidate");
+
             NCIceCandidate ncIceCandidate = new NCIceCandidate();
             ncIceCandidate.setSdpMid(iceCandidate.sdpMid);
             ncIceCandidate.setSdpMLineIndex(iceCandidate.sdpMLineIndex);
             ncIceCandidate.setCandidate(iceCandidate.sdp);
-            EventBus.getDefault().post(new SessionDescriptionSendEvent(null, sessionId,
-                    "candidate", ncIceCandidate, videoStreamType));
+            ncMessagePayload.setIceCandidate(ncIceCandidate);
+
+            ncSignalingMessage.setPayload(ncMessagePayload);
+
+            signalingMessageSender.send(ncSignalingMessage);
         }
 
         @Override
@@ -484,6 +507,12 @@ public class PeerConnectionWrapper {
 
         @Override
         public void onCreateSuccess(SessionDescription sessionDescription) {
+            String type = sessionDescription.type.canonicalForm();
+
+            NCSignalingMessage ncSignalingMessage = createBaseSignalingMessage(type);
+            NCMessagePayload ncMessagePayload = new NCMessagePayload();
+            ncMessagePayload.setType(type);
+
             SessionDescription sessionDescriptionWithPreferredCodec;
             String sessionDescriptionStringWithPreferredCodec = MagicWebRTCUtils.preferCodec
                     (sessionDescription.description,
@@ -492,9 +521,11 @@ public class PeerConnectionWrapper {
                     sessionDescription.type,
                     sessionDescriptionStringWithPreferredCodec);
 
+            ncMessagePayload.setSdp(sessionDescriptionWithPreferredCodec.description);
+
+            ncSignalingMessage.setPayload(ncMessagePayload);
 
-            EventBus.getDefault().post(new SessionDescriptionSendEvent(sessionDescriptionWithPreferredCodec, sessionId,
-                    sessionDescription.type.canonicalForm().toLowerCase(), null, videoStreamType));
+            signalingMessageSender.send(ncSignalingMessage);
 
             if (peerConnection != null) {
                 peerConnection.setLocalDescription(magicSdpObserver, sessionDescriptionWithPreferredCodec);

+ 4 - 27
app/src/main/java/com/nextcloud/talk/webrtc/WebSocketConnectionHelper.java

@@ -25,7 +25,7 @@ import android.util.Log;
 
 import com.nextcloud.talk.application.NextcloudTalkApplication;
 import com.nextcloud.talk.data.user.model.User;
-import com.nextcloud.talk.models.json.signaling.NCMessageWrapper;
+import com.nextcloud.talk.models.json.signaling.NCSignalingMessage;
 import com.nextcloud.talk.models.json.websocket.ActorWebSocketMessage;
 import com.nextcloud.talk.models.json.websocket.AuthParametersWebSocketMessage;
 import com.nextcloud.talk.models.json.websocket.AuthWebSocketMessage;
@@ -33,11 +33,8 @@ import com.nextcloud.talk.models.json.websocket.CallOverallWebSocketMessage;
 import com.nextcloud.talk.models.json.websocket.CallWebSocketMessage;
 import com.nextcloud.talk.models.json.websocket.HelloOverallWebSocketMessage;
 import com.nextcloud.talk.models.json.websocket.HelloWebSocketMessage;
-import com.nextcloud.talk.models.json.websocket.RequestOfferOverallWebSocketMessage;
-import com.nextcloud.talk.models.json.websocket.RequestOfferSignalingMessage;
 import com.nextcloud.talk.models.json.websocket.RoomOverallWebSocketMessage;
 import com.nextcloud.talk.models.json.websocket.RoomWebSocketMessage;
-import com.nextcloud.talk.models.json.websocket.SignalingDataWebSocketMessageForOffer;
 import com.nextcloud.talk.utils.ApiUtils;
 
 import java.util.HashMap;
@@ -146,27 +143,7 @@ public class WebSocketConnectionHelper {
         return roomOverallWebSocketMessage;
     }
 
-    RequestOfferOverallWebSocketMessage getAssembledRequestOfferModel(String sessionId, String roomType) {
-        RequestOfferOverallWebSocketMessage requestOfferOverallWebSocketMessage = new RequestOfferOverallWebSocketMessage();
-        requestOfferOverallWebSocketMessage.setType("message");
-
-        RequestOfferSignalingMessage requestOfferSignalingMessage = new RequestOfferSignalingMessage();
-
-        ActorWebSocketMessage actorWebSocketMessage = new ActorWebSocketMessage();
-        actorWebSocketMessage.setType("session");
-        actorWebSocketMessage.setSessionId(sessionId);
-        requestOfferSignalingMessage.setActorWebSocketMessage(actorWebSocketMessage);
-
-        SignalingDataWebSocketMessageForOffer signalingDataWebSocketMessageForOffer = new SignalingDataWebSocketMessageForOffer();
-        signalingDataWebSocketMessageForOffer.setRoomType(roomType);
-        signalingDataWebSocketMessageForOffer.setType("requestoffer");
-        requestOfferSignalingMessage.setSignalingDataWebSocketMessageForOffer(signalingDataWebSocketMessageForOffer);
-
-        requestOfferOverallWebSocketMessage.setRequestOfferOverallWebSocketMessage(requestOfferSignalingMessage);
-        return requestOfferOverallWebSocketMessage;
-    }
-
-    CallOverallWebSocketMessage getAssembledCallMessageModel(NCMessageWrapper ncMessageWrapper) {
+    CallOverallWebSocketMessage getAssembledCallMessageModel(NCSignalingMessage ncSignalingMessage) {
         CallOverallWebSocketMessage callOverallWebSocketMessage = new CallOverallWebSocketMessage();
         callOverallWebSocketMessage.setType("message");
 
@@ -174,9 +151,9 @@ public class WebSocketConnectionHelper {
 
         ActorWebSocketMessage actorWebSocketMessage = new ActorWebSocketMessage();
         actorWebSocketMessage.setType("session");
-        actorWebSocketMessage.setSessionId(ncMessageWrapper.getSignalingMessage().getTo());
+        actorWebSocketMessage.setSessionId(ncSignalingMessage.getTo());
         callWebSocketMessage.setRecipientWebSocketMessage(actorWebSocketMessage);
-        callWebSocketMessage.setNcSignalingMessage(ncMessageWrapper.getSignalingMessage());
+        callWebSocketMessage.setNcSignalingMessage(ncSignalingMessage);
 
         callOverallWebSocketMessage.setCallWebSocketMessage(callWebSocketMessage);
         return callOverallWebSocketMessage;

+ 1 - 1
scripts/analysis/lint-results.txt

@@ -1,2 +1,2 @@
 DO NOT TOUCH; GENERATED BY DRONE
-      <span class="mdl-layout-title">Lint Report: 112 warnings</span>
+      <span class="mdl-layout-title">Lint Report: 111 warnings</span>