Add ActiveRoomsHolder to manage the active rooms for a session (#4758)

This commit is contained in:
Jorge Martin Espinosa 2025-05-26 13:03:55 +02:00 committed by GitHub
parent 630e1d19c0
commit 9b9d75aa5f
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
19 changed files with 240 additions and 30 deletions

View file

@ -23,6 +23,7 @@ import io.element.android.libraries.push.api.notifications.NotificationCleaner
import io.element.android.libraries.push.impl.R
import io.element.android.libraries.push.impl.notifications.model.NotifiableMessageEvent
import io.element.android.libraries.push.impl.push.OnNotifiableEventReceived
import io.element.android.services.appnavstate.api.ActiveRoomsHolder
import io.element.android.services.toolbox.api.strings.StringProvider
import io.element.android.services.toolbox.api.systemclock.SystemClock
import kotlinx.coroutines.CoroutineScope
@ -44,6 +45,7 @@ class NotificationBroadcastReceiverHandler @Inject constructor(
private val onNotifiableEventReceived: OnNotifiableEventReceived,
private val stringProvider: StringProvider,
private val replyMessageExtractor: ReplyMessageExtractor,
private val activeRoomsHolder: ActiveRoomsHolder,
) {
fun onReceive(intent: Intent) {
val sessionId = intent.getStringExtra(NotificationBroadcastReceiver.KEY_SESSION_ID)?.let(::SessionId) ?: return
@ -117,13 +119,15 @@ class NotificationBroadcastReceiverHandler @Inject constructor(
return@launch
}
val client = matrixClientProvider.getOrRestore(sessionId).getOrNull() ?: return@launch
client.getJoinedRoom(roomId)?.let { room ->
val room = activeRoomsHolder.getActiveRoomMatching(sessionId, roomId) ?: client.getJoinedRoom(roomId)
room?.let {
sendMatrixEvent(
sessionId = sessionId,
roomId = roomId,
replyToEventId = replyToEventId,
threadId = threadId,
room = room,
room = it,
message = message,
)
}

View file

@ -17,6 +17,7 @@ import io.element.android.libraries.matrix.api.room.JoinedRoom
import io.element.android.libraries.matrix.api.timeline.MatrixTimelineItem
import io.element.android.libraries.push.impl.notifications.model.NotifiableEvent
import io.element.android.libraries.push.impl.notifications.model.NotifiableRingingCallEvent
import io.element.android.services.appnavstate.api.ActiveRoomsHolder
import io.element.android.services.appnavstate.api.AppForegroundStateService
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.withContext
@ -30,28 +31,43 @@ class SyncOnNotifiableEvent @Inject constructor(
private val featureFlagService: FeatureFlagService,
private val appForegroundStateService: AppForegroundStateService,
private val dispatchers: CoroutineDispatchers,
private val activeRoomsHolder: ActiveRoomsHolder,
) {
suspend operator fun invoke(notifiableEvent: NotifiableEvent) = withContext(dispatchers.io) {
val isRingingCallEvent = notifiableEvent is NotifiableRingingCallEvent
if (!featureFlagService.isFeatureEnabled(FeatureFlags.SyncOnPush) && !isRingingCallEvent) {
return@withContext
}
val client = matrixClientProvider.getOrRestore(notifiableEvent.sessionId).getOrNull() ?: return@withContext
client.getJoinedRoom(notifiableEvent.roomId)?.use { room ->
room.subscribeToSync()
val activeRoom = activeRoomsHolder.getActiveRoomMatching(notifiableEvent.sessionId, notifiableEvent.roomId)
// If the app is in foreground, sync is already running, so we just add the subscription above.
if (!appForegroundStateService.isInForeground.value) {
if (isRingingCallEvent) {
room.waitsUntilUserIsInTheCall(timeout = 60.seconds)
} else {
try {
appForegroundStateService.updateIsSyncingNotificationEvent(true)
room.waitsUntilEventIsKnown(eventId = notifiableEvent.eventId, timeout = 10.seconds)
} finally {
appForegroundStateService.updateIsSyncingNotificationEvent(false)
}
if (activeRoom != null) {
// If the room is already active, we can use it directly
activeRoom.subscribeToSyncAndWait(notifiableEvent, isRingingCallEvent)
} else {
// Otherwise, we need to get the room from the matrix client
val room = matrixClientProvider
.getOrRestore(notifiableEvent.sessionId)
.mapCatching { it.getJoinedRoom(notifiableEvent.roomId) }
.getOrNull()
room?.use { it.subscribeToSyncAndWait(notifiableEvent, isRingingCallEvent) }
}
}
private suspend fun JoinedRoom.subscribeToSyncAndWait(notifiableEvent: NotifiableEvent, isRingingCallEvent: Boolean) {
subscribeToSync()
// If the app is in foreground, sync is already running, so we just add the subscription above.
if (!appForegroundStateService.isInForeground.value) {
if (isRingingCallEvent) {
waitsUntilUserIsInTheCall(timeout = 60.seconds)
} else {
try {
appForegroundStateService.updateIsSyncingNotificationEvent(true)
waitsUntilEventIsKnown(eventId = notifiableEvent.eventId, timeout = 10.seconds)
} finally {
appForegroundStateService.updateIsSyncingNotificationEvent(false)
}
}
}

View file

@ -41,6 +41,7 @@ import io.element.android.libraries.push.impl.notifications.model.NotifiableEven
import io.element.android.libraries.push.impl.push.FakeOnNotifiableEventReceived
import io.element.android.libraries.push.impl.push.OnNotifiableEventReceived
import io.element.android.libraries.push.test.notifications.FakeNotificationCleaner
import io.element.android.services.appnavstate.api.ActiveRoomsHolder
import io.element.android.services.toolbox.api.strings.StringProvider
import io.element.android.services.toolbox.api.systemclock.SystemClock
import io.element.android.services.toolbox.test.strings.FakeStringProvider
@ -477,6 +478,7 @@ class NotificationBroadcastReceiverHandlerTest {
onNotifiableEventReceived: OnNotifiableEventReceived = FakeOnNotifiableEventReceived(),
stringProvider: StringProvider = FakeStringProvider(),
replyMessageExtractor: ReplyMessageExtractor = FakeReplyMessageExtractor(),
activeRoomsHolder: ActiveRoomsHolder = ActiveRoomsHolder(),
): NotificationBroadcastReceiverHandler {
return NotificationBroadcastReceiverHandler(
appCoroutineScope = this,
@ -494,6 +496,7 @@ class NotificationBroadcastReceiverHandlerTest {
onNotifiableEventReceived = onNotifiableEventReceived,
stringProvider = stringProvider,
replyMessageExtractor = replyMessageExtractor,
activeRoomsHolder = activeRoomsHolder,
)
}
}

View file

@ -26,6 +26,7 @@ import io.element.android.libraries.matrix.test.timeline.FakeTimeline
import io.element.android.libraries.matrix.test.timeline.anEventTimelineItem
import io.element.android.libraries.push.impl.notifications.fixtures.aNotifiableCallEvent
import io.element.android.libraries.push.impl.notifications.fixtures.aNotifiableMessageEvent
import io.element.android.services.appnavstate.api.ActiveRoomsHolder
import io.element.android.services.appnavstate.test.FakeAppForegroundStateService
import io.element.android.tests.testutils.lambda.assert
import io.element.android.tests.testutils.lambda.lambdaRecorder
@ -199,7 +200,8 @@ class SyncOnNotifiableEventTest {
isSyncOnPushEnabled: Boolean = true,
appForegroundStateService: FakeAppForegroundStateService = FakeAppForegroundStateService(
initialForegroundValue = true,
)
),
activeRoomsHolder: ActiveRoomsHolder = ActiveRoomsHolder(),
): SyncOnNotifiableEvent {
val featureFlagService = FakeFeatureFlagService(
initialState = mapOf(
@ -212,6 +214,7 @@ class SyncOnNotifiableEventTest {
featureFlagService = featureFlagService,
appForegroundStateService = appForegroundStateService,
dispatchers = testCoroutineDispatchers(),
activeRoomsHolder = activeRoomsHolder,
)
}
}