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 f0b746be0fb..90e3689f1a6 100644 --- a/app/src/main/java/com/nextcloud/talk/activities/CallActivity.kt +++ b/app/src/main/java/com/nextcloud/talk/activities/CallActivity.kt @@ -1453,7 +1453,7 @@ class CallActivity : CallBaseActivity() { private fun fetchSignalingSettings() { Log.d(TAG, "fetchSignalingSettings") val apiVersion = ApiUtils.getSignalingApiVersion(conversationUser, intArrayOf(ApiUtils.API_V3, 2, 1)) - ncApi!!.getSignalingSettings(credentials, ApiUtils.getUrlForSignalingSettings(apiVersion, baseUrl)) + ncApi!!.getSignalingSettings(credentials, ApiUtils.getUrlForSignalingSettings(apiVersion, baseUrl, roomToken!!)) .subscribeOn(Schedulers.io()) .retry(API_RETRIES) .observeOn(AndroidSchedulers.mainThread()) @@ -1475,6 +1475,8 @@ class CallActivity : CallBaseActivity() { signalingSettingsOverall.ocs!!.settings!!.externalSignalingServer externalSignalingServer!!.externalSignalingTicket = signalingSettingsOverall.ocs!!.settings!!.externalSignalingTicket + externalSignalingServer!!.federation = + signalingSettingsOverall.ocs!!.settings!!.federation hasExternalSignalingServer = true } else { hasExternalSignalingServer = false @@ -1630,7 +1632,9 @@ class CallActivity : CallBaseActivity() { private fun callOrJoinRoomViaWebSocket() { if (hasExternalSignalingServer) { - webSocketClient!!.joinRoomWithRoomTokenAndSession(roomToken!!, callSession) + webSocketClient!!.joinRoomWithRoomTokenAndSession( + roomToken!!, callSession, externalSignalingServer!!.federation + ) } else { performCall() } @@ -2157,6 +2161,10 @@ class CallActivity : CallBaseActivity() { Log.d(TAG, " newSession joined: $sessionId") addCallParticipant(sessionId) + if (participant.actorType != null && participant.actorId != null) { + callParticipants[sessionId]!!.setActor(participant.actorType, participant.actorId) + } + val userId = participant.userId if (userId != null) { callParticipants[sessionId]!!.setUserId(userId) @@ -2510,10 +2518,12 @@ class CallActivity : CallBaseActivity() { } val defaultGuestNick = resources.getString(R.string.nc_nick_guest) val participantDisplayItem = ParticipantDisplayItem( + context, baseUrl, defaultGuestNick, rootEglBase, videoStreamType, + roomToken, callParticipantModel ) val sessionId = callParticipantModel.sessionId diff --git a/app/src/main/java/com/nextcloud/talk/adapters/ParticipantDisplayItem.java b/app/src/main/java/com/nextcloud/talk/adapters/ParticipantDisplayItem.java index 0eae25ac92b..b4bc87c5688 100644 --- a/app/src/main/java/com/nextcloud/talk/adapters/ParticipantDisplayItem.java +++ b/app/src/main/java/com/nextcloud/talk/adapters/ParticipantDisplayItem.java @@ -8,13 +8,16 @@ */ package com.nextcloud.talk.adapters; +import android.content.Context; import android.os.Handler; import android.os.Looper; import android.text.TextUtils; import com.nextcloud.talk.call.CallParticipantModel; import com.nextcloud.talk.call.RaisedHand; +import com.nextcloud.talk.models.json.participants.Participant; import com.nextcloud.talk.utils.ApiUtils; +import com.nextcloud.talk.utils.DisplayUtils; import org.webrtc.EglBase; import org.webrtc.MediaStream; @@ -29,6 +32,8 @@ public class ParticipantDisplayItem { private final ParticipantDisplayItemNotifier participantDisplayItemNotifier = new ParticipantDisplayItemNotifier(); + private final Context context; + private final String baseUrl; private final String defaultGuestNick; private final EglBase rootEglBase; @@ -36,8 +41,12 @@ public class ParticipantDisplayItem { private final String session; private final String streamType; + private final String roomToken; + private final CallParticipantModel callParticipantModel; + private Participant.ActorType actorType; + private String actorId; private String userId; private PeerConnection.IceConnectionState iceConnectionState; private String nick; @@ -62,8 +71,10 @@ public void onReaction(String reaction) { } }; - public ParticipantDisplayItem(String baseUrl, String defaultGuestNick, EglBase rootEglBase, String streamType, - CallParticipantModel callParticipantModel) { + public ParticipantDisplayItem(Context context, String baseUrl, String defaultGuestNick, EglBase rootEglBase, + String streamType, String roomToken, CallParticipantModel callParticipantModel) { + this.context = context; + this.baseUrl = baseUrl; this.defaultGuestNick = defaultGuestNick; this.rootEglBase = rootEglBase; @@ -71,6 +82,8 @@ public ParticipantDisplayItem(String baseUrl, String defaultGuestNick, EglBase r this.session = callParticipantModel.getSessionId(); this.streamType = streamType; + this.roomToken = roomToken; + this.callParticipantModel = callParticipantModel; this.callParticipantModel.addObserver(callParticipantModelObserver, handler); @@ -82,6 +95,8 @@ public void destroy() { } private void updateFromModel() { + actorType = callParticipantModel.getActorType(); + actorId = callParticipantModel.getActorId(); userId = callParticipantModel.getUserId(); nick = callParticipantModel.getNick(); @@ -107,7 +122,10 @@ private void updateFromModel() { } private void updateUrlForAvatar() { - if (!TextUtils.isEmpty(userId)) { + if (actorType == Participant.ActorType.FEDERATED) { + int darkTheme = DisplayUtils.INSTANCE.isDarkModeOn(context) ? 1 : 0; + urlForAvatar = ApiUtils.getUrlForFederatedAvatar(baseUrl, roomToken, actorId, darkTheme, true); + } else if (!TextUtils.isEmpty(userId)) { urlForAvatar = ApiUtils.getUrlForAvatar(baseUrl, userId, true); } else { urlForAvatar = ApiUtils.getUrlForGuestAvatar(baseUrl, getNick(), true); @@ -166,6 +184,8 @@ public void removeObserver(Observer observer) { public String toString() { return "ParticipantSession{" + "userId='" + userId + '\'' + + ", actorType='" + actorType + '\'' + + ", actorId='" + actorId + '\'' + ", session='" + session + '\'' + ", nick='" + nick + '\'' + ", urlForAvatar='" + urlForAvatar + '\'' + diff --git a/app/src/main/java/com/nextcloud/talk/call/CallParticipant.java b/app/src/main/java/com/nextcloud/talk/call/CallParticipant.java index 50c1ae66913..af1ac26728a 100644 --- a/app/src/main/java/com/nextcloud/talk/call/CallParticipant.java +++ b/app/src/main/java/com/nextcloud/talk/call/CallParticipant.java @@ -6,6 +6,7 @@ */ package com.nextcloud.talk.call; +import com.nextcloud.talk.models.json.participants.Participant; import com.nextcloud.talk.signaling.SignalingMessageReceiver; import com.nextcloud.talk.webrtc.PeerConnectionWrapper; @@ -132,6 +133,10 @@ public CallParticipantModel getCallParticipantModel() { return callParticipantModel; } + public void setActor(Participant.ActorType actorType, String actorId) { + callParticipantModel.setActor(actorType, actorId); + } + public void setUserId(String userId) { callParticipantModel.setUserId(userId); } diff --git a/app/src/main/java/com/nextcloud/talk/call/CallParticipantList.java b/app/src/main/java/com/nextcloud/talk/call/CallParticipantList.java index 05712f5c06c..921900e7ec7 100644 --- a/app/src/main/java/com/nextcloud/talk/call/CallParticipantList.java +++ b/app/src/main/java/com/nextcloud/talk/call/CallParticipantList.java @@ -122,6 +122,8 @@ public void onAllParticipantsUpdate(long inCall) { private Participant copyParticipant(Participant participant) { Participant copiedParticipant = new Participant(); + copiedParticipant.setActorId(participant.getActorId()); + copiedParticipant.setActorType(participant.getActorType()); copiedParticipant.setInCall(participant.getInCall()); copiedParticipant.setInternal(participant.getInternal()); copiedParticipant.setLastPing(participant.getLastPing()); diff --git a/app/src/main/java/com/nextcloud/talk/call/CallParticipantModel.java b/app/src/main/java/com/nextcloud/talk/call/CallParticipantModel.java index 5a05cbc4257..bd59a28e838 100644 --- a/app/src/main/java/com/nextcloud/talk/call/CallParticipantModel.java +++ b/app/src/main/java/com/nextcloud/talk/call/CallParticipantModel.java @@ -8,6 +8,8 @@ import android.os.Handler; +import com.nextcloud.talk.models.json.participants.Participant; + import org.webrtc.MediaStream; import org.webrtc.PeerConnection; @@ -25,6 +27,8 @@ * * Audio and video in screen shares, on the other hand, are always seen as available. * + * Actor type and actor id will be set only in Talk >= 20. + * * Clients of the model can observe it with CallParticipantModel.Observer to be notified when any value changes. * Getters called after receiving a notification are guaranteed to provide at least the value that triggered the * notification, but it may return even a more up to date one (so getting the value again on the following @@ -39,6 +43,8 @@ public class CallParticipantModel { protected final String sessionId; + protected Data actorType; + protected Data actorId; protected Data userId; protected Data nick; @@ -81,6 +87,8 @@ public void setValue(T value) { public CallParticipantModel(String sessionId) { this.sessionId = sessionId; + this.actorType = new Data<>(); + this.actorId = new Data<>(); this.userId = new Data<>(); this.nick = new Data<>(); @@ -101,6 +109,14 @@ public String getSessionId() { return sessionId; } + public Participant.ActorType getActorType() { + return actorType.getValue(); + } + + public String getActorId() { + return actorId.getValue(); + } + public String getUserId() { return userId.getValue(); } diff --git a/app/src/main/java/com/nextcloud/talk/call/MutableCallParticipantModel.java b/app/src/main/java/com/nextcloud/talk/call/MutableCallParticipantModel.java index f1288a8a032..221b383521a 100644 --- a/app/src/main/java/com/nextcloud/talk/call/MutableCallParticipantModel.java +++ b/app/src/main/java/com/nextcloud/talk/call/MutableCallParticipantModel.java @@ -6,6 +6,8 @@ */ package com.nextcloud.talk.call; +import com.nextcloud.talk.models.json.participants.Participant; + import org.webrtc.MediaStream; import org.webrtc.PeerConnection; @@ -20,6 +22,11 @@ public MutableCallParticipantModel(String sessionId) { super(sessionId); } + public void setActor(Participant.ActorType actorType, String actorId) { + this.actorType.setValue(actorType); + this.actorId.setValue(actorId); + } + public void setUserId(String userId) { this.userId.setValue(userId); } diff --git a/app/src/main/java/com/nextcloud/talk/chat/ChatActivity.kt b/app/src/main/java/com/nextcloud/talk/chat/ChatActivity.kt index c41ac3c2319..ae0cc210e5d 100644 --- a/app/src/main/java/com/nextcloud/talk/chat/ChatActivity.kt +++ b/app/src/main/java/com/nextcloud/talk/chat/ChatActivity.kt @@ -120,10 +120,12 @@ import com.nextcloud.talk.jobs.ShareOperationWorker import com.nextcloud.talk.jobs.UploadAndShareFilesWorker import com.nextcloud.talk.location.LocationPickerActivity import com.nextcloud.talk.messagesearch.MessageSearchActivity +import com.nextcloud.talk.models.ExternalSignalingServer import com.nextcloud.talk.models.domain.ConversationModel import com.nextcloud.talk.models.json.capabilities.SpreedCapability import com.nextcloud.talk.models.json.chat.ReadStatus import com.nextcloud.talk.models.json.conversations.ConversationEnums +import com.nextcloud.talk.models.json.signaling.settings.SignalingSettingsOverall import com.nextcloud.talk.polls.ui.PollCreateDialogFragment import com.nextcloud.talk.remotefilebrowser.activities.RemoteFileBrowserActivity import com.nextcloud.talk.shareditems.activities.SharedItemsActivity @@ -177,6 +179,8 @@ import com.stfalcon.chatkit.messages.MessageHolders import com.stfalcon.chatkit.messages.MessageHolders.ContentChecker import com.stfalcon.chatkit.messages.MessagesListAdapter import com.stfalcon.chatkit.utils.DateFormatter +import io.reactivex.Observer +import io.reactivex.disposables.Disposable import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.flow.collect @@ -302,6 +306,7 @@ class ChatActivity : var webSocketInstance: WebSocketInstance? = null var signalingMessageSender: SignalingMessageSender? = null + var externalSignalingServer: ExternalSignalingServer? = null var getRoomInfoTimerHandler: Handler? = null @@ -631,10 +636,12 @@ class ChatActivity : logConversationInfos("joinRoomWithPassword#onNext") + setupWebsocket() if (webSocketInstance != null) { webSocketInstance?.joinRoomWithRoomTokenAndSession( roomToken, - sessionIdAfterRoomJoined + sessionIdAfterRoomJoined, + externalSignalingServer?.federation ) } if (startCallFromNotification != null && startCallFromNotification) { @@ -952,7 +959,6 @@ class ChatActivity : pullChatMessagesPending = false - setupWebsocket() webSocketInstance?.getSignalingMessageReceiver()?.addListener(localParticipantMessageListener) webSocketInstance?.getSignalingMessageReceiver()?.addListener(conversationMessageListener) @@ -2391,10 +2397,12 @@ class ChatActivity : } else { Log.d(TAG, "sessionID was valid -> skip joinRoom") + setupWebsocket() if (webSocketInstance != null) { webSocketInstance?.joinRoomWithRoomTokenAndSession( roomToken, - sessionIdAfterRoomJoined + sessionIdAfterRoomJoined, + externalSignalingServer?.federation ) } } @@ -2423,16 +2431,59 @@ class ChatActivity : } private fun setupWebsocket() { - if (conversationUser == null) { + if (currentConversation == null || conversationUser == null) { return } - webSocketInstance = WebSocketConnectionHelper.getWebSocketInstanceForUser(conversationUser!!) + + if (currentConversation!!.remoteServer != null) { + val apiVersion = ApiUtils.getSignalingApiVersion(conversationUser!!, intArrayOf(ApiUtils.API_V3, 2, 1)) + ncApi!!.getSignalingSettings( + credentials, + ApiUtils.getUrlForSignalingSettings(apiVersion, conversationUser!!.baseUrl, roomToken!!) + ).blockingSubscribe(object : Observer { + override fun onSubscribe(d: Disposable) { + // unused atm + } + + override fun onNext(signalingSettingsOverall: SignalingSettingsOverall) { + if (signalingSettingsOverall.ocs!!.settings!!.externalSignalingServer == null) { + return + } + + externalSignalingServer = ExternalSignalingServer() + externalSignalingServer!!.externalSignalingServer = signalingSettingsOverall.ocs!!.settings!! + .externalSignalingServer + externalSignalingServer!!.externalSignalingTicket = signalingSettingsOverall.ocs!!.settings!! + .externalSignalingTicket + externalSignalingServer!!.federation = signalingSettingsOverall.ocs!!.settings!!.federation + + webSocketInstance = WebSocketConnectionHelper.getExternalSignalingInstanceForServer( + externalSignalingServer!!.externalSignalingServer, + conversationUser, + externalSignalingServer!!.externalSignalingTicket, + TextUtils.isEmpty(credentials) + ) + } + + override fun onError(e: Throwable) { + Log.e(CallActivity.TAG, e.message, e) + } + + override fun onComplete() { + // unused atm + } + }) + } else { + webSocketInstance = WebSocketConnectionHelper.getWebSocketInstanceForUser(conversationUser!!) + } if (webSocketInstance == null) { Log.d(TAG, "webSocketInstance not set up. This should only happen when not using the HPB") } signalingMessageSender = webSocketInstance?.signalingMessageSender + webSocketInstance?.getSignalingMessageReceiver()?.addListener(localParticipantMessageListener) + webSocketInstance?.getSignalingMessageReceiver()?.addListener(conversationMessageListener) } private fun processCallStartedMessages(chatMessageList: List) { @@ -3190,7 +3241,7 @@ class ChatActivity : val lon = data["longitude"]!! metaData = "{\"type\":\"geo-location\",\"id\":\"geo:$lat,$lon\",\"latitude\":\"$lat\"," + - "\"longitude\":\"$lon\",\"name\":\"$name\"}" + "\"longitude\":\"$lon\",\"name\":\"$name\"}" } when (type) { diff --git a/app/src/main/java/com/nextcloud/talk/models/ExternalSignalingServer.kt b/app/src/main/java/com/nextcloud/talk/models/ExternalSignalingServer.kt index 9ed3f7b4de9..b561f9bde60 100644 --- a/app/src/main/java/com/nextcloud/talk/models/ExternalSignalingServer.kt +++ b/app/src/main/java/com/nextcloud/talk/models/ExternalSignalingServer.kt @@ -10,6 +10,7 @@ package com.nextcloud.talk.models import android.os.Parcelable import com.bluelinelabs.logansquare.annotation.JsonField import com.bluelinelabs.logansquare.annotation.JsonObject +import com.nextcloud.talk.models.json.signaling.settings.FederationSettings import kotlinx.parcelize.Parcelize @Parcelize @@ -18,8 +19,10 @@ data class ExternalSignalingServer( @JsonField(name = ["externalSignalingServer"]) var externalSignalingServer: String? = null, @JsonField(name = ["externalSignalingTicket"]) - var externalSignalingTicket: String? = null + var externalSignalingTicket: String? = null, + @JsonField(name = ["federation"]) + var federation: FederationSettings? = null ) : Parcelable { // This constructor is added to work with the 'com.bluelinelabs.logansquare.annotation.JsonObject' - constructor() : this(null, null) + constructor() : this(null, null, null) } diff --git a/app/src/main/java/com/nextcloud/talk/models/json/signaling/settings/FederationHelloAuthParams.kt b/app/src/main/java/com/nextcloud/talk/models/json/signaling/settings/FederationHelloAuthParams.kt new file mode 100644 index 00000000000..290a8f8ede8 --- /dev/null +++ b/app/src/main/java/com/nextcloud/talk/models/json/signaling/settings/FederationHelloAuthParams.kt @@ -0,0 +1,24 @@ +/* + * Nextcloud Talk - Android Client + * + * SPDX-FileCopyrightText: 2024 Daniel Calviño Sánchez + * SPDX-License-Identifier: GPL-3.0-or-later + */ +package com.nextcloud.talk.models.json.signaling.settings + +import android.os.Parcelable +import com.bluelinelabs.logansquare.annotation.JsonField +import com.bluelinelabs.logansquare.annotation.JsonObject +import kotlinx.parcelize.Parcelize +import kotlinx.serialization.Serializable + +@Parcelize +@JsonObject +@Serializable +data class FederationHelloAuthParams( + @JsonField(name = ["token"]) + var token: String? = null +) : Parcelable { + // This constructor is added to work with the 'com.bluelinelabs.logansquare.annotation.JsonObject' + constructor() : this(null) +} diff --git a/app/src/main/java/com/nextcloud/talk/models/json/signaling/settings/FederationSettings.kt b/app/src/main/java/com/nextcloud/talk/models/json/signaling/settings/FederationSettings.kt new file mode 100644 index 00000000000..27c29364c44 --- /dev/null +++ b/app/src/main/java/com/nextcloud/talk/models/json/signaling/settings/FederationSettings.kt @@ -0,0 +1,30 @@ +/* + * Nextcloud Talk - Android Client + * + * SPDX-FileCopyrightText: 2024 Daniel Calviño Sánchez + * SPDX-License-Identifier: GPL-3.0-or-later + */ +package com.nextcloud.talk.models.json.signaling.settings + +import android.os.Parcelable +import com.bluelinelabs.logansquare.annotation.JsonField +import com.bluelinelabs.logansquare.annotation.JsonObject +import kotlinx.parcelize.Parcelize +import kotlinx.serialization.Serializable + +@Parcelize +@JsonObject +@Serializable +data class FederationSettings( + @JsonField(name = ["server"]) + var server: String? = null, + @JsonField(name = ["nextcloudServer"]) + var nextcloudServer: String? = null, + @JsonField(name = ["helloAuthParams"]) + var helloAuthParams: FederationHelloAuthParams? = null, + @JsonField(name = ["roomId"]) + var roomId: String? = null +) : Parcelable { + // This constructor is added to work with the 'com.bluelinelabs.logansquare.annotation.JsonObject' + constructor() : this(null, null, null, null) +} diff --git a/app/src/main/java/com/nextcloud/talk/models/json/signaling/settings/SignalingSettings.kt b/app/src/main/java/com/nextcloud/talk/models/json/signaling/settings/SignalingSettings.kt index 55d50664efe..89dc837a5b4 100644 --- a/app/src/main/java/com/nextcloud/talk/models/json/signaling/settings/SignalingSettings.kt +++ b/app/src/main/java/com/nextcloud/talk/models/json/signaling/settings/SignalingSettings.kt @@ -24,8 +24,10 @@ data class SignalingSettings( @JsonField(name = ["server"]) var externalSignalingServer: String? = null, @JsonField(name = ["ticket"]) - var externalSignalingTicket: String? = null + var externalSignalingTicket: String? = null, + @JsonField(name = ["federation"]) + var federation: FederationSettings? = null ) : Parcelable { // This constructor is added to work with the 'com.bluelinelabs.logansquare.annotation.JsonObject' - constructor() : this(null, null, null) + constructor() : this(null, null, null, null) } diff --git a/app/src/main/java/com/nextcloud/talk/models/json/websocket/RoomFederationWebSocketMessage.kt b/app/src/main/java/com/nextcloud/talk/models/json/websocket/RoomFederationWebSocketMessage.kt new file mode 100644 index 00000000000..e5352bd45ee --- /dev/null +++ b/app/src/main/java/com/nextcloud/talk/models/json/websocket/RoomFederationWebSocketMessage.kt @@ -0,0 +1,28 @@ +/* + * Nextcloud Talk - Android Client + * + * SPDX-FileCopyrightText: 2024 Daniel Calviño Sánchez + * SPDX-License-Identifier: GPL-3.0-or-later + */ +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.parcelize.Parcelize + +@Parcelize +@JsonObject +class RoomFederationWebSocketMessage( + @JsonField(name = ["signaling"]) + var signaling: String? = null, + @JsonField(name = ["url"]) + var url: String? = null, + @JsonField(name = ["roomid"]) + var roomid: String? = null, + @JsonField(name = ["token"]) + var token: String? = null +) : Parcelable { + // This constructor is added to work with the 'com.bluelinelabs.logansquare.annotation.JsonObject' + constructor() : this(null, null, null, null) +} diff --git a/app/src/main/java/com/nextcloud/talk/models/json/websocket/RoomWebSocketMessage.kt b/app/src/main/java/com/nextcloud/talk/models/json/websocket/RoomWebSocketMessage.kt index a836a3b8ef8..ce3af7fa6df 100644 --- a/app/src/main/java/com/nextcloud/talk/models/json/websocket/RoomWebSocketMessage.kt +++ b/app/src/main/java/com/nextcloud/talk/models/json/websocket/RoomWebSocketMessage.kt @@ -20,8 +20,10 @@ class RoomWebSocketMessage( @JsonField(name = ["sessionid"]) var sessionId: String? = null, @JsonField(name = ["properties"]) - var roomPropertiesWebSocketMessage: RoomPropertiesWebSocketMessage? = null + var roomPropertiesWebSocketMessage: RoomPropertiesWebSocketMessage? = null, + @JsonField(name = ["federation"]) + var roomFederationWebSocketMessage: RoomFederationWebSocketMessage? = null ) : Parcelable { // This constructor is added to work with the 'com.bluelinelabs.logansquare.annotation.JsonObject' - constructor() : this(null, null, null) + constructor() : this(null, null, null, null) } 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 4abfe757be5..90f658f9284 100644 --- a/app/src/main/java/com/nextcloud/talk/signaling/SignalingMessageReceiver.java +++ b/app/src/main/java/com/nextcloud/talk/signaling/SignalingMessageReceiver.java @@ -6,6 +6,7 @@ */ package com.nextcloud.talk.signaling; +import com.nextcloud.talk.models.json.converters.EnumActorTypeConverter; import com.nextcloud.talk.models.json.converters.EnumParticipantTypeConverter; import com.nextcloud.talk.models.json.participants.Participant; import com.nextcloud.talk.models.json.signaling.NCIceCandidate; @@ -38,6 +39,8 @@ */ public abstract class SignalingMessageReceiver { + private final EnumActorTypeConverter enumActorTypeConverter = new EnumActorTypeConverter(); + private final ParticipantListMessageNotifier participantListMessageNotifier = new ParticipantListMessageNotifier(); private final LocalParticipantMessageNotifier localParticipantMessageNotifier = new LocalParticipantMessageNotifier(); @@ -398,6 +401,8 @@ private void processParticipantsUpdate(Map updateMap) { // "nextcloudSessionId": #STRING#, // Optional // "internal": #BOOLEAN#, // Optional // "participantPermissions": #INTEGER#, // Talk >= 13 + // "actorType": #STRING#, // Talk >= 20 + // "actorId": #STRING#, // Talk >= 20 // }, // ... // ], @@ -447,6 +452,8 @@ protected void processUsersInRoom(List> users) { // "sessionId": #STRING#, // "userId": #STRING#, // Always included, although it can be empty // "participantPermissions": #INTEGER#, // Talk >= 13 + // "actorType": #STRING#, // Talk >= 20 + // "actorId": #STRING#, // Talk >= 20 // }, // ... // ], @@ -492,6 +499,14 @@ private Participant getParticipantFromMessageMap(Map participant participant.setInternal(Boolean.TRUE); } + if (participantMap.get("actorType") != null && !participantMap.get("actorType").toString().isEmpty()) { + participant.setActorType(enumActorTypeConverter.getFromString(participantMap.get("actorType").toString())); + } + + if (participantMap.get("actorId") != null && !participantMap.get("actorId").toString().isEmpty()) { + participant.setActorId(participantMap.get("actorId").toString()); + } + // Only in external signaling messages if (participantMap.get("participantType") != null) { int participantTypeInt = Integer.parseInt(participantMap.get("participantType").toString()); diff --git a/app/src/main/java/com/nextcloud/talk/utils/ApiUtils.kt b/app/src/main/java/com/nextcloud/talk/utils/ApiUtils.kt index c0168c41354..fb4e6a770cf 100644 --- a/app/src/main/java/com/nextcloud/talk/utils/ApiUtils.kt +++ b/app/src/main/java/com/nextcloud/talk/utils/ApiUtils.kt @@ -283,6 +283,10 @@ object ApiUtils { return getUrlForSignaling(version, baseUrl) + "/settings" } + fun getUrlForSignalingSettings(version: Int, baseUrl: String?, token: String): String { + return getUrlForSignaling(version, baseUrl) + "/settings?token=" + token + } + fun getUrlForSignaling(version: Int, baseUrl: String?, token: String): String { return getUrlForSignaling(version, baseUrl) + "/" + token } diff --git a/app/src/main/java/com/nextcloud/talk/webrtc/WebSocketConnectionHelper.java b/app/src/main/java/com/nextcloud/talk/webrtc/WebSocketConnectionHelper.java index bd32ff6b881..57452c0943a 100644 --- a/app/src/main/java/com/nextcloud/talk/webrtc/WebSocketConnectionHelper.java +++ b/app/src/main/java/com/nextcloud/talk/webrtc/WebSocketConnectionHelper.java @@ -12,6 +12,7 @@ import com.nextcloud.talk.application.NextcloudTalkApplication; import com.nextcloud.talk.data.user.model.User; import com.nextcloud.talk.models.json.signaling.NCSignalingMessage; +import com.nextcloud.talk.models.json.signaling.settings.FederationSettings; import com.nextcloud.talk.models.json.websocket.ActorWebSocketMessage; import com.nextcloud.talk.models.json.websocket.AuthParametersWebSocketMessage; import com.nextcloud.talk.models.json.websocket.AuthWebSocketMessage; @@ -19,6 +20,7 @@ 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.RoomFederationWebSocketMessage; import com.nextcloud.talk.models.json.websocket.RoomOverallWebSocketMessage; import com.nextcloud.talk.models.json.websocket.RoomWebSocketMessage; import com.nextcloud.talk.utils.ApiUtils; @@ -128,12 +130,25 @@ HelloOverallWebSocketMessage getAssembledHelloModelForResume(String resumeId) { return helloOverallWebSocketMessage; } - RoomOverallWebSocketMessage getAssembledJoinOrLeaveRoomModel(String roomId, String sessionId) { + RoomOverallWebSocketMessage getAssembledJoinOrLeaveRoomModel(String roomId, String sessionId, + FederationSettings federation) { RoomOverallWebSocketMessage roomOverallWebSocketMessage = new RoomOverallWebSocketMessage(); roomOverallWebSocketMessage.setType("room"); RoomWebSocketMessage roomWebSocketMessage = new RoomWebSocketMessage(); roomWebSocketMessage.setRoomId(roomId); roomWebSocketMessage.setSessionId(sessionId); + if (federation != null) { + String federationAuthToken = null; + if (federation.getHelloAuthParams() != null) { + federationAuthToken = federation.getHelloAuthParams().getToken(); + } + RoomFederationWebSocketMessage roomFederationWebSocketMessage = new RoomFederationWebSocketMessage(); + roomFederationWebSocketMessage.setSignaling(federation.getServer()); + roomFederationWebSocketMessage.setUrl(federation.getNextcloudServer() + "/ocs/v2.php/apps/spreed/api/v3/signaling/backend"); + roomFederationWebSocketMessage.setRoomid(federation.getRoomId()); + roomFederationWebSocketMessage.setToken(federationAuthToken); + roomWebSocketMessage.setRoomFederationWebSocketMessage(roomFederationWebSocketMessage); + } roomOverallWebSocketMessage.setRoomWebSocketMessage(roomWebSocketMessage); return roomOverallWebSocketMessage; } diff --git a/app/src/main/java/com/nextcloud/talk/webrtc/WebSocketInstance.kt b/app/src/main/java/com/nextcloud/talk/webrtc/WebSocketInstance.kt index 451d4f5eab4..10f338b1a2a 100644 --- a/app/src/main/java/com/nextcloud/talk/webrtc/WebSocketInstance.kt +++ b/app/src/main/java/com/nextcloud/talk/webrtc/WebSocketInstance.kt @@ -20,6 +20,7 @@ import com.nextcloud.talk.events.WebSocketCommunicationEvent import com.nextcloud.talk.models.json.participants.Participant import com.nextcloud.talk.models.json.participants.Participant.ActorType import com.nextcloud.talk.models.json.signaling.NCSignalingMessage +import com.nextcloud.talk.models.json.signaling.settings.FederationSettings import com.nextcloud.talk.models.json.websocket.BaseWebSocketMessage import com.nextcloud.talk.models.json.websocket.ByeWebSocketMessage import com.nextcloud.talk.models.json.websocket.CallOverallWebSocketMessage @@ -75,6 +76,7 @@ class WebSocketInstance internal constructor( private val connectionUrl: String private var currentRoomToken: String? = null private var currentNormalBackendSession: String? = null + private var currentFederation: FederationSettings? = null private var reconnecting = false private val usersHashMap: HashMap private var messagesQueue: MutableList = ArrayList() @@ -367,24 +369,36 @@ class WebSocketInstance internal constructor( return hasMCU } - fun joinRoomWithRoomTokenAndSession(roomToken: String, normalBackendSession: String?) { + @Suppress("Detekt.ComplexMethod") + fun joinRoomWithRoomTokenAndSession( + roomToken: String, + normalBackendSession: String?, + federation: FederationSettings? = null + ) { Log.d(TAG, "joinRoomWithRoomTokenAndSession") Log.d(TAG, " roomToken: $roomToken") Log.d(TAG, " session: $normalBackendSession") try { val message = LoganSquare.serialize( - webSocketConnectionHelper.getAssembledJoinOrLeaveRoomModel(roomToken, normalBackendSession) + webSocketConnectionHelper.getAssembledJoinOrLeaveRoomModel(roomToken, normalBackendSession, federation) ) if (roomToken == "") { Log.d(TAG, "sending 'leave room' via websocket") currentNormalBackendSession = "" + currentFederation = null sendMessage(message) - } else if (roomToken == currentRoomToken && normalBackendSession == currentNormalBackendSession) { + } else if ( + roomToken == currentRoomToken && + normalBackendSession == currentNormalBackendSession && + federation?.roomId == currentFederation?.roomId && + federation?.nextcloudServer == federation?.nextcloudServer + ) { Log.d(TAG, "roomToken & session are unchanged. Joining locally without to send websocket message") sendRoomJoinedEvent() } else { Log.d(TAG, "Sending join room message via websocket") currentNormalBackendSession = normalBackendSession + currentFederation = federation sendMessage(message) } } catch (e: IOException) { diff --git a/app/src/test/java/com/nextcloud/talk/call/CallParticipantListExternalSignalingTest.java b/app/src/test/java/com/nextcloud/talk/call/CallParticipantListExternalSignalingTest.java index 51a1d20c97d..02b6dc7aa71 100644 --- a/app/src/test/java/com/nextcloud/talk/call/CallParticipantListExternalSignalingTest.java +++ b/app/src/test/java/com/nextcloud/talk/call/CallParticipantListExternalSignalingTest.java @@ -71,6 +71,8 @@ private Participant newUser(long inCall, long lastPing, String sessionId, Partic participant.setSessionId(sessionId); participant.setType(type); participant.setUserId(userId); + participant.setActorType(Participant.ActorType.USERS); + participant.setActorId(userId); return participant; } @@ -81,6 +83,8 @@ private Participant newGuest(long inCall, long lastPing, String sessionId, Parti participant.setLastPing(lastPing); participant.setSessionId(sessionId); participant.setType(type); + participant.setActorType(Participant.ActorType.GUESTS); + participant.setActorId("sha1-" + sessionId); return participant; } diff --git a/app/src/test/java/com/nextcloud/talk/call/CallParticipantListInternalSignalingTest.java b/app/src/test/java/com/nextcloud/talk/call/CallParticipantListInternalSignalingTest.java index 9ec9ed776ae..a93325fce95 100644 --- a/app/src/test/java/com/nextcloud/talk/call/CallParticipantListInternalSignalingTest.java +++ b/app/src/test/java/com/nextcloud/talk/call/CallParticipantListInternalSignalingTest.java @@ -61,6 +61,8 @@ private Participant newUser(long inCall, long lastPing, String sessionId, String participant.setLastPing(lastPing); participant.setSessionId(sessionId); participant.setUserId(userId); + participant.setActorType(Participant.ActorType.USERS); + participant.setActorId(userId); return participant; } @@ -70,6 +72,8 @@ private Participant newGuest(long inCall, long lastPing, String sessionId) { participant.setInCall(inCall); participant.setLastPing(lastPing); participant.setSessionId(sessionId); + participant.setActorType(Participant.ActorType.GUESTS); + participant.setActorId("sha1-" + sessionId); return participant; } diff --git a/app/src/test/java/com/nextcloud/talk/signaling/SignalingMessageReceiverParticipantListTest.java b/app/src/test/java/com/nextcloud/talk/signaling/SignalingMessageReceiverParticipantListTest.java index 5ac43b06d79..fa1e14220dc 100644 --- a/app/src/test/java/com/nextcloud/talk/signaling/SignalingMessageReceiverParticipantListTest.java +++ b/app/src/test/java/com/nextcloud/talk/signaling/SignalingMessageReceiverParticipantListTest.java @@ -58,10 +58,12 @@ public void testInternalSignalingParticipantListMessageUsersInRoom() { 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. + // If any of the following properties 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 they are included only in one of the participants. user1.put("participantPermissions", 42); + user1.put("actorType", "federated_users"); + user1.put("actorId", "theActorId"); users.add(user1); Map user2 = new HashMap<>(); user2.put("inCall", 0); @@ -78,6 +80,8 @@ public void testInternalSignalingParticipantListMessageUsersInRoom() { expectedParticipant1.setLastPing(4815); expectedParticipant1.setSessionId("theSessionId1"); expectedParticipant1.setUserId("theUserId"); + expectedParticipant1.setActorType(Participant.ActorType.FEDERATED); + expectedParticipant1.setActorId("theActorId"); expectedParticipantList.add(expectedParticipant1); Participant expectedParticipant2 = new Participant(); @@ -266,11 +270,13 @@ public void testExternalSignalingParticipantListMessageParticipantsUpdate() { 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. + // If any of the following properties 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 they are included only in one of the participants. user1.put("nextcloudSessionId", "theNextcloudSessionId"); user1.put("participantPermissions", 42); + user1.put("actorType", "federated_users"); + user1.put("actorId", "theActorId"); users.add(user1); Map user2 = new HashMap<>(); user2.put("inCall", 0); @@ -289,6 +295,8 @@ public void testExternalSignalingParticipantListMessageParticipantsUpdate() { expectedParticipant1.setSessionId("theSessionId1"); expectedParticipant1.setType(Participant.ParticipantType.USER); expectedParticipant1.setUserId("theUserId"); + expectedParticipant1.setActorType(Participant.ActorType.FEDERATED); + expectedParticipant1.setActorId("theActorId"); expectedParticipantList.add(expectedParticipant1); Participant expectedParticipant2 = new Participant();