Skip to content

Commit 568146a

Browse files
committed
Revert "Make sure declining a call stops observing the ringing call state (#5…" (#5615)
This reverts commit 6512631.
1 parent acbf988 commit 568146a

File tree

2 files changed

+64
-117
lines changed

2 files changed

+64
-117
lines changed

features/call/impl/src/main/kotlin/io/element/android/features/call/impl/utils/ActiveCallManager.kt

Lines changed: 64 additions & 72 deletions
Original file line numberDiff line numberDiff line change
@@ -28,9 +28,7 @@ import io.element.android.libraries.core.extensions.runCatchingExceptions
2828
import io.element.android.libraries.di.annotations.AppCoroutineScope
2929
import io.element.android.libraries.di.annotations.ApplicationContext
3030
import io.element.android.libraries.matrix.api.MatrixClientProvider
31-
import io.element.android.libraries.matrix.api.core.EventId
3231
import io.element.android.libraries.matrix.api.core.SessionId
33-
import io.element.android.libraries.matrix.api.room.BaseRoom
3432
import io.element.android.libraries.matrix.ui.media.ImageLoaderHolder
3533
import io.element.android.libraries.push.api.notifications.ForegroundServiceType
3634
import io.element.android.libraries.push.api.notifications.NotificationIdProvider
@@ -41,17 +39,16 @@ import kotlinx.coroutines.CoroutineScope
4139
import kotlinx.coroutines.ExperimentalCoroutinesApi
4240
import kotlinx.coroutines.Job
4341
import kotlinx.coroutines.delay
44-
import kotlinx.coroutines.flow.Flow
4542
import kotlinx.coroutines.flow.MutableStateFlow
4643
import kotlinx.coroutines.flow.StateFlow
4744
import kotlinx.coroutines.flow.distinctUntilChanged
4845
import kotlinx.coroutines.flow.drop
4946
import kotlinx.coroutines.flow.filter
47+
import kotlinx.coroutines.flow.filterNotNull
5048
import kotlinx.coroutines.flow.flatMapLatest
5149
import kotlinx.coroutines.flow.flowOf
5250
import kotlinx.coroutines.flow.launchIn
5351
import kotlinx.coroutines.flow.map
54-
import kotlinx.coroutines.flow.mapLatest
5552
import kotlinx.coroutines.flow.onEach
5653
import kotlinx.coroutines.launch
5754
import kotlinx.coroutines.sync.Mutex
@@ -183,7 +180,13 @@ class DefaultActiveCallManager(
183180

184181
val previousActiveCall = activeCall.value ?: return
185182
val notificationData = (previousActiveCall.callState as? CallState.Ringing)?.notificationData ?: return
186-
removeCurrentCall()
183+
activeCall.value = null
184+
if (activeWakeLock?.isHeld == true) {
185+
Timber.tag(tag).d("Releasing partial wakelock after timeout")
186+
activeWakeLock.release()
187+
}
188+
189+
cancelIncomingCallNotification()
187190

188191
if (displayMissedCallNotification) {
189192
displayMissedCallNotification(notificationData)
@@ -208,40 +211,31 @@ class DefaultActiveCallManager(
208211
?.declineCall(notificationData.eventId)
209212
}
210213

211-
removeCurrentCall()
214+
cancelIncomingCallNotification()
215+
if (activeWakeLock?.isHeld == true) {
216+
Timber.tag(tag).d("Releasing partial wakelock after hang up")
217+
activeWakeLock.release()
218+
}
219+
timedOutCallJob?.cancel()
220+
activeCall.value = null
212221
}
213222

214-
/**
215-
* Removes the current active call and any associated UI, cancelling the timeouts and wakelocks.
216-
*/
217223
override suspend fun joinedCall(callType: CallType) = mutex.withLock {
218224
Timber.tag(tag).d("Joined call: $callType")
219225

220-
removeCurrentCall()
226+
cancelIncomingCallNotification()
227+
if (activeWakeLock?.isHeld == true) {
228+
Timber.tag(tag).d("Releasing partial wakelock after joining call")
229+
activeWakeLock.release()
230+
}
231+
timedOutCallJob?.cancel()
221232

222233
activeCall.value = ActiveCall(
223234
callType = callType,
224235
callState = CallState.InCall,
225236
)
226237
}
227238

228-
@VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
229-
internal fun removeCurrentCall() {
230-
// Cancel and remove the timeout call job, if any
231-
timedOutCallJob?.cancel()
232-
timedOutCallJob = null
233-
234-
// Remove the active call and cancel the notification
235-
activeCall.value = null
236-
cancelIncomingCallNotification()
237-
238-
// Also remove any wake locks that may be held
239-
if (activeWakeLock?.isHeld == true) {
240-
Timber.tag(tag).d("Releasing partial wakelock after call declined from another session")
241-
activeWakeLock.release()
242-
}
243-
}
244-
245239
@SuppressLint("MissingPermission")
246240
private suspend fun showIncomingCallNotification(notificationData: CallNotificationData) {
247241
Timber.tag(tag).d("Displaying ringing call notification")
@@ -287,75 +281,73 @@ class DefaultActiveCallManager(
287281

288282
@OptIn(ExperimentalCoroutinesApi::class)
289283
private fun observeRingingCall() {
290-
val roomForActiveCallFlow: Flow<Pair<BaseRoom, EventId>?> = activeCall.mapLatest { activeCall ->
291-
val callType = activeCall?.callType as? CallType.RoomCall ?: return@mapLatest null
292-
val ringingInfo = activeCall.callState as? CallState.Ringing ?: return@mapLatest null
293-
val client = matrixClientProvider.getOrRestore(callType.sessionId).getOrNull() ?: run {
294-
Timber.tag(tag).d("Couldn't find session for incoming call: $activeCall")
295-
return@mapLatest null
296-
}
297-
val room = client.getRoom(callType.roomId) ?: run {
298-
Timber.tag(tag).d("Couldn't find room for incoming call: $activeCall")
299-
return@mapLatest null
300-
}
301-
302-
Timber.tag(tag).d("Found room for ringing call: ${room.roomId}")
303-
304-
val eventId = ringingInfo.notificationData.eventId
305-
room to eventId
306-
}
284+
activeCall
285+
.filterNotNull()
286+
.filter { it.callState is CallState.Ringing && it.callType is CallType.RoomCall }
287+
.flatMapLatest { activeCall ->
288+
val callType = activeCall.callType as CallType.RoomCall
289+
val ringingInfo = activeCall.callState as CallState.Ringing
290+
val client = matrixClientProvider.getOrRestore(callType.sessionId).getOrNull() ?: run {
291+
Timber.tag(tag).d("Couldn't find session for incoming call: $activeCall")
292+
return@flatMapLatest flowOf()
293+
}
294+
val room = client.getRoom(callType.roomId) ?: run {
295+
Timber.tag(tag).d("Couldn't find room for incoming call: $activeCall")
296+
return@flatMapLatest flowOf()
297+
}
307298

308-
roomForActiveCallFlow
309-
.flatMapLatest { pair ->
310-
val (room, eventId) = pair
311-
// This will cancel the previous iteration of flatMapLatest if the active call is now null
312-
?: return@flatMapLatest flowOf()
299+
Timber.tag(tag).d("Found room for ringing call: ${room.roomId}")
313300

314301
// If we have declined from another phone we want to stop ringing.
315-
room.subscribeToCallDecline(eventId)
302+
room.subscribeToCallDecline(ringingInfo.notificationData.eventId)
316303
.filter { decliner ->
317304
Timber.tag(tag).d("Call: $activeCall was declined by $decliner")
318305
// only want to listen if the call was declined from another of my sessions,
319306
// (we are ringing for an incoming call in a DM)
320-
decliner == room.sessionId
307+
decliner == client.sessionId
321308
}
322309
}
323310
.onEach { decliner ->
324311
Timber.tag(tag).d("Call: $activeCall was declined by user from another session")
325-
removeCurrentCall()
312+
// Remove the active call and cancel the notification
313+
activeCall.value = null
314+
if (activeWakeLock?.isHeld == true) {
315+
Timber.tag(tag).d("Releasing partial wakelock after call declined from another session")
316+
activeWakeLock.release()
317+
}
318+
cancelIncomingCallNotification()
326319
}
327320
.launchIn(coroutineScope)
328-
329321
// This will observe ringing calls and ensure they're terminated if the room call is cancelled or if the user
330322
// has joined the call from another session.
331-
roomForActiveCallFlow
332-
.flatMapLatest { pair ->
333-
val (room, _) = pair
334-
// This will cancel the previous iteration of flatMapLatest if the active call is now null
335-
?: return@flatMapLatest flowOf()
336-
337-
// We now observe the room info for changes to the active call state and the call participants
323+
activeCall
324+
.filterNotNull()
325+
.filter { it.callState is CallState.Ringing && it.callType is CallType.RoomCall }
326+
.flatMapLatest { activeCall ->
327+
val callType = activeCall.callType as CallType.RoomCall
328+
// Get a flow of updated `hasRoomCall` and `activeRoomCallParticipants` values for the room
329+
val room = matrixClientProvider.getOrRestore(callType.sessionId).getOrNull()?.getRoom(callType.roomId) ?: run {
330+
Timber.tag(tag).d("Couldn't find room for incoming call: $activeCall")
331+
return@flatMapLatest flowOf()
332+
}
338333
room.roomInfoFlow.map {
339-
val participants = it.activeRoomCallParticipants
340-
Timber.tag(tag).d("Room call status changed for ringing call | hasRoomCall: ${it.hasRoomCall} | participants: $participants")
341-
val userIsInTheCall = room.sessionId in participants
342-
it.hasRoomCall to userIsInTheCall
334+
Timber.tag(tag).d("Has room call status changed for ringing call: ${it.hasRoomCall}")
335+
it.hasRoomCall to (callType.sessionId in it.activeRoomCallParticipants)
343336
}
344337
}
345-
// Filter out duplicate values
338+
// We only want to check if the room active call status changes
346339
.distinctUntilChanged()
347340
// Skip the first one, we're not interested in it (if the check below passes, it had to be active anyway)
348341
.drop(1)
349342
.onEach { (roomHasActiveCall, userIsInTheCall) ->
350343
if (!roomHasActiveCall) {
351-
val notificationData = (activeCall.value?.callState as? CallState.Ringing)?.notificationData
352-
removeCurrentCall()
353-
354-
if (notificationData != null) {
355-
displayMissedCallNotification(notificationData)
356-
}
344+
// The call was cancelled
345+
timedOutCallJob?.cancel()
346+
incomingCallTimedOut(displayMissedCallNotification = true)
357347
} else if (userIsInTheCall) {
358-
removeCurrentCall()
348+
// The user joined the call from another session
349+
timedOutCallJob?.cancel()
350+
incomingCallTimedOut(displayMissedCallNotification = false)
359351
}
360352
}
361353
.launchIn(coroutineScope)

features/call/impl/src/test/kotlin/io/element/android/features/call/utils/DefaultActiveCallManagerTest.kt

Lines changed: 0 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,6 @@ import io.element.android.libraries.matrix.test.AN_EVENT_ID_2
2828
import io.element.android.libraries.matrix.test.A_ROOM_ID
2929
import io.element.android.libraries.matrix.test.A_ROOM_ID_2
3030
import io.element.android.libraries.matrix.test.A_SESSION_ID
31-
import io.element.android.libraries.matrix.test.A_USER_ID_2
3231
import io.element.android.libraries.matrix.test.FakeMatrixClient
3332
import io.element.android.libraries.matrix.test.FakeMatrixClientProvider
3433
import io.element.android.libraries.matrix.test.room.FakeBaseRoom
@@ -47,7 +46,6 @@ import io.element.android.tests.testutils.lambda.value
4746
import io.element.android.tests.testutils.plantTestTimber
4847
import io.mockk.coVerify
4948
import io.mockk.mockk
50-
import io.mockk.spyk
5149
import io.mockk.verify
5250
import kotlinx.coroutines.ExperimentalCoroutinesApi
5351
import kotlinx.coroutines.test.TestScope
@@ -333,49 +331,6 @@ class DefaultActiveCallManagerTest {
333331
assertThat(manager.activeCall.value).isNull()
334332
}
335333

336-
@OptIn(ExperimentalCoroutinesApi::class)
337-
@Test
338-
fun `observeRingingCalls - declining won't do anything if the call was already cancelled`() = runTest {
339-
val room = FakeBaseRoom().apply {
340-
givenRoomInfo(aRoomInfo())
341-
}
342-
val client = FakeMatrixClient().apply {
343-
givenGetRoomResult(A_ROOM_ID, room)
344-
}
345-
val matrixClientProvider = FakeMatrixClientProvider(getClient = { Result.success(client) })
346-
val notificationManagerCompat = mockk<NotificationManagerCompat>(relaxed = true)
347-
val manager = spyk<DefaultActiveCallManager>(
348-
createActiveCallManager(
349-
matrixClientProvider = matrixClientProvider,
350-
notificationManagerCompat = notificationManagerCompat,
351-
)
352-
)
353-
354-
manager.registerIncomingCall(aCallNotificationData())
355-
356-
// Call is active (the other user join the call)
357-
room.givenRoomInfo(aRoomInfo(hasRoomCall = true))
358-
advanceTimeBy(1)
359-
360-
// Call is cancelled by us, hanging up
361-
manager.hungUpCall(CallType.RoomCall(A_SESSION_ID, A_ROOM_ID))
362-
advanceTimeBy(1)
363-
364-
verify(exactly = 1) { notificationManagerCompat.cancel(any()) }
365-
verify(exactly = 1) { manager.removeCurrentCall() }
366-
assertThat(manager.activeCall.value).isNull()
367-
assertThat(manager.activeWakeLock?.isHeld).isNull()
368-
369-
// Simulate that another user declined the call
370-
room.givenDecliner(A_USER_ID_2, AN_EVENT_ID)
371-
advanceTimeBy(1)
372-
373-
// Check everything stays the same, no extra call to cancelling notifications
374-
verify(exactly = 1) { notificationManagerCompat.cancel(any()) }
375-
verify(exactly = 1) { manager.removeCurrentCall() }
376-
assertThat(manager.activeWakeLock?.isHeld).isNull()
377-
}
378-
379334
@OptIn(ExperimentalCoroutinesApi::class)
380335
@Test
381336
fun `observeRingingCalls - will do nothing if either the session or the room are not found`() = runTest {

0 commit comments

Comments
 (0)