Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature/fga/fix some voip issues #2830

Merged
merged 11 commits into from
Feb 17, 2021
Merged
1 change: 1 addition & 0 deletions .idea/dictionaries/bmarty.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,11 @@

package org.matrix.android.sdk.api.session.call

import org.matrix.android.sdk.api.MatrixCallback
import org.matrix.android.sdk.api.util.Cancelable

interface CallSignalingService {

fun getTurnServer(callback: MatrixCallback<TurnServerResponse>): Cancelable
suspend fun getTurnServer(): TurnServerResponse

fun getPSTNProtocolChecker(): PSTNProtocolChecker

/**
* Create an outgoing call
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
/*
* Copyright (c) 2021 The Matrix.org Foundation C.I.C.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package org.matrix.android.sdk.api.session.call

import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import org.matrix.android.sdk.api.extensions.tryOrNull
import org.matrix.android.sdk.api.session.room.model.thirdparty.ThirdPartyProtocol
import org.matrix.android.sdk.internal.session.SessionScope
import org.matrix.android.sdk.internal.session.thirdparty.GetThirdPartyProtocolsTask
import org.matrix.android.sdk.internal.task.TaskExecutor
import timber.log.Timber
import java.util.concurrent.atomic.AtomicBoolean
import javax.inject.Inject

private const val PSTN_VECTOR_KEY = "im.vector.protocol.pstn"
private const val PSTN_MATRIX_KEY = "m.protocol.pstn"

/**
* This class is responsible for checking if the HS support the PSTN protocol.
* As long as the request succeed, it'll check only once by session.
*/
@SessionScope
class PSTNProtocolChecker @Inject internal constructor(private val taskExecutor: TaskExecutor,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

no interface to hide implementation?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes I can, but I try to remove interfaces when it's not really necessary as we already have a lots... Fields are private and constructor is internal

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, I'd seen the internal constructor. OK, thanks!

private val getThirdPartyProtocolsTask: GetThirdPartyProtocolsTask) {

interface Listener {
fun onPSTNSupportUpdated()
}

private var alreadyChecked = AtomicBoolean(false)

private val pstnSupportListeners = mutableListOf<Listener>()

fun addListener(listener: Listener) {
pstnSupportListeners.add(listener)
}

fun removeListener(listener: Listener) {
pstnSupportListeners.remove(listener)
}

var supportedPSTNProtocol: String? = null
private set

fun checkForPSTNSupportIfNeeded() {
if (alreadyChecked.get()) return
taskExecutor.executorScope.checkForPSTNSupport()
}

private fun CoroutineScope.checkForPSTNSupport() = launch {
try {
supportedPSTNProtocol = getSupportedPSTN(3)
alreadyChecked.set(true)
if (supportedPSTNProtocol != null) {
pstnSupportListeners.forEach {
tryOrNull { it.onPSTNSupportUpdated() }
}
}
} catch (failure: Throwable) {
Timber.v("Fail to get supported PSTN, will check again next time.")
}
}

private suspend fun getSupportedPSTN(maxTries: Int): String? {
val thirdPartyProtocols: Map<String, ThirdPartyProtocol> = try {
getThirdPartyProtocolsTask.execute(Unit)
} catch (failure: Throwable) {
if (maxTries == 1) {
throw failure
} else {
// Wait for 10s before trying again
delay(10_000L)
return getSupportedPSTN(maxTries - 1)
}
}
return when {
thirdPartyProtocols.containsKey(PSTN_VECTOR_KEY) -> PSTN_VECTOR_KEY
thirdPartyProtocols.containsKey(PSTN_MATRIX_KEY) -> PSTN_MATRIX_KEY
else -> null
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,16 +16,12 @@

package org.matrix.android.sdk.internal.session.call

import kotlinx.coroutines.Dispatchers
import org.matrix.android.sdk.api.MatrixCallback
import org.matrix.android.sdk.api.session.call.CallListener
import org.matrix.android.sdk.api.session.call.CallSignalingService
import org.matrix.android.sdk.api.session.call.MxCall
import org.matrix.android.sdk.api.session.call.PSTNProtocolChecker
import org.matrix.android.sdk.api.session.call.TurnServerResponse
import org.matrix.android.sdk.api.util.Cancelable
import org.matrix.android.sdk.internal.session.SessionScope
import org.matrix.android.sdk.internal.task.TaskExecutor
import org.matrix.android.sdk.internal.task.launchToCallback
import timber.log.Timber
import javax.inject.Inject

Expand All @@ -34,14 +30,16 @@ internal class DefaultCallSignalingService @Inject constructor(
private val callSignalingHandler: CallSignalingHandler,
private val mxCallFactory: MxCallFactory,
private val activeCallHandler: ActiveCallHandler,
private val taskExecutor: TaskExecutor,
private val turnServerDataSource: TurnServerDataSource
private val turnServerDataSource: TurnServerDataSource,
private val pstnProtocolChecker: PSTNProtocolChecker
) : CallSignalingService {

override fun getTurnServer(callback: MatrixCallback<TurnServerResponse>): Cancelable {
return taskExecutor.executorScope.launchToCallback(Dispatchers.Default, callback) {
turnServerDataSource.getTurnServer()
}
override suspend fun getTurnServer(): TurnServerResponse {
return turnServerDataSource.getTurnServer()
}

override fun getPSTNProtocolChecker(): PSTNProtocolChecker {
return pstnProtocolChecker
}

override fun createOutgoingCall(roomId: String, otherUserId: String, isVideoCall: Boolean): MxCall {
Expand Down
34 changes: 34 additions & 0 deletions vector/src/main/java/im/vector/app/core/epoxy/TimelineEmptyItem.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
/*
* Copyright 2019 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package im.vector.app.core.epoxy

import com.airbnb.epoxy.EpoxyAttribute
import com.airbnb.epoxy.EpoxyModelClass
import im.vector.app.R
import im.vector.app.features.home.room.detail.timeline.item.ItemWithEvents

@EpoxyModelClass(layout = R.layout.item_timeline_empty)
abstract class TimelineEmptyItem : VectorEpoxyModel<TimelineEmptyItem.Holder>(), ItemWithEvents {

@EpoxyAttribute lateinit var eventId: String

override fun getEventIds(): List<String> {
return listOf(eventId)
}

class Holder : VectorEpoxyHolder()
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@

package im.vector.app.features.call

import androidx.lifecycle.viewModelScope
import com.airbnb.mvrx.Fail
import com.airbnb.mvrx.Loading
import com.airbnb.mvrx.MvRxViewModelFactory
Expand All @@ -29,17 +30,16 @@ import im.vector.app.core.platform.VectorViewModel
import im.vector.app.features.call.audio.CallAudioManager
import im.vector.app.features.call.webrtc.WebRtcCall
import im.vector.app.features.call.webrtc.WebRtcCallManager
import org.matrix.android.sdk.api.MatrixCallback
import kotlinx.coroutines.Job
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import org.matrix.android.sdk.api.session.Session
import org.matrix.android.sdk.api.session.call.CallState
import org.matrix.android.sdk.api.session.call.MxCall
import org.matrix.android.sdk.api.session.call.MxPeerConnectionState
import org.matrix.android.sdk.api.session.call.TurnServerResponse
import org.matrix.android.sdk.api.session.room.model.call.supportCallTransfer
import org.matrix.android.sdk.api.util.MatrixItem
import org.matrix.android.sdk.api.util.toMatrixItem
import java.util.Timer
import java.util.TimerTask

class VectorCallViewModel @AssistedInject constructor(
@Assisted initialState: VectorCallViewState,
Expand All @@ -50,7 +50,7 @@ class VectorCallViewModel @AssistedInject constructor(

private var call: WebRtcCall? = null

private var connectionTimeoutTimer: Timer? = null
private var connectionTimeoutJob: Job? = null
private var hasBeenConnectedOnce = false

private val callListener = object : WebRtcCall.Listener {
Expand Down Expand Up @@ -92,26 +92,20 @@ class VectorCallViewModel @AssistedInject constructor(
val callState = call.state
if (callState is CallState.Connected && callState.iceConnectionState == MxPeerConnectionState.CONNECTED) {
hasBeenConnectedOnce = true
connectionTimeoutTimer?.cancel()
connectionTimeoutTimer = null
connectionTimeoutJob?.cancel()
connectionTimeoutJob = null
} else {
// do we reset as long as it's moving?
connectionTimeoutTimer?.cancel()
connectionTimeoutJob?.cancel()
if (hasBeenConnectedOnce) {
connectionTimeoutTimer = Timer().apply {
schedule(object : TimerTask() {
override fun run() {
session.callSignalingService().getTurnServer(object : MatrixCallback<TurnServerResponse> {
override fun onFailure(failure: Throwable) {
_viewEvents.post(VectorCallViewEvents.ConnectionTimeout(null))
}

override fun onSuccess(data: TurnServerResponse) {
_viewEvents.post(VectorCallViewEvents.ConnectionTimeout(data))
}
})
}
}, 30_000)
connectionTimeoutJob = viewModelScope.launch {
delay(30_000)
try {
val turn = session.callSignalingService().getTurnServer()
_viewEvents.post(VectorCallViewEvents.ConnectionTimeout(turn))
} catch (failure: Throwable) {
_viewEvents.post(VectorCallViewEvents.ConnectionTimeout(null))
}
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,20 +22,22 @@ import org.matrix.android.sdk.api.extensions.tryOrNull
import org.matrix.android.sdk.api.session.Session
import javax.inject.Inject

class DialPadLookup @Inject constructor(val session: Session,
val directRoomHelper: DirectRoomHelper,
val callManager: WebRtcCallManager
class DialPadLookup @Inject constructor(
private val session: Session,
private val directRoomHelper: DirectRoomHelper,
private val callManager: WebRtcCallManager
) {

class Failure : Throwable()

data class Result(val userId: String, val roomId: String)

suspend fun lookupPhoneNumber(phoneNumber: String): Result {
val supportedProtocolKey = callManager.supportedPSTNProtocol ?: throw Failure()
val thirdPartyUser = tryOrNull {
session.thirdPartyService().getThirdPartyUser(supportedProtocolKey, fields = mapOf(
"m.id.phone" to phoneNumber
)).firstOrNull()
session.thirdPartyService().getThirdPartyUser(
protocol = supportedProtocolKey,
fields = mapOf("m.id.phone" to phoneNumber)
).firstOrNull()
} ?: throw Failure()

val roomId = directRoomHelper.ensureDMExists(thirdPartyUser.userId)
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,6 @@ import org.matrix.android.sdk.api.session.room.model.call.CallHangupContent
import org.matrix.android.sdk.api.session.room.model.call.CallInviteContent
import org.matrix.android.sdk.api.session.room.model.call.CallNegotiateContent
import org.matrix.android.sdk.api.session.room.model.call.SdpType
import org.matrix.android.sdk.internal.util.awaitCallback
import org.threeten.bp.Duration
import org.webrtc.AudioSource
import org.webrtc.AudioTrack
Expand Down Expand Up @@ -420,9 +419,7 @@ class WebRtcCall(val mxCall: MxCall,

private suspend fun getTurnServer(): TurnServerResponse? {
return tryOrNull {
awaitCallback {
sessionProvider.get()?.callSignalingService()?.getTurnServer(it)
}
sessionProvider.get()?.callSignalingService()?.getTurnServer()
}
}

Expand Down
Loading