Merge pull request #1993 from vector-im/feature/fix_call_in_background

Don't pause the sync thread if there is an active or pending call.
This commit is contained in:
Benoit Marty 2020-08-27 22:41:46 +02:00 committed by GitHub
commit fbcc7aa211
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 94 additions and 13 deletions

View file

@ -16,6 +16,7 @@ Bugfix 🐛:
- Fix bad color for settings icon on Android < 24 (#1786)
- Change user or room avatar: when selecting Gallery, I'm not proposed to crop the selected image (#1590)
- Fix uploads still don't work with room v6 (#1879)
- Can't handle ongoing call events in background (#1992)
Translations 🗣:
-

View file

@ -34,4 +34,6 @@ interface CallSignalingService {
fun removeCallListener(listener: CallsListener)
fun getCallWithId(callId: String) : MxCall?
fun isThereAnyActiveCall(): Boolean
}

View file

@ -0,0 +1,45 @@
/*
* Copyright (c) 2020 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.internal.session.call
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import org.matrix.android.sdk.api.session.call.MxCall
import org.matrix.android.sdk.internal.session.SessionScope
import javax.inject.Inject
@SessionScope
internal class ActiveCallHandler @Inject constructor() {
private val activeCallListLiveData: MutableLiveData<MutableList<MxCall>> by lazy {
MutableLiveData<MutableList<MxCall>>(mutableListOf())
}
fun addCall(call: MxCall) {
activeCallListLiveData.postValue(activeCallListLiveData.value?.apply { add(call) })
}
fun removeCall(callId: String) {
activeCallListLiveData.postValue(activeCallListLiveData.value?.apply { removeAll { it.callId == callId } })
}
fun getCallWithId(callId: String): MxCall? {
return activeCallListLiveData.value?.find { it.callId == callId }
}
fun getActiveCallsLiveData(): LiveData<MutableList<MxCall>> = activeCallListLiveData
}

View file

@ -49,6 +49,7 @@ import javax.inject.Inject
internal class DefaultCallSignalingService @Inject constructor(
@UserId
private val userId: String,
private val activeCallHandler: ActiveCallHandler,
private val localEchoEventFactory: LocalEchoEventFactory,
private val roomEventSender: RoomEventSender,
private val taskExecutor: TaskExecutor,
@ -57,8 +58,6 @@ internal class DefaultCallSignalingService @Inject constructor(
private val callListeners = mutableSetOf<CallsListener>()
private val activeCalls = mutableListOf<MxCall>()
private val cachedTurnServerResponse = object {
// Keep one minute safe to avoid considering the data is valid and then actually it is not when effectively using it.
private val MIN_TTL = 60
@ -97,7 +96,7 @@ internal class DefaultCallSignalingService @Inject constructor(
}
override fun createOutgoingCall(roomId: String, otherUserId: String, isVideoCall: Boolean): MxCall {
return MxCallImpl(
val call = MxCallImpl(
callId = UUID.randomUUID().toString(),
isOutgoing = true,
roomId = roomId,
@ -106,8 +105,9 @@ internal class DefaultCallSignalingService @Inject constructor(
isVideoCall = isVideoCall,
localEchoEventFactory = localEchoEventFactory,
roomEventSender = roomEventSender
).also {
activeCalls.add(it)
)
activeCallHandler.addCall(call).also {
return call
}
}
@ -120,8 +120,12 @@ internal class DefaultCallSignalingService @Inject constructor(
}
override fun getCallWithId(callId: String): MxCall? {
Timber.v("## VOIP getCallWithId $callId all calls ${activeCalls.map { it.callId }}")
return activeCalls.find { it.callId == callId }
Timber.v("## VOIP getCallWithId $callId all calls ${activeCallHandler.getActiveCallsLiveData().value?.map { it.callId }}")
return activeCallHandler.getCallWithId(callId)
}
override fun isThereAnyActiveCall(): Boolean {
return activeCallHandler.getActiveCallsLiveData().value?.isNotEmpty() == true
}
internal fun onCallEvent(event: Event) {
@ -152,6 +156,7 @@ internal class DefaultCallSignalingService @Inject constructor(
// Always ignore local echos of invite
return
}
event.getClearContent().toModel<CallInviteContent>()?.let { content ->
val incomingCall = MxCallImpl(
callId = content.callId ?: return@let,
@ -163,7 +168,7 @@ internal class DefaultCallSignalingService @Inject constructor(
localEchoEventFactory = localEchoEventFactory,
roomEventSender = roomEventSender
)
activeCalls.add(incomingCall)
activeCallHandler.addCall(incomingCall)
onCallInvite(incomingCall, content)
}
}
@ -185,8 +190,8 @@ internal class DefaultCallSignalingService @Inject constructor(
return
}
activeCallHandler.removeCall(content.callId)
onCallHangup(content)
activeCalls.removeAll { it.callId == content.callId }
}
}
EventType.CALL_CANDIDATES -> {
@ -195,7 +200,7 @@ internal class DefaultCallSignalingService @Inject constructor(
return
}
event.getClearContent().toModel<CallCandidatesContent>()?.let { content ->
activeCalls.firstOrNull { it.callId == content.callId }?.let {
activeCallHandler.getCallWithId(content.callId)?.let {
onCallIceCandidate(it, content)
}
}

View file

@ -19,6 +19,7 @@ package org.matrix.android.sdk.internal.session.sync.job
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.Observer
import com.squareup.moshi.JsonEncodingException
import org.matrix.android.sdk.api.failure.Failure
import org.matrix.android.sdk.api.failure.isTokenError
@ -30,11 +31,14 @@ import org.matrix.android.sdk.internal.util.BackgroundDetectionObserver
import org.matrix.android.sdk.internal.util.Debouncer
import org.matrix.android.sdk.internal.util.createUIHandler
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.cancelChildren
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
import org.matrix.android.sdk.api.session.call.MxCall
import org.matrix.android.sdk.internal.session.call.ActiveCallHandler
import timber.log.Timber
import java.net.SocketTimeoutException
import java.util.Timer
@ -48,8 +52,9 @@ private const val DEFAULT_LONG_POOL_TIMEOUT = 30_000L
internal class SyncThread @Inject constructor(private val syncTask: SyncTask,
private val typingUsersTracker: DefaultTypingUsersTracker,
private val networkConnectivityChecker: NetworkConnectivityChecker,
private val backgroundDetectionObserver: BackgroundDetectionObserver)
: Thread(), NetworkConnectivityChecker.Listener, BackgroundDetectionObserver.Listener {
private val backgroundDetectionObserver: BackgroundDetectionObserver,
private val activeCallHandler: ActiveCallHandler
) : Thread(), NetworkConnectivityChecker.Listener, BackgroundDetectionObserver.Listener {
private var state: SyncState = SyncState.Idle
private var liveState = MutableLiveData<SyncState>(state)
@ -62,6 +67,12 @@ internal class SyncThread @Inject constructor(private val syncTask: SyncTask,
private var isTokenValid = true
private var retryNoNetworkTask: TimerTask? = null
private val activeCallListObserver = Observer<MutableList<MxCall>> { activeCalls ->
if (activeCalls.isEmpty() && backgroundDetectionObserver.isInBackground) {
pause()
}
}
init {
updateStateTo(SyncState.Idle)
}
@ -115,9 +126,11 @@ internal class SyncThread @Inject constructor(private val syncTask: SyncTask,
override fun run() {
Timber.v("Start syncing...")
isStarted = true
networkConnectivityChecker.register(this)
backgroundDetectionObserver.register(this)
registerActiveCallsObserver()
while (state != SyncState.Killing) {
Timber.v("Entering loop, state: $state")
if (!isStarted) {
@ -163,6 +176,19 @@ internal class SyncThread @Inject constructor(private val syncTask: SyncTask,
updateStateTo(SyncState.Killed)
backgroundDetectionObserver.unregister(this)
networkConnectivityChecker.unregister(this)
unregisterActiveCallsObserver()
}
private fun registerActiveCallsObserver() {
syncScope.launch(Dispatchers.Main) {
activeCallHandler.getActiveCallsLiveData().observeForever(activeCallListObserver)
}
}
private fun unregisterActiveCallsObserver() {
syncScope.launch(Dispatchers.Main) {
activeCallHandler.getActiveCallsLiveData().removeObserver(activeCallListObserver)
}
}
private suspend fun doSync(params: SyncTask.Params) {
@ -215,6 +241,8 @@ internal class SyncThread @Inject constructor(private val syncTask: SyncTask,
}
override fun onMoveToBackground() {
pause()
if (activeCallHandler.getActiveCallsLiveData().value.isNullOrEmpty()) {
pause()
}
}
}