diff --git a/CHANGES.md b/CHANGES.md index bc363683a2..1ffb6bcad0 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -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 🗣: - diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/call/CallSignalingService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/call/CallSignalingService.kt index 2962f9fac3..382ab54248 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/call/CallSignalingService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/call/CallSignalingService.kt @@ -34,4 +34,6 @@ interface CallSignalingService { fun removeCallListener(listener: CallsListener) fun getCallWithId(callId: String) : MxCall? + + fun isThereAnyActiveCall(): Boolean } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/call/ActiveCallHandler.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/call/ActiveCallHandler.kt new file mode 100644 index 0000000000..40f5a56c33 --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/call/ActiveCallHandler.kt @@ -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> by lazy { + MutableLiveData>(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> = activeCallListLiveData +} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/call/DefaultCallSignalingService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/call/DefaultCallSignalingService.kt index 0b097cf64f..d9bc66eddf 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/call/DefaultCallSignalingService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/call/DefaultCallSignalingService.kt @@ -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() - private val activeCalls = mutableListOf() - 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()?.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()?.let { content -> - activeCalls.firstOrNull { it.callId == content.callId }?.let { + activeCallHandler.getCallWithId(content.callId)?.let { onCallIceCandidate(it, content) } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/job/SyncThread.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/job/SyncThread.kt index 1a2d6b1fd3..9fd9c313db 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/job/SyncThread.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/job/SyncThread.kt @@ -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(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> { 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() + } } }