Delegate call notifications to Element Call, upgrade SDK and EC embedded (#5119)

* Stop sending call notifications manually: the Element Call widget can now assume responsibility for sending them when you start a call.

* Upgrade SDK version to `v25.8.5`, fix API breaks

* Upgrade Element Call embedded to `v0.14.1`

* Fix tests and lint issues

* Add `RoomListEntriesDynamicFilterKind.NonSpace` to avoid displaying spaces in the room list

---------

Co-authored-by: Robin <robin@robin.town>
This commit is contained in:
Jorge Martin Espinosa 2025-08-05 17:14:51 +02:00 committed by GitHub
parent bfdcc97985
commit dff295eadf
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
17 changed files with 64 additions and 174 deletions

View file

@ -33,14 +33,11 @@ import io.element.android.libraries.architecture.Presenter
import io.element.android.libraries.architecture.runCatchingUpdatingState
import io.element.android.libraries.core.coroutine.CoroutineDispatchers
import io.element.android.libraries.di.annotations.AppCoroutineScope
import io.element.android.libraries.matrix.api.MatrixClient
import io.element.android.libraries.matrix.api.MatrixClientProvider
import io.element.android.libraries.matrix.api.core.RoomId
import io.element.android.libraries.matrix.api.sync.SyncState
import io.element.android.libraries.matrix.api.widget.MatrixWidgetDriver
import io.element.android.libraries.network.useragent.UserAgentProvider
import io.element.android.services.analytics.api.ScreenTracker
import io.element.android.services.appnavstate.api.ActiveRoomsHolder
import io.element.android.services.appnavstate.api.AppForegroundStateService
import io.element.android.services.toolbox.api.systemclock.SystemClock
import kotlinx.coroutines.CoroutineScope
@ -64,7 +61,6 @@ class CallScreenPresenter @AssistedInject constructor(
private val activeCallManager: ActiveCallManager,
private val languageTagProvider: LanguageTagProvider,
private val appForegroundStateService: AppForegroundStateService,
private val activeRoomsHolder: ActiveRoomsHolder,
@AppCoroutineScope
private val appCoroutineScope: CoroutineScope,
) : Presenter<CallScreenState> {
@ -75,7 +71,6 @@ class CallScreenPresenter @AssistedInject constructor(
private val isInWidgetMode = callType is CallType.RoomCall
private val userAgent = userAgentProvider.provide()
private var notifiedCallStart = false
@Composable
override fun present(): CallScreenState {
@ -248,9 +243,7 @@ class CallScreenPresenter @AssistedInject constructor(
Timber.d("Observing sync state in-call for sessionId: ${roomCallType.sessionId}")
client.syncService().syncState
.collect { state ->
if (state == SyncState.Running) {
client.notifyCallStartIfNeeded(callType.roomId)
} else {
if (state != SyncState.Running) {
appForegroundStateService.updateIsInCallState(true)
}
}
@ -263,32 +256,6 @@ class CallScreenPresenter @AssistedInject constructor(
}
}
private suspend fun MatrixClient.notifyCallStartIfNeeded(roomId: RoomId) {
if (notifiedCallStart) return
val activeRoomForSession = activeRoomsHolder.getActiveRoomMatching(sessionId, roomId)
val sendCallNotificationResult = if (activeRoomForSession != null) {
Timber.d("Notifying call start for room $roomId. Has room call: ${activeRoomForSession.info().hasRoomCall}")
activeRoomForSession.sendCallNotificationIfNeeded()
} else {
// Instantiate the room from the session and roomId and send the notification
getJoinedRoom(roomId)?.use { room ->
Timber.d("Notifying call start for room $roomId. Has room call: ${room.info().hasRoomCall}")
room.sendCallNotificationIfNeeded()
} ?: run {
Timber.w("No room found for session $sessionId and room $roomId, skipping call notification.")
return
}
}
sendCallNotificationResult.fold(
onSuccess = { notifiedCallStart = true },
onFailure = { error ->
Timber.e(error, "Failed to send call notification for room $roomId.")
}
)
}
private fun parseMessage(message: String): WidgetMessage? {
return WidgetMessageSerializer.deserialize(message).getOrNull()
}

View file

@ -13,6 +13,7 @@ import io.element.android.libraries.di.AppScope
import io.element.android.libraries.matrix.api.MatrixClientProvider
import io.element.android.libraries.matrix.api.core.RoomId
import io.element.android.libraries.matrix.api.core.SessionId
import io.element.android.libraries.matrix.api.room.isDm
import io.element.android.libraries.matrix.api.widget.CallWidgetSettingsProvider
import io.element.android.libraries.preferences.api.store.AppPreferencesStore
import io.element.android.services.appnavstate.api.ActiveRoomsHolder
@ -44,7 +45,7 @@ class DefaultCallWidgetProvider @Inject constructor(
val baseUrl = customBaseUrl ?: EMBEDDED_CALL_WIDGET_BASE_URL
val isEncrypted = room.info().isEncrypted ?: room.getUpdatedIsEncrypted().getOrThrow()
val widgetSettings = callWidgetSettingsProvider.provide(baseUrl, encrypted = isEncrypted)
val widgetSettings = callWidgetSettingsProvider.provide(baseUrl, encrypted = isEncrypted, direct = room.isDm())
val callUrl = room.generateWidgetWebViewUrl(
widgetSettings = widgetSettings,
clientId = clientId,

View file

@ -26,13 +26,11 @@ import io.element.android.libraries.matrix.test.A_ROOM_ID
import io.element.android.libraries.matrix.test.A_SESSION_ID
import io.element.android.libraries.matrix.test.FakeMatrixClient
import io.element.android.libraries.matrix.test.FakeMatrixClientProvider
import io.element.android.libraries.matrix.test.room.FakeJoinedRoom
import io.element.android.libraries.matrix.test.sync.FakeSyncService
import io.element.android.libraries.matrix.test.widget.FakeMatrixWidgetDriver
import io.element.android.libraries.network.useragent.UserAgentProvider
import io.element.android.services.analytics.api.ScreenTracker
import io.element.android.services.analytics.test.FakeScreenTracker
import io.element.android.services.appnavstate.api.ActiveRoomsHolder
import io.element.android.services.appnavstate.test.FakeAppForegroundStateService
import io.element.android.services.toolbox.api.systemclock.SystemClock
import io.element.android.tests.testutils.WarmUpRule
@ -82,19 +80,12 @@ import kotlin.time.Duration.Companion.seconds
}
@Test
fun `present - with CallType RoomCall sets call as active, loads URL, runs WidgetDriver and notifies the other clients a call started`() = runTest {
val sendCallNotificationIfNeededLambda = lambdaRecorder<Result<Boolean>> { Result.success(true) }
val syncService = FakeSyncService(SyncState.Running)
val fakeRoom = FakeJoinedRoom(sendCallNotificationIfNeededResult = sendCallNotificationIfNeededLambda)
val client = FakeMatrixClient(syncService = syncService).apply {
givenGetRoomResult(A_ROOM_ID, fakeRoom)
}
fun `present - with CallType RoomCall sets call as active, loads URL and runs WidgetDriver`() = runTest {
val widgetDriver = FakeMatrixWidgetDriver()
val widgetProvider = FakeCallWidgetProvider(widgetDriver)
val analyticsLambda = lambdaRecorder<MobileScreen.ScreenName, Unit> {}
val joinedCallLambda = lambdaRecorder<CallType, Unit> {}
val presenter = createCallScreenPresenter(
matrixClientsProvider = FakeMatrixClientProvider(getClient = { Result.success(client) }),
callType = CallType.RoomCall(A_SESSION_ID, A_ROOM_ID),
widgetDriver = widgetDriver,
widgetProvider = widgetProvider,
@ -116,7 +107,6 @@ import kotlin.time.Duration.Companion.seconds
assertThat(widgetProvider.getWidgetCalled).isTrue()
assertThat(widgetDriver.runCalledCount).isEqualTo(1)
analyticsLambda.assertions().isCalledOnce().with(value(MobileScreen.ScreenName.RoomCall))
sendCallNotificationIfNeededLambda.assertions().isCalledOnce()
// Wait until the WidgetDriver is loaded
skipItems(1)
@ -399,7 +389,6 @@ import kotlin.time.Duration.Companion.seconds
activeCallManager: FakeActiveCallManager = FakeActiveCallManager(),
screenTracker: ScreenTracker = FakeScreenTracker(),
appForegroundStateService: FakeAppForegroundStateService = FakeAppForegroundStateService(),
activeRoomsHolder: ActiveRoomsHolder = ActiveRoomsHolder(),
): CallScreenPresenter {
val userAgentProvider = object : UserAgentProvider {
override fun provide(): String {
@ -420,7 +409,6 @@ import kotlin.time.Duration.Companion.seconds
languageTagProvider = FakeLanguageTagProvider("en-US"),
appForegroundStateService = appForegroundStateService,
appCoroutineScope = backgroundScope,
activeRoomsHolder = activeRoomsHolder,
)
}
}