Fix ringing calls not stopping when the other user cancels the call (#4613)
* Fix ringing calls not being automatically canceled This will keep the sync active while the user is screening an incoming call, allowing receiving a call cancellation event, which will terminate the incoming call ringing early. * Add extra logs to help debugging ringing call issues.
This commit is contained in:
parent
75cb3156a1
commit
3b35d96e1a
10 changed files with 112 additions and 17 deletions
|
|
@ -26,6 +26,7 @@ import io.element.android.libraries.matrix.api.MatrixClientProvider
|
|||
import io.element.android.libraries.push.api.notifications.ForegroundServiceType
|
||||
import io.element.android.libraries.push.api.notifications.NotificationIdProvider
|
||||
import io.element.android.libraries.push.api.notifications.OnMissedCallNotificationHandler
|
||||
import io.element.android.services.appnavstate.api.AppForegroundStateService
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
import kotlinx.coroutines.Job
|
||||
|
|
@ -87,7 +88,9 @@ class DefaultActiveCallManager @Inject constructor(
|
|||
private val notificationManagerCompat: NotificationManagerCompat,
|
||||
private val matrixClientProvider: MatrixClientProvider,
|
||||
private val defaultCurrentCallService: DefaultCurrentCallService,
|
||||
private val appForegroundStateService: AppForegroundStateService,
|
||||
) : ActiveCallManager {
|
||||
private val tag = "DefaultActiveCallManager"
|
||||
private var timedOutCallJob: Job? = null
|
||||
|
||||
@VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
|
||||
|
|
@ -106,9 +109,11 @@ class DefaultActiveCallManager @Inject constructor(
|
|||
|
||||
override suspend fun registerIncomingCall(notificationData: CallNotificationData) {
|
||||
mutex.withLock {
|
||||
appForegroundStateService.updateHasRingingCall(true)
|
||||
Timber.tag(tag).d("Received incoming call for room id: ${notificationData.roomId}")
|
||||
if (activeCall.value != null) {
|
||||
displayMissedCallNotification(notificationData)
|
||||
Timber.w("Already have an active call, ignoring incoming call: $notificationData")
|
||||
Timber.tag(tag).w("Already have an active call, ignoring incoming call: $notificationData")
|
||||
return
|
||||
}
|
||||
activeCall.value = ActiveCall(
|
||||
|
|
@ -129,6 +134,7 @@ class DefaultActiveCallManager @Inject constructor(
|
|||
|
||||
// Acquire a wake lock to keep the device awake during the incoming call, so we can process the room info data
|
||||
if (activeWakeLock?.isHeld == false) {
|
||||
Timber.tag(tag).d("Acquiring partial wakelock")
|
||||
activeWakeLock.acquire(ElementCallConfig.RINGING_CALL_DURATION_SECONDS * 1000L)
|
||||
}
|
||||
}
|
||||
|
|
@ -139,10 +145,13 @@ class DefaultActiveCallManager @Inject constructor(
|
|||
*/
|
||||
@VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
|
||||
suspend fun incomingCallTimedOut(displayMissedCallNotification: Boolean) = mutex.withLock {
|
||||
Timber.tag(tag).d("Incoming call timed out")
|
||||
|
||||
val previousActiveCall = activeCall.value ?: return
|
||||
val notificationData = (previousActiveCall.callState as? CallState.Ringing)?.notificationData ?: return
|
||||
activeCall.value = null
|
||||
if (activeWakeLock?.isHeld == true) {
|
||||
Timber.tag(tag).d("Releasing partial wakelock after timeout")
|
||||
activeWakeLock.release()
|
||||
}
|
||||
|
||||
|
|
@ -155,11 +164,12 @@ class DefaultActiveCallManager @Inject constructor(
|
|||
|
||||
override suspend fun hungUpCall(callType: CallType) = mutex.withLock {
|
||||
if (activeCall.value?.callType != callType) {
|
||||
Timber.w("Call type $callType does not match the active call type, ignoring")
|
||||
Timber.tag(tag).w("Call type $callType does not match the active call type, ignoring")
|
||||
return
|
||||
}
|
||||
cancelIncomingCallNotification()
|
||||
if (activeWakeLock?.isHeld == true) {
|
||||
Timber.tag(tag).d("Releasing partial wakelock after hang up")
|
||||
activeWakeLock.release()
|
||||
}
|
||||
timedOutCallJob?.cancel()
|
||||
|
|
@ -169,6 +179,7 @@ class DefaultActiveCallManager @Inject constructor(
|
|||
override suspend fun joinedCall(callType: CallType) = mutex.withLock {
|
||||
cancelIncomingCallNotification()
|
||||
if (activeWakeLock?.isHeld == true) {
|
||||
Timber.tag(tag).d("Releasing partial wakelock after joining call")
|
||||
activeWakeLock.release()
|
||||
}
|
||||
timedOutCallJob?.cancel()
|
||||
|
|
@ -181,6 +192,7 @@ class DefaultActiveCallManager @Inject constructor(
|
|||
|
||||
@SuppressLint("MissingPermission")
|
||||
private suspend fun showIncomingCallNotification(notificationData: CallNotificationData) {
|
||||
Timber.tag(tag).d("Displaying ringing call notification")
|
||||
val notification = ringingCallNotificationCreator.createNotification(
|
||||
sessionId = notificationData.sessionId,
|
||||
roomId = notificationData.roomId,
|
||||
|
|
@ -204,10 +216,13 @@ class DefaultActiveCallManager @Inject constructor(
|
|||
}
|
||||
|
||||
private fun cancelIncomingCallNotification() {
|
||||
appForegroundStateService.updateHasRingingCall(false)
|
||||
Timber.tag(tag).d("Ringing call notification cancelled")
|
||||
notificationManagerCompat.cancel(NotificationIdProvider.getForegroundServiceNotificationId(ForegroundServiceType.INCOMING_CALL))
|
||||
}
|
||||
|
||||
private fun displayMissedCallNotification(notificationData: CallNotificationData) {
|
||||
Timber.tag(tag).d("Displaying missed call notification")
|
||||
coroutineScope.launch {
|
||||
onMissedCallNotificationHandler.addMissedCallNotification(
|
||||
sessionId = notificationData.sessionId,
|
||||
|
|
@ -227,14 +242,14 @@ class DefaultActiveCallManager @Inject constructor(
|
|||
.flatMapLatest { activeCall ->
|
||||
val callType = activeCall.callType as CallType.RoomCall
|
||||
// Get a flow of updated `hasRoomCall` and `activeRoomCallParticipants` values for the room
|
||||
matrixClientProvider.getOrRestore(callType.sessionId).getOrNull()
|
||||
?.getRoom(callType.roomId)
|
||||
?.roomInfoFlow
|
||||
?.map {
|
||||
Timber.d("Has room call status changed for ringing call: ${it.hasRoomCall}")
|
||||
it.hasRoomCall to (callType.sessionId in it.activeRoomCallParticipants)
|
||||
}
|
||||
?: flowOf()
|
||||
val room = matrixClientProvider.getOrRestore(callType.sessionId).getOrNull()?.getRoom(callType.roomId) ?: run {
|
||||
Timber.tag(tag).d("Couldn't find room for incoming call: $activeCall")
|
||||
return@flatMapLatest flowOf()
|
||||
}
|
||||
room.roomInfoFlow.map {
|
||||
Timber.tag(tag).d("Has room call status changed for ringing call: ${it.hasRoomCall}")
|
||||
it.hasRoomCall to (callType.sessionId in it.activeRoomCallParticipants)
|
||||
}
|
||||
}
|
||||
// We only want to check if the room active call status changes
|
||||
.distinctUntilChanged()
|
||||
|
|
|
|||
|
|
@ -35,6 +35,7 @@ import io.element.android.libraries.push.api.notifications.NotificationIdProvide
|
|||
import io.element.android.libraries.push.test.notifications.FakeImageLoaderHolder
|
||||
import io.element.android.libraries.push.test.notifications.FakeOnMissedCallNotificationHandler
|
||||
import io.element.android.libraries.push.test.notifications.push.FakeNotificationBitmapLoader
|
||||
import io.element.android.services.appnavstate.test.FakeAppForegroundStateService
|
||||
import io.element.android.tests.testutils.lambda.lambdaRecorder
|
||||
import io.element.android.tests.testutils.lambda.value
|
||||
import io.mockk.mockk
|
||||
|
|
@ -323,5 +324,6 @@ class DefaultActiveCallManagerTest {
|
|||
notificationManagerCompat = notificationManagerCompat,
|
||||
matrixClientProvider = matrixClientProvider,
|
||||
defaultCurrentCallService = DefaultCurrentCallService(),
|
||||
appForegroundStateService = FakeAppForegroundStateService(),
|
||||
)
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue