@@ -28,9 +28,7 @@ import io.element.android.libraries.core.extensions.runCatchingExceptions
2828import io.element.android.libraries.di.annotations.AppCoroutineScope
2929import io.element.android.libraries.di.annotations.ApplicationContext
3030import io.element.android.libraries.matrix.api.MatrixClientProvider
31- import io.element.android.libraries.matrix.api.core.EventId
3231import io.element.android.libraries.matrix.api.core.SessionId
33- import io.element.android.libraries.matrix.api.room.BaseRoom
3432import io.element.android.libraries.matrix.ui.media.ImageLoaderHolder
3533import io.element.android.libraries.push.api.notifications.ForegroundServiceType
3634import io.element.android.libraries.push.api.notifications.NotificationIdProvider
@@ -41,17 +39,16 @@ import kotlinx.coroutines.CoroutineScope
4139import kotlinx.coroutines.ExperimentalCoroutinesApi
4240import kotlinx.coroutines.Job
4341import kotlinx.coroutines.delay
44- import kotlinx.coroutines.flow.Flow
4542import kotlinx.coroutines.flow.MutableStateFlow
4643import kotlinx.coroutines.flow.StateFlow
4744import kotlinx.coroutines.flow.distinctUntilChanged
4845import kotlinx.coroutines.flow.drop
4946import kotlinx.coroutines.flow.filter
47+ import kotlinx.coroutines.flow.filterNotNull
5048import kotlinx.coroutines.flow.flatMapLatest
5149import kotlinx.coroutines.flow.flowOf
5250import kotlinx.coroutines.flow.launchIn
5351import kotlinx.coroutines.flow.map
54- import kotlinx.coroutines.flow.mapLatest
5552import kotlinx.coroutines.flow.onEach
5653import kotlinx.coroutines.launch
5754import 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)
0 commit comments