Skip to content

Commit

Permalink
Merge pull request #3842 from element-hq/feature/bma/stopIncomingcall
Browse files Browse the repository at this point in the history
Stop incoming call ringing if answered on another device.
  • Loading branch information
bmarty authored Nov 12, 2024
2 parents 4548648 + f372295 commit 4721380
Show file tree
Hide file tree
Showing 3 changed files with 24 additions and 20 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
package io.element.android.features.call.impl.utils

import android.annotation.SuppressLint
import androidx.annotation.VisibleForTesting
import androidx.core.app.NotificationManagerCompat
import com.squareup.anvil.annotations.ContributesBinding
import io.element.android.appconfig.ElementCallConfig
Expand Down Expand Up @@ -56,11 +57,6 @@ interface ActiveCallManager {
*/
fun registerIncomingCall(notificationData: CallNotificationData)

/**
* Called when the incoming call timed out. It will remove the active call and remove any associated UI, adding a 'missed call' notification.
*/
fun incomingCallTimedOut()

/**
* Called when the active call has been hung up. It will remove any existing UI and the active call.
* @param callType The type of call that the user hung up, either an external url one or a room one.
Expand Down Expand Up @@ -113,18 +109,24 @@ class DefaultActiveCallManager @Inject constructor(

// Wait for the ringing call to time out
delay(ElementCallConfig.RINGING_CALL_DURATION_SECONDS.seconds)
incomingCallTimedOut()
incomingCallTimedOut(displayMissedCallNotification = true)
}
}

override fun incomingCallTimedOut() {
/**
* Called when the incoming call timed out. It will remove the active call and remove any associated UI, adding a 'missed call' notification.
*/
@VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
fun incomingCallTimedOut(displayMissedCallNotification: Boolean) {
val previousActiveCall = activeCall.value ?: return
val notificationData = (previousActiveCall.callState as? CallState.Ringing)?.notificationData ?: return
activeCall.value = null

cancelIncomingCallNotification()

displayMissedCallNotification(notificationData)
if (displayMissedCallNotification) {
displayMissedCallNotification(notificationData)
}
}

override fun hungUpCall(callType: CallType) {
Expand Down Expand Up @@ -186,28 +188,35 @@ class DefaultActiveCallManager @Inject constructor(

@OptIn(ExperimentalCoroutinesApi::class)
private fun observeRingingCall() {
// This will observe ringing calls and ensure they're terminated if the room call is cancelled
// This will observe ringing calls and ensure they're terminated if the room call is cancelled or if the user
// has joined the call from another session.
activeCall
.filterNotNull()
.filter { it.callState is CallState.Ringing && it.callType is CallType.RoomCall }
.flatMapLatest { activeCall ->
val callType = activeCall.callType as CallType.RoomCall
// Get a flow of updated `hasRoomCall` values for the room
// Get a flow of updated `hasRoomCall` and `activeRoomCallParticipants` values for the room
matrixClientProvider.getOrRestore(callType.sessionId).getOrNull()
?.getRoom(callType.roomId)
?.roomInfoFlow
?.map { it.hasRoomCall }
?.map {
it.hasRoomCall to (callType.sessionId in it.activeRoomCallParticipants)
}
?: flowOf()
}
// We only want to check if the room active call status changes
.distinctUntilChanged()
// Skip the first one, we're not interested in it (if the check below passes, it had to be active anyway)
.drop(1)
.onEach { roomHasActiveCall ->
.onEach { (roomHasActiveCall, userIsInTheCall) ->
if (!roomHasActiveCall) {
// The call was cancelled
timedOutCallJob?.cancel()
incomingCallTimedOut()
incomingCallTimedOut(displayMissedCallNotification = true)
} else if (userIsInTheCall) {
// The user joined the call from another session
timedOutCallJob?.cancel()
incomingCallTimedOut(displayMissedCallNotification = false)
}
}
.launchIn(coroutineScope)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,7 @@ class DefaultActiveCallManagerTest {
onMissedCallNotificationHandler = FakeOnMissedCallNotificationHandler(addMissedCallNotificationLambda = addMissedCallNotificationLambda)
)

manager.incomingCallTimedOut()
manager.incomingCallTimedOut(displayMissedCallNotification = true)

addMissedCallNotificationLambda.assertions().isNeverCalled()
}
Expand All @@ -139,7 +139,7 @@ class DefaultActiveCallManagerTest {
manager.registerIncomingCall(aCallNotificationData())
assertThat(manager.activeCall.value).isNotNull()

manager.incomingCallTimedOut()
manager.incomingCallTimedOut(displayMissedCallNotification = true)
advanceTimeBy(1)

assertThat(manager.activeCall.value).isNull()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@ import kotlinx.coroutines.flow.MutableStateFlow

class FakeActiveCallManager(
var registerIncomingCallResult: (CallNotificationData) -> Unit = {},
var incomingCallTimedOutResult: () -> Unit = {},
var hungUpCallResult: (CallType) -> Unit = {},
var joinedCallResult: (CallType) -> Unit = {},
) : ActiveCallManager {
Expand All @@ -25,10 +24,6 @@ class FakeActiveCallManager(
registerIncomingCallResult(notificationData)
}

override fun incomingCallTimedOut() {
incomingCallTimedOutResult()
}

override fun hungUpCall(callType: CallType) {
hungUpCallResult(callType)
}
Expand Down

0 comments on commit 4721380

Please sign in to comment.