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) - 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) - 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) - Fix uploads still don't work with room v6 (#1879)
- Can't handle ongoing call events in background (#1992)
Translations 🗣: Translations 🗣:
- -

View file

@ -34,4 +34,6 @@ interface CallSignalingService {
fun removeCallListener(listener: CallsListener) fun removeCallListener(listener: CallsListener)
fun getCallWithId(callId: String) : MxCall? 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( internal class DefaultCallSignalingService @Inject constructor(
@UserId @UserId
private val userId: String, private val userId: String,
private val activeCallHandler: ActiveCallHandler,
private val localEchoEventFactory: LocalEchoEventFactory, private val localEchoEventFactory: LocalEchoEventFactory,
private val roomEventSender: RoomEventSender, private val roomEventSender: RoomEventSender,
private val taskExecutor: TaskExecutor, private val taskExecutor: TaskExecutor,
@ -57,8 +58,6 @@ internal class DefaultCallSignalingService @Inject constructor(
private val callListeners = mutableSetOf<CallsListener>() private val callListeners = mutableSetOf<CallsListener>()
private val activeCalls = mutableListOf<MxCall>()
private val cachedTurnServerResponse = object { 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. // 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 private val MIN_TTL = 60
@ -97,7 +96,7 @@ internal class DefaultCallSignalingService @Inject constructor(
} }
override fun createOutgoingCall(roomId: String, otherUserId: String, isVideoCall: Boolean): MxCall { override fun createOutgoingCall(roomId: String, otherUserId: String, isVideoCall: Boolean): MxCall {
return MxCallImpl( val call = MxCallImpl(
callId = UUID.randomUUID().toString(), callId = UUID.randomUUID().toString(),
isOutgoing = true, isOutgoing = true,
roomId = roomId, roomId = roomId,
@ -106,8 +105,9 @@ internal class DefaultCallSignalingService @Inject constructor(
isVideoCall = isVideoCall, isVideoCall = isVideoCall,
localEchoEventFactory = localEchoEventFactory, localEchoEventFactory = localEchoEventFactory,
roomEventSender = roomEventSender 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? { override fun getCallWithId(callId: String): MxCall? {
Timber.v("## VOIP getCallWithId $callId all calls ${activeCalls.map { it.callId }}") Timber.v("## VOIP getCallWithId $callId all calls ${activeCallHandler.getActiveCallsLiveData().value?.map { it.callId }}")
return activeCalls.find { it.callId == callId } return activeCallHandler.getCallWithId(callId)
}
override fun isThereAnyActiveCall(): Boolean {
return activeCallHandler.getActiveCallsLiveData().value?.isNotEmpty() == true
} }
internal fun onCallEvent(event: Event) { internal fun onCallEvent(event: Event) {
@ -152,6 +156,7 @@ internal class DefaultCallSignalingService @Inject constructor(
// Always ignore local echos of invite // Always ignore local echos of invite
return return
} }
event.getClearContent().toModel<CallInviteContent>()?.let { content -> event.getClearContent().toModel<CallInviteContent>()?.let { content ->
val incomingCall = MxCallImpl( val incomingCall = MxCallImpl(
callId = content.callId ?: return@let, callId = content.callId ?: return@let,
@ -163,7 +168,7 @@ internal class DefaultCallSignalingService @Inject constructor(
localEchoEventFactory = localEchoEventFactory, localEchoEventFactory = localEchoEventFactory,
roomEventSender = roomEventSender roomEventSender = roomEventSender
) )
activeCalls.add(incomingCall) activeCallHandler.addCall(incomingCall)
onCallInvite(incomingCall, content) onCallInvite(incomingCall, content)
} }
} }
@ -185,8 +190,8 @@ internal class DefaultCallSignalingService @Inject constructor(
return return
} }
activeCallHandler.removeCall(content.callId)
onCallHangup(content) onCallHangup(content)
activeCalls.removeAll { it.callId == content.callId }
} }
} }
EventType.CALL_CANDIDATES -> { EventType.CALL_CANDIDATES -> {
@ -195,7 +200,7 @@ internal class DefaultCallSignalingService @Inject constructor(
return return
} }
event.getClearContent().toModel<CallCandidatesContent>()?.let { content -> event.getClearContent().toModel<CallCandidatesContent>()?.let { content ->
activeCalls.firstOrNull { it.callId == content.callId }?.let { activeCallHandler.getCallWithId(content.callId)?.let {
onCallIceCandidate(it, content) 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.LiveData
import androidx.lifecycle.MutableLiveData import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.Observer
import com.squareup.moshi.JsonEncodingException import com.squareup.moshi.JsonEncodingException
import org.matrix.android.sdk.api.failure.Failure import org.matrix.android.sdk.api.failure.Failure
import org.matrix.android.sdk.api.failure.isTokenError 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.Debouncer
import org.matrix.android.sdk.internal.util.createUIHandler import org.matrix.android.sdk.internal.util.createUIHandler
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.SupervisorJob import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.cancelChildren import kotlinx.coroutines.cancelChildren
import kotlinx.coroutines.delay import kotlinx.coroutines.delay
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking 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 timber.log.Timber
import java.net.SocketTimeoutException import java.net.SocketTimeoutException
import java.util.Timer 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, internal class SyncThread @Inject constructor(private val syncTask: SyncTask,
private val typingUsersTracker: DefaultTypingUsersTracker, private val typingUsersTracker: DefaultTypingUsersTracker,
private val networkConnectivityChecker: NetworkConnectivityChecker, private val networkConnectivityChecker: NetworkConnectivityChecker,
private val backgroundDetectionObserver: BackgroundDetectionObserver) private val backgroundDetectionObserver: BackgroundDetectionObserver,
: Thread(), NetworkConnectivityChecker.Listener, BackgroundDetectionObserver.Listener { private val activeCallHandler: ActiveCallHandler
) : Thread(), NetworkConnectivityChecker.Listener, BackgroundDetectionObserver.Listener {
private var state: SyncState = SyncState.Idle private var state: SyncState = SyncState.Idle
private var liveState = MutableLiveData<SyncState>(state) 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 isTokenValid = true
private var retryNoNetworkTask: TimerTask? = null private var retryNoNetworkTask: TimerTask? = null
private val activeCallListObserver = Observer<MutableList<MxCall>> { activeCalls ->
if (activeCalls.isEmpty() && backgroundDetectionObserver.isInBackground) {
pause()
}
}
init { init {
updateStateTo(SyncState.Idle) updateStateTo(SyncState.Idle)
} }
@ -115,9 +126,11 @@ internal class SyncThread @Inject constructor(private val syncTask: SyncTask,
override fun run() { override fun run() {
Timber.v("Start syncing...") Timber.v("Start syncing...")
isStarted = true isStarted = true
networkConnectivityChecker.register(this) networkConnectivityChecker.register(this)
backgroundDetectionObserver.register(this) backgroundDetectionObserver.register(this)
registerActiveCallsObserver()
while (state != SyncState.Killing) { while (state != SyncState.Killing) {
Timber.v("Entering loop, state: $state") Timber.v("Entering loop, state: $state")
if (!isStarted) { if (!isStarted) {
@ -163,6 +176,19 @@ internal class SyncThread @Inject constructor(private val syncTask: SyncTask,
updateStateTo(SyncState.Killed) updateStateTo(SyncState.Killed)
backgroundDetectionObserver.unregister(this) backgroundDetectionObserver.unregister(this)
networkConnectivityChecker.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) { private suspend fun doSync(params: SyncTask.Params) {
@ -215,6 +241,8 @@ internal class SyncThread @Inject constructor(private val syncTask: SyncTask,
} }
override fun onMoveToBackground() { override fun onMoveToBackground() {
pause() if (activeCallHandler.getActiveCallsLiveData().value.isNullOrEmpty()) {
pause()
}
} }
} }