mirror of
https://github.com/element-hq/element-android
synced 2024-11-28 05:31:21 +03:00
VoIP: extract the PSTNProtocoltChecker to the SDK
This commit is contained in:
parent
5e3e5d2648
commit
3170d4428c
8 changed files with 207 additions and 211 deletions
|
@ -16,12 +16,11 @@
|
||||||
|
|
||||||
package org.matrix.android.sdk.api.session.call
|
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 {
|
interface CallSignalingService {
|
||||||
|
|
||||||
fun getTurnServer(callback: MatrixCallback<TurnServerResponse>): Cancelable
|
suspend fun getTurnServer(): TurnServerResponse
|
||||||
|
|
||||||
|
fun getPSTNProtocolChecker(): PSTNProtocolChecker
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create an outgoing call
|
* Create an outgoing call
|
||||||
|
|
|
@ -0,0 +1,98 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2021 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 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,
|
||||||
|
private val getThirdPartyProtocolsTask: GetThirdPartyProtocolsTask) {
|
||||||
|
|
||||||
|
interface Listener {
|
||||||
|
fun onPSTNSupportUpdated()
|
||||||
|
}
|
||||||
|
|
||||||
|
private var alreadyChecked = AtomicBoolean(false)
|
||||||
|
|
||||||
|
private val pstnSupportListeners = emptyList<Listener>().toMutableList()
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -16,16 +16,12 @@
|
||||||
|
|
||||||
package org.matrix.android.sdk.internal.session.call
|
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.CallListener
|
||||||
import org.matrix.android.sdk.api.session.call.CallSignalingService
|
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.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.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.session.SessionScope
|
||||||
import org.matrix.android.sdk.internal.task.TaskExecutor
|
|
||||||
import org.matrix.android.sdk.internal.task.launchToCallback
|
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
@ -34,14 +30,16 @@ internal class DefaultCallSignalingService @Inject constructor(
|
||||||
private val callSignalingHandler: CallSignalingHandler,
|
private val callSignalingHandler: CallSignalingHandler,
|
||||||
private val mxCallFactory: MxCallFactory,
|
private val mxCallFactory: MxCallFactory,
|
||||||
private val activeCallHandler: ActiveCallHandler,
|
private val activeCallHandler: ActiveCallHandler,
|
||||||
private val taskExecutor: TaskExecutor,
|
private val turnServerDataSource: TurnServerDataSource,
|
||||||
private val turnServerDataSource: TurnServerDataSource
|
private val pstnProtocolChecker: PSTNProtocolChecker
|
||||||
) : CallSignalingService {
|
) : CallSignalingService {
|
||||||
|
|
||||||
override fun getTurnServer(callback: MatrixCallback<TurnServerResponse>): Cancelable {
|
override suspend fun getTurnServer(): TurnServerResponse {
|
||||||
return taskExecutor.executorScope.launchToCallback(Dispatchers.Default, callback) {
|
return turnServerDataSource.getTurnServer()
|
||||||
turnServerDataSource.getTurnServer()
|
}
|
||||||
}
|
|
||||||
|
override fun getPSTNProtocolChecker(): PSTNProtocolChecker {
|
||||||
|
return pstnProtocolChecker
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun createOutgoingCall(roomId: String, otherUserId: String, isVideoCall: Boolean): MxCall {
|
override fun createOutgoingCall(roomId: String, otherUserId: String, isVideoCall: Boolean): MxCall {
|
||||||
|
|
|
@ -16,6 +16,7 @@
|
||||||
|
|
||||||
package im.vector.app.features.call
|
package im.vector.app.features.call
|
||||||
|
|
||||||
|
import androidx.lifecycle.viewModelScope
|
||||||
import com.airbnb.mvrx.Fail
|
import com.airbnb.mvrx.Fail
|
||||||
import com.airbnb.mvrx.Loading
|
import com.airbnb.mvrx.Loading
|
||||||
import com.airbnb.mvrx.MvRxViewModelFactory
|
import com.airbnb.mvrx.MvRxViewModelFactory
|
||||||
|
@ -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.audio.CallAudioManager
|
||||||
import im.vector.app.features.call.webrtc.WebRtcCall
|
import im.vector.app.features.call.webrtc.WebRtcCall
|
||||||
import im.vector.app.features.call.webrtc.WebRtcCallManager
|
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.Session
|
||||||
import org.matrix.android.sdk.api.session.call.CallState
|
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.MxCall
|
||||||
import org.matrix.android.sdk.api.session.call.MxPeerConnectionState
|
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.session.room.model.call.supportCallTransfer
|
||||||
import org.matrix.android.sdk.api.util.MatrixItem
|
import org.matrix.android.sdk.api.util.MatrixItem
|
||||||
import org.matrix.android.sdk.api.util.toMatrixItem
|
import org.matrix.android.sdk.api.util.toMatrixItem
|
||||||
import java.util.Timer
|
|
||||||
import java.util.TimerTask
|
|
||||||
|
|
||||||
class VectorCallViewModel @AssistedInject constructor(
|
class VectorCallViewModel @AssistedInject constructor(
|
||||||
@Assisted initialState: VectorCallViewState,
|
@Assisted initialState: VectorCallViewState,
|
||||||
|
@ -50,7 +50,7 @@ class VectorCallViewModel @AssistedInject constructor(
|
||||||
|
|
||||||
private var call: WebRtcCall? = null
|
private var call: WebRtcCall? = null
|
||||||
|
|
||||||
private var connectionTimeoutTimer: Timer? = null
|
private var connectionTimeoutJob: Job? = null
|
||||||
private var hasBeenConnectedOnce = false
|
private var hasBeenConnectedOnce = false
|
||||||
|
|
||||||
private val callListener = object : WebRtcCall.Listener {
|
private val callListener = object : WebRtcCall.Listener {
|
||||||
|
@ -92,26 +92,20 @@ class VectorCallViewModel @AssistedInject constructor(
|
||||||
val callState = call.state
|
val callState = call.state
|
||||||
if (callState is CallState.Connected && callState.iceConnectionState == MxPeerConnectionState.CONNECTED) {
|
if (callState is CallState.Connected && callState.iceConnectionState == MxPeerConnectionState.CONNECTED) {
|
||||||
hasBeenConnectedOnce = true
|
hasBeenConnectedOnce = true
|
||||||
connectionTimeoutTimer?.cancel()
|
connectionTimeoutJob?.cancel()
|
||||||
connectionTimeoutTimer = null
|
connectionTimeoutJob = null
|
||||||
} else {
|
} else {
|
||||||
// do we reset as long as it's moving?
|
// do we reset as long as it's moving?
|
||||||
connectionTimeoutTimer?.cancel()
|
connectionTimeoutJob?.cancel()
|
||||||
if (hasBeenConnectedOnce) {
|
if (hasBeenConnectedOnce) {
|
||||||
connectionTimeoutTimer = Timer().apply {
|
connectionTimeoutJob = viewModelScope.launch {
|
||||||
schedule(object : TimerTask() {
|
delay(30_000)
|
||||||
override fun run() {
|
try {
|
||||||
session.callSignalingService().getTurnServer(object : MatrixCallback<TurnServerResponse> {
|
val turn = session.callSignalingService().getTurnServer()
|
||||||
override fun onFailure(failure: Throwable) {
|
_viewEvents.post(VectorCallViewEvents.ConnectionTimeout(turn))
|
||||||
_viewEvents.post(VectorCallViewEvents.ConnectionTimeout(null))
|
} catch (failure: Throwable) {
|
||||||
}
|
_viewEvents.post(VectorCallViewEvents.ConnectionTimeout(null))
|
||||||
|
}
|
||||||
override fun onSuccess(data: TurnServerResponse) {
|
|
||||||
_viewEvents.post(VectorCallViewEvents.ConnectionTimeout(data))
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}, 30_000)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,88 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright (c) 2021 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.features.call.webrtc
|
|
||||||
|
|
||||||
import kotlinx.coroutines.CoroutineScope
|
|
||||||
import kotlinx.coroutines.GlobalScope
|
|
||||||
import kotlinx.coroutines.delay
|
|
||||||
import kotlinx.coroutines.launch
|
|
||||||
import org.matrix.android.sdk.api.extensions.tryOrNull
|
|
||||||
import org.matrix.android.sdk.api.session.Session
|
|
||||||
import org.matrix.android.sdk.api.session.room.model.thirdparty.ThirdPartyProtocol
|
|
||||||
import timber.log.Timber
|
|
||||||
import java.util.concurrent.atomic.AtomicBoolean
|
|
||||||
import javax.inject.Inject
|
|
||||||
import javax.inject.Singleton
|
|
||||||
|
|
||||||
private const val PSTN_VECTOR_KEY = "im.vector.protocol.pstn"
|
|
||||||
private const val PSTN_MATRIX_KEY = "m.protocol.pstn"
|
|
||||||
|
|
||||||
@Singleton
|
|
||||||
class PSTNProtocolChecker @Inject constructor() {
|
|
||||||
|
|
||||||
private var alreadyChecked = AtomicBoolean(false)
|
|
||||||
|
|
||||||
private val pstnSupportListeners = emptyList<WebRtcCallManager.PSTNSupportListener>().toMutableList()
|
|
||||||
fun addPstnSupportListener(listener: WebRtcCallManager.PSTNSupportListener) {
|
|
||||||
pstnSupportListeners.add(listener)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun removePstnSupportListener(listener: WebRtcCallManager.PSTNSupportListener) {
|
|
||||||
pstnSupportListeners.remove(listener)
|
|
||||||
}
|
|
||||||
|
|
||||||
var supportedPSTNProtocol: String? = null
|
|
||||||
private set
|
|
||||||
|
|
||||||
fun checkForPSTNSupportIfNeeded(currentSession: Session?) {
|
|
||||||
if (alreadyChecked.get()) return
|
|
||||||
GlobalScope.checkForPSTNSupport(currentSession)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun CoroutineScope.checkForPSTNSupport(currentSession: Session?) = launch {
|
|
||||||
try {
|
|
||||||
supportedPSTNProtocol = currentSession?.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.")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
suspend fun Session.getSupportedPSTN(maxTries: Int): String? {
|
|
||||||
val thirdPartyProtocols: Map<String, ThirdPartyProtocol> = try {
|
|
||||||
thirdPartyService().getThirdPartyProtocols()
|
|
||||||
} 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
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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.CallInviteContent
|
||||||
import org.matrix.android.sdk.api.session.room.model.call.CallNegotiateContent
|
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.api.session.room.model.call.SdpType
|
||||||
import org.matrix.android.sdk.internal.util.awaitCallback
|
|
||||||
import org.threeten.bp.Duration
|
import org.threeten.bp.Duration
|
||||||
import org.webrtc.AudioSource
|
import org.webrtc.AudioSource
|
||||||
import org.webrtc.AudioTrack
|
import org.webrtc.AudioTrack
|
||||||
|
@ -420,9 +419,7 @@ class WebRtcCall(val mxCall: MxCall,
|
||||||
|
|
||||||
private suspend fun getTurnServer(): TurnServerResponse? {
|
private suspend fun getTurnServer(): TurnServerResponse? {
|
||||||
return tryOrNull {
|
return tryOrNull {
|
||||||
awaitCallback {
|
sessionProvider.get()?.callSignalingService()?.getTurnServer()
|
||||||
sessionProvider.get()?.callSignalingService()?.getTurnServer(it)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -32,6 +32,7 @@ import org.matrix.android.sdk.api.session.Session
|
||||||
import org.matrix.android.sdk.api.session.call.CallListener
|
import org.matrix.android.sdk.api.session.call.CallListener
|
||||||
import org.matrix.android.sdk.api.session.call.CallState
|
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.MxCall
|
||||||
|
import org.matrix.android.sdk.api.session.call.PSTNProtocolChecker
|
||||||
import org.matrix.android.sdk.api.session.room.model.call.CallAnswerContent
|
import org.matrix.android.sdk.api.session.room.model.call.CallAnswerContent
|
||||||
import org.matrix.android.sdk.api.session.room.model.call.CallCandidatesContent
|
import org.matrix.android.sdk.api.session.room.model.call.CallCandidatesContent
|
||||||
import org.matrix.android.sdk.api.session.room.model.call.CallHangupContent
|
import org.matrix.android.sdk.api.session.room.model.call.CallHangupContent
|
||||||
|
@ -57,34 +58,32 @@ import javax.inject.Singleton
|
||||||
@Singleton
|
@Singleton
|
||||||
class WebRtcCallManager @Inject constructor(
|
class WebRtcCallManager @Inject constructor(
|
||||||
private val context: Context,
|
private val context: Context,
|
||||||
private val activeSessionDataSource: ActiveSessionDataSource,
|
private val activeSessionDataSource: ActiveSessionDataSource
|
||||||
private val pstnProtocolChecker: PSTNProtocolChecker
|
|
||||||
) : CallListener, LifecycleObserver {
|
) : CallListener, LifecycleObserver {
|
||||||
|
|
||||||
private val currentSession: Session?
|
private val currentSession: Session?
|
||||||
get() = activeSessionDataSource.currentValue?.orNull()
|
get() = activeSessionDataSource.currentValue?.orNull()
|
||||||
|
|
||||||
|
private val pstnProtocolChecker: PSTNProtocolChecker?
|
||||||
|
get() = currentSession?.callSignalingService()?.getPSTNProtocolChecker()
|
||||||
|
|
||||||
interface CurrentCallListener {
|
interface CurrentCallListener {
|
||||||
fun onCurrentCallChange(call: WebRtcCall?) {}
|
fun onCurrentCallChange(call: WebRtcCall?) {}
|
||||||
fun onAudioDevicesChange() {}
|
fun onAudioDevicesChange() {}
|
||||||
}
|
}
|
||||||
|
|
||||||
interface PSTNSupportListener {
|
|
||||||
fun onPSTNSupportUpdated()
|
|
||||||
}
|
|
||||||
|
|
||||||
val supportedPSTNProtocol: String?
|
val supportedPSTNProtocol: String?
|
||||||
get() = pstnProtocolChecker.supportedPSTNProtocol
|
get() = pstnProtocolChecker?.supportedPSTNProtocol
|
||||||
|
|
||||||
val supportsPSTNProtocol: Boolean
|
val supportsPSTNProtocol: Boolean
|
||||||
get() = supportedPSTNProtocol != null
|
get() = supportedPSTNProtocol != null
|
||||||
|
|
||||||
fun addPstnSupportListener(listener: PSTNSupportListener) {
|
fun addPstnSupportListener(listener: PSTNProtocolChecker.Listener) {
|
||||||
pstnProtocolChecker.addPstnSupportListener(listener)
|
pstnProtocolChecker?.addListener(listener)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun removePstnSupportListener(listener: PSTNSupportListener) {
|
fun removePstnSupportListener(listener: PSTNProtocolChecker.Listener) {
|
||||||
pstnProtocolChecker.removePstnSupportListener(listener)
|
pstnProtocolChecker?.removeListener(listener)
|
||||||
}
|
}
|
||||||
|
|
||||||
private val currentCallsListeners = CopyOnWriteArrayList<CurrentCallListener>()
|
private val currentCallsListeners = CopyOnWriteArrayList<CurrentCallListener>()
|
||||||
|
@ -116,7 +115,6 @@ class WebRtcCallManager @Inject constructor(
|
||||||
@OnLifecycleEvent(Lifecycle.Event.ON_RESUME)
|
@OnLifecycleEvent(Lifecycle.Event.ON_RESUME)
|
||||||
fun entersForeground() {
|
fun entersForeground() {
|
||||||
isInBackground = false
|
isInBackground = false
|
||||||
checkForPSTNSupportIfNeeded()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@OnLifecycleEvent(Lifecycle.Event.ON_PAUSE)
|
@OnLifecycleEvent(Lifecycle.Event.ON_PAUSE)
|
||||||
|
@ -157,7 +155,7 @@ class WebRtcCallManager @Inject constructor(
|
||||||
}
|
}
|
||||||
|
|
||||||
fun checkForPSTNSupportIfNeeded() {
|
fun checkForPSTNSupportIfNeeded() {
|
||||||
pstnProtocolChecker.checkForPSTNSupportIfNeeded(currentSession)
|
pstnProtocolChecker?.checkForPSTNSupportIfNeeded()
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -169,7 +167,6 @@ class WebRtcCallManager @Inject constructor(
|
||||||
Timber.v("## VOIP headSetButtonTapped")
|
Timber.v("## VOIP headSetButtonTapped")
|
||||||
val call = getCurrentCall() ?: return
|
val call = getCurrentCall() ?: return
|
||||||
if (call.mxCall.state is CallState.LocalRinging) {
|
if (call.mxCall.state is CallState.LocalRinging) {
|
||||||
// accept call
|
|
||||||
call.acceptIncomingCall()
|
call.acceptIncomingCall()
|
||||||
}
|
}
|
||||||
if (call.mxCall.state is CallState.Connected) {
|
if (call.mxCall.state is CallState.Connected) {
|
||||||
|
|
|
@ -26,8 +26,8 @@ import com.airbnb.mvrx.ViewModelContext
|
||||||
import com.jakewharton.rxrelay2.BehaviorRelay
|
import com.jakewharton.rxrelay2.BehaviorRelay
|
||||||
import com.jakewharton.rxrelay2.PublishRelay
|
import com.jakewharton.rxrelay2.PublishRelay
|
||||||
import dagger.assisted.Assisted
|
import dagger.assisted.Assisted
|
||||||
import dagger.assisted.AssistedInject
|
|
||||||
import dagger.assisted.AssistedFactory
|
import dagger.assisted.AssistedFactory
|
||||||
|
import dagger.assisted.AssistedInject
|
||||||
import im.vector.app.R
|
import im.vector.app.R
|
||||||
import im.vector.app.core.extensions.exhaustive
|
import im.vector.app.core.extensions.exhaustive
|
||||||
import im.vector.app.core.platform.VectorViewModel
|
import im.vector.app.core.platform.VectorViewModel
|
||||||
|
@ -65,6 +65,7 @@ import org.matrix.android.sdk.api.extensions.tryOrNull
|
||||||
import org.matrix.android.sdk.api.query.QueryStringValue
|
import org.matrix.android.sdk.api.query.QueryStringValue
|
||||||
import org.matrix.android.sdk.api.raw.RawService
|
import org.matrix.android.sdk.api.raw.RawService
|
||||||
import org.matrix.android.sdk.api.session.Session
|
import org.matrix.android.sdk.api.session.Session
|
||||||
|
import org.matrix.android.sdk.api.session.call.PSTNProtocolChecker
|
||||||
import org.matrix.android.sdk.api.session.crypto.MXCryptoError
|
import org.matrix.android.sdk.api.session.crypto.MXCryptoError
|
||||||
import org.matrix.android.sdk.api.session.events.model.EventType
|
import org.matrix.android.sdk.api.session.events.model.EventType
|
||||||
import org.matrix.android.sdk.api.session.events.model.LocalEcho
|
import org.matrix.android.sdk.api.session.events.model.LocalEcho
|
||||||
|
@ -121,7 +122,7 @@ class RoomDetailViewModel @AssistedInject constructor(
|
||||||
private val directRoomHelper: DirectRoomHelper,
|
private val directRoomHelper: DirectRoomHelper,
|
||||||
timelineSettingsFactory: TimelineSettingsFactory
|
timelineSettingsFactory: TimelineSettingsFactory
|
||||||
) : VectorViewModel<RoomDetailViewState, RoomDetailAction, RoomDetailViewEvents>(initialState),
|
) : VectorViewModel<RoomDetailViewState, RoomDetailAction, RoomDetailViewEvents>(initialState),
|
||||||
Timeline.Listener, ChatEffectManager.Delegate, WebRtcCallManager.PSTNSupportListener {
|
Timeline.Listener, ChatEffectManager.Delegate, PSTNProtocolChecker.Listener {
|
||||||
|
|
||||||
private val room = session.getRoom(initialState.roomId)!!
|
private val room = session.getRoom(initialState.roomId)!!
|
||||||
private val eventId = initialState.eventId
|
private val eventId = initialState.eventId
|
||||||
|
@ -233,65 +234,65 @@ class RoomDetailViewModel @AssistedInject constructor(
|
||||||
|
|
||||||
override fun handle(action: RoomDetailAction) {
|
override fun handle(action: RoomDetailAction) {
|
||||||
when (action) {
|
when (action) {
|
||||||
is RoomDetailAction.UserIsTyping -> handleUserIsTyping(action)
|
is RoomDetailAction.UserIsTyping -> handleUserIsTyping(action)
|
||||||
is RoomDetailAction.SaveDraft -> handleSaveDraft(action)
|
is RoomDetailAction.SaveDraft -> handleSaveDraft(action)
|
||||||
is RoomDetailAction.SendMessage -> handleSendMessage(action)
|
is RoomDetailAction.SendMessage -> handleSendMessage(action)
|
||||||
is RoomDetailAction.SendMedia -> handleSendMedia(action)
|
is RoomDetailAction.SendMedia -> handleSendMedia(action)
|
||||||
is RoomDetailAction.SendSticker -> handleSendSticker(action)
|
is RoomDetailAction.SendSticker -> handleSendSticker(action)
|
||||||
is RoomDetailAction.TimelineEventTurnsVisible -> handleEventVisible(action)
|
is RoomDetailAction.TimelineEventTurnsVisible -> handleEventVisible(action)
|
||||||
is RoomDetailAction.TimelineEventTurnsInvisible -> handleEventInvisible(action)
|
is RoomDetailAction.TimelineEventTurnsInvisible -> handleEventInvisible(action)
|
||||||
is RoomDetailAction.LoadMoreTimelineEvents -> handleLoadMore(action)
|
is RoomDetailAction.LoadMoreTimelineEvents -> handleLoadMore(action)
|
||||||
is RoomDetailAction.SendReaction -> handleSendReaction(action)
|
is RoomDetailAction.SendReaction -> handleSendReaction(action)
|
||||||
is RoomDetailAction.AcceptInvite -> handleAcceptInvite()
|
is RoomDetailAction.AcceptInvite -> handleAcceptInvite()
|
||||||
is RoomDetailAction.RejectInvite -> handleRejectInvite()
|
is RoomDetailAction.RejectInvite -> handleRejectInvite()
|
||||||
is RoomDetailAction.RedactAction -> handleRedactEvent(action)
|
is RoomDetailAction.RedactAction -> handleRedactEvent(action)
|
||||||
is RoomDetailAction.UndoReaction -> handleUndoReact(action)
|
is RoomDetailAction.UndoReaction -> handleUndoReact(action)
|
||||||
is RoomDetailAction.UpdateQuickReactAction -> handleUpdateQuickReaction(action)
|
is RoomDetailAction.UpdateQuickReactAction -> handleUpdateQuickReaction(action)
|
||||||
is RoomDetailAction.EnterRegularMode -> handleEnterRegularMode(action)
|
is RoomDetailAction.EnterRegularMode -> handleEnterRegularMode(action)
|
||||||
is RoomDetailAction.EnterEditMode -> handleEditAction(action)
|
is RoomDetailAction.EnterEditMode -> handleEditAction(action)
|
||||||
is RoomDetailAction.EnterQuoteMode -> handleQuoteAction(action)
|
is RoomDetailAction.EnterQuoteMode -> handleQuoteAction(action)
|
||||||
is RoomDetailAction.EnterReplyMode -> handleReplyAction(action)
|
is RoomDetailAction.EnterReplyMode -> handleReplyAction(action)
|
||||||
is RoomDetailAction.DownloadOrOpen -> handleOpenOrDownloadFile(action)
|
is RoomDetailAction.DownloadOrOpen -> handleOpenOrDownloadFile(action)
|
||||||
is RoomDetailAction.NavigateToEvent -> handleNavigateToEvent(action)
|
is RoomDetailAction.NavigateToEvent -> handleNavigateToEvent(action)
|
||||||
is RoomDetailAction.HandleTombstoneEvent -> handleTombstoneEvent(action)
|
is RoomDetailAction.HandleTombstoneEvent -> handleTombstoneEvent(action)
|
||||||
is RoomDetailAction.ResendMessage -> handleResendEvent(action)
|
is RoomDetailAction.ResendMessage -> handleResendEvent(action)
|
||||||
is RoomDetailAction.RemoveFailedEcho -> handleRemove(action)
|
is RoomDetailAction.RemoveFailedEcho -> handleRemove(action)
|
||||||
is RoomDetailAction.ResendAll -> handleResendAll()
|
is RoomDetailAction.ResendAll -> handleResendAll()
|
||||||
is RoomDetailAction.MarkAllAsRead -> handleMarkAllAsRead()
|
is RoomDetailAction.MarkAllAsRead -> handleMarkAllAsRead()
|
||||||
is RoomDetailAction.ReportContent -> handleReportContent(action)
|
is RoomDetailAction.ReportContent -> handleReportContent(action)
|
||||||
is RoomDetailAction.IgnoreUser -> handleIgnoreUser(action)
|
is RoomDetailAction.IgnoreUser -> handleIgnoreUser(action)
|
||||||
is RoomDetailAction.EnterTrackingUnreadMessagesState -> startTrackingUnreadMessages()
|
is RoomDetailAction.EnterTrackingUnreadMessagesState -> startTrackingUnreadMessages()
|
||||||
is RoomDetailAction.ExitTrackingUnreadMessagesState -> stopTrackingUnreadMessages()
|
is RoomDetailAction.ExitTrackingUnreadMessagesState -> stopTrackingUnreadMessages()
|
||||||
is RoomDetailAction.ReplyToOptions -> handleReplyToOptions(action)
|
is RoomDetailAction.ReplyToOptions -> handleReplyToOptions(action)
|
||||||
is RoomDetailAction.AcceptVerificationRequest -> handleAcceptVerification(action)
|
is RoomDetailAction.AcceptVerificationRequest -> handleAcceptVerification(action)
|
||||||
is RoomDetailAction.DeclineVerificationRequest -> handleDeclineVerification(action)
|
is RoomDetailAction.DeclineVerificationRequest -> handleDeclineVerification(action)
|
||||||
is RoomDetailAction.RequestVerification -> handleRequestVerification(action)
|
is RoomDetailAction.RequestVerification -> handleRequestVerification(action)
|
||||||
is RoomDetailAction.ResumeVerification -> handleResumeRequestVerification(action)
|
is RoomDetailAction.ResumeVerification -> handleResumeRequestVerification(action)
|
||||||
is RoomDetailAction.ReRequestKeys -> handleReRequestKeys(action)
|
is RoomDetailAction.ReRequestKeys -> handleReRequestKeys(action)
|
||||||
is RoomDetailAction.TapOnFailedToDecrypt -> handleTapOnFailedToDecrypt(action)
|
is RoomDetailAction.TapOnFailedToDecrypt -> handleTapOnFailedToDecrypt(action)
|
||||||
is RoomDetailAction.SelectStickerAttachment -> handleSelectStickerAttachment()
|
is RoomDetailAction.SelectStickerAttachment -> handleSelectStickerAttachment()
|
||||||
is RoomDetailAction.OpenIntegrationManager -> handleOpenIntegrationManager()
|
is RoomDetailAction.OpenIntegrationManager -> handleOpenIntegrationManager()
|
||||||
is RoomDetailAction.StartCallWithPhoneNumber -> handleStartCallWithPhoneNumber(action)
|
is RoomDetailAction.StartCallWithPhoneNumber -> handleStartCallWithPhoneNumber(action)
|
||||||
is RoomDetailAction.StartCall -> handleStartCall(action)
|
is RoomDetailAction.StartCall -> handleStartCall(action)
|
||||||
is RoomDetailAction.AcceptCall -> handleAcceptCall(action)
|
is RoomDetailAction.AcceptCall -> handleAcceptCall(action)
|
||||||
is RoomDetailAction.EndCall -> handleEndCall()
|
is RoomDetailAction.EndCall -> handleEndCall()
|
||||||
is RoomDetailAction.ManageIntegrations -> handleManageIntegrations()
|
is RoomDetailAction.ManageIntegrations -> handleManageIntegrations()
|
||||||
is RoomDetailAction.AddJitsiWidget -> handleAddJitsiConference(action)
|
is RoomDetailAction.AddJitsiWidget -> handleAddJitsiConference(action)
|
||||||
is RoomDetailAction.RemoveWidget -> handleDeleteWidget(action.widgetId)
|
is RoomDetailAction.RemoveWidget -> handleDeleteWidget(action.widgetId)
|
||||||
is RoomDetailAction.EnsureNativeWidgetAllowed -> handleCheckWidgetAllowed(action)
|
is RoomDetailAction.EnsureNativeWidgetAllowed -> handleCheckWidgetAllowed(action)
|
||||||
is RoomDetailAction.CancelSend -> handleCancel(action)
|
is RoomDetailAction.CancelSend -> handleCancel(action)
|
||||||
is RoomDetailAction.OpenOrCreateDm -> handleOpenOrCreateDm(action)
|
is RoomDetailAction.OpenOrCreateDm -> handleOpenOrCreateDm(action)
|
||||||
is RoomDetailAction.JumpToReadReceipt -> handleJumpToReadReceipt(action)
|
is RoomDetailAction.JumpToReadReceipt -> handleJumpToReadReceipt(action)
|
||||||
RoomDetailAction.QuickActionInvitePeople -> handleInvitePeople()
|
RoomDetailAction.QuickActionInvitePeople -> handleInvitePeople()
|
||||||
RoomDetailAction.QuickActionSetAvatar -> handleQuickSetAvatar()
|
RoomDetailAction.QuickActionSetAvatar -> handleQuickSetAvatar()
|
||||||
is RoomDetailAction.SetAvatarAction -> handleSetNewAvatar(action)
|
is RoomDetailAction.SetAvatarAction -> handleSetNewAvatar(action)
|
||||||
RoomDetailAction.QuickActionSetTopic -> _viewEvents.post(RoomDetailViewEvents.OpenRoomSettings)
|
RoomDetailAction.QuickActionSetTopic -> _viewEvents.post(RoomDetailViewEvents.OpenRoomSettings)
|
||||||
is RoomDetailAction.ShowRoomAvatarFullScreen -> {
|
is RoomDetailAction.ShowRoomAvatarFullScreen -> {
|
||||||
_viewEvents.post(
|
_viewEvents.post(
|
||||||
RoomDetailViewEvents.ShowRoomAvatarFullScreen(action.matrixItem, action.transitionView)
|
RoomDetailViewEvents.ShowRoomAvatarFullScreen(action.matrixItem, action.transitionView)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
is RoomDetailAction.DoNotShowPreviewUrlFor -> handleDoNotShowPreviewUrlFor(action)
|
is RoomDetailAction.DoNotShowPreviewUrlFor -> handleDoNotShowPreviewUrlFor(action)
|
||||||
}.exhaustive
|
}.exhaustive
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -620,16 +621,16 @@ class RoomDetailViewModel @AssistedInject constructor(
|
||||||
return@withState false
|
return@withState false
|
||||||
}
|
}
|
||||||
when (itemId) {
|
when (itemId) {
|
||||||
R.id.resend_all -> state.asyncRoomSummary()?.hasFailedSending == true
|
R.id.resend_all -> state.asyncRoomSummary()?.hasFailedSending == true
|
||||||
R.id.timeline_setting -> true
|
R.id.timeline_setting -> true
|
||||||
R.id.invite -> state.canInvite
|
R.id.invite -> state.canInvite
|
||||||
R.id.clear_all -> state.asyncRoomSummary()?.hasFailedSending == true
|
R.id.clear_all -> state.asyncRoomSummary()?.hasFailedSending == true
|
||||||
R.id.open_matrix_apps -> true
|
R.id.open_matrix_apps -> true
|
||||||
R.id.voice_call,
|
R.id.voice_call,
|
||||||
R.id.video_call -> callManager.getCallsByRoomId(state.roomId).isEmpty()
|
R.id.video_call -> callManager.getCallsByRoomId(state.roomId).isEmpty()
|
||||||
R.id.hangup_call -> callManager.getCallsByRoomId(state.roomId).isNotEmpty()
|
R.id.hangup_call -> callManager.getCallsByRoomId(state.roomId).isNotEmpty()
|
||||||
R.id.search -> true
|
R.id.search -> true
|
||||||
else -> false
|
else -> false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -742,7 +743,7 @@ class RoomDetailViewModel @AssistedInject constructor(
|
||||||
_viewEvents.post(RoomDetailViewEvents.SlashCommandHandled())
|
_viewEvents.post(RoomDetailViewEvents.SlashCommandHandled())
|
||||||
popDraft()
|
popDraft()
|
||||||
}
|
}
|
||||||
is ParsedCommand.SendChatEffect -> {
|
is ParsedCommand.SendChatEffect -> {
|
||||||
sendChatEffect(slashCommandResult)
|
sendChatEffect(slashCommandResult)
|
||||||
_viewEvents.post(RoomDetailViewEvents.SlashCommandHandled())
|
_viewEvents.post(RoomDetailViewEvents.SlashCommandHandled())
|
||||||
popDraft()
|
popDraft()
|
||||||
|
@ -775,7 +776,7 @@ class RoomDetailViewModel @AssistedInject constructor(
|
||||||
}
|
}
|
||||||
}.exhaustive
|
}.exhaustive
|
||||||
}
|
}
|
||||||
is SendMode.EDIT -> {
|
is SendMode.EDIT -> {
|
||||||
// is original event a reply?
|
// is original event a reply?
|
||||||
val inReplyTo = state.sendMode.timelineEvent.getRelationContent()?.inReplyTo?.eventId
|
val inReplyTo = state.sendMode.timelineEvent.getRelationContent()?.inReplyTo?.eventId
|
||||||
if (inReplyTo != null) {
|
if (inReplyTo != null) {
|
||||||
|
@ -800,7 +801,7 @@ class RoomDetailViewModel @AssistedInject constructor(
|
||||||
_viewEvents.post(RoomDetailViewEvents.MessageSent)
|
_viewEvents.post(RoomDetailViewEvents.MessageSent)
|
||||||
popDraft()
|
popDraft()
|
||||||
}
|
}
|
||||||
is SendMode.QUOTE -> {
|
is SendMode.QUOTE -> {
|
||||||
val messageContent: MessageContent? =
|
val messageContent: MessageContent? =
|
||||||
state.sendMode.timelineEvent.annotations?.editSummary?.aggregatedContent.toModel()
|
state.sendMode.timelineEvent.annotations?.editSummary?.aggregatedContent.toModel()
|
||||||
?: state.sendMode.timelineEvent.root.getClearContent().toModel()
|
?: state.sendMode.timelineEvent.root.getClearContent().toModel()
|
||||||
|
@ -823,7 +824,7 @@ class RoomDetailViewModel @AssistedInject constructor(
|
||||||
_viewEvents.post(RoomDetailViewEvents.MessageSent)
|
_viewEvents.post(RoomDetailViewEvents.MessageSent)
|
||||||
popDraft()
|
popDraft()
|
||||||
}
|
}
|
||||||
is SendMode.REPLY -> {
|
is SendMode.REPLY -> {
|
||||||
state.sendMode.timelineEvent.let {
|
state.sendMode.timelineEvent.let {
|
||||||
room.replyToMessage(it, action.text.toString(), action.autoMarkdown)
|
room.replyToMessage(it, action.text.toString(), action.autoMarkdown)
|
||||||
_viewEvents.post(RoomDetailViewEvents.MessageSent)
|
_viewEvents.post(RoomDetailViewEvents.MessageSent)
|
||||||
|
@ -1442,7 +1443,7 @@ class RoomDetailViewModel @AssistedInject constructor(
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onPSTNSupportUpdated() {
|
override fun onPSTNSupportUpdated() {
|
||||||
updateShowDialerOptionState()
|
updateShowDialerOptionState()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun updateShowDialerOptionState() {
|
private fun updateShowDialerOptionState() {
|
||||||
|
|
Loading…
Reference in a new issue