fix(deps): update dependency org.matrix.rustcomponents:sdk-android to v25.4.7 (#4548)

* fix(deps): update dependency org.matrix.rustcomponents:sdk-android to v25.4.8

* Fix API breaks:

- Add `ReplyParameters` class and parameters to send functions.
- Remove outdated OIDC related values.
- Stop pre-processing the timeline to add the timeline start item, this is already done by the SDK.

* Use the new function to reply to messages in a quick reply from a notification, however:

1. We don't have the thread id value at the moment since the SDK does not provide it yet.
2. The replied to event id wasn't being passed from the notification info.

* Remove also timeline start virtual item for DMs, since this wasn't present before either

---------

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: Jorge Martín <jorgem@element.io>
This commit is contained in:
renovate[bot] 2025-04-08 14:21:49 +02:00 committed by GitHub
parent 7bcfb20268
commit b9385ce382
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
35 changed files with 454 additions and 296 deletions

View file

@ -105,7 +105,7 @@ class RustMatrixClientFactory @Inject constructor(
cachePath = sessionPaths.cacheDirectory.absolutePath,
)
.setSessionDelegate(sessionDelegate)
.passphrase(passphrase)
.sessionPassphrase(passphrase)
.userAgent(userAgentProvider.provide())
.addRootCertificates(userCertificatesProvider.provides())
.autoEnableBackups(true)

View file

@ -9,12 +9,9 @@ package io.element.android.libraries.matrix.impl.auth
import io.element.android.libraries.matrix.api.auth.OidcConfig
import org.matrix.rustcomponents.sdk.OidcConfiguration
import java.io.File
import javax.inject.Inject
class OidcConfigurationProvider @Inject constructor(
private val baseDirectory: File,
) {
class OidcConfigurationProvider @Inject constructor() {
fun get(): OidcConfiguration = OidcConfiguration(
clientName = "Element",
redirectUri = OidcConfig.REDIRECT_URI,
@ -29,6 +26,5 @@ class OidcConfigurationProvider @Inject constructor(
staticRegistrations = mapOf(
"https://id.thirdroom.io/realms/thirdroom" to "elementx",
),
dynamicRegistrationsFile = File(baseDirectory, "oidc/registrations.json").absolutePath,
)
}

View file

@ -324,7 +324,7 @@ class RustMatrixAuthenticationService @Inject constructor(
passphrase = pendingPassphrase,
slidingSyncType = ClientBuilderSlidingSync.Discovered,
)
.passphrase(passphrase)
.sessionPassphrase(passphrase)
.buildWithQrCode(qrCodeData, oidcConfiguration, progressListener)
}

View file

@ -36,6 +36,8 @@ class NotificationMapper(
)
NotificationData(
eventId = eventId,
// FIXME once the `NotificationItem` in the SDK returns the thread id
threadId = null,
roomId = roomId,
senderAvatarUrl = item.senderInfo.avatarUrl,
senderDisplayName = item.senderInfo.displayName,

View file

@ -43,6 +43,7 @@ import io.element.android.libraries.matrix.api.room.history.RoomHistoryVisibilit
import io.element.android.libraries.matrix.api.room.join.JoinRule
import io.element.android.libraries.matrix.api.room.knock.KnockRequest
import io.element.android.libraries.matrix.api.room.location.AssetType
import io.element.android.libraries.matrix.api.room.message.ReplyParameters
import io.element.android.libraries.matrix.api.room.powerlevels.MatrixRoomPowerLevels
import io.element.android.libraries.matrix.api.room.powerlevels.UserRoleChange
import io.element.android.libraries.matrix.api.room.roomNotificationSettings
@ -497,8 +498,17 @@ class RustMatrixRoom(
caption: String?,
formattedCaption: String?,
progressCallback: ProgressCallback?,
replyParameters: ReplyParameters?,
): Result<MediaUploadHandler> {
return liveTimeline.sendImage(file, thumbnailFile, imageInfo, caption, formattedCaption, progressCallback)
return liveTimeline.sendImage(
file = file,
thumbnailFile = thumbnailFile,
imageInfo = imageInfo,
caption = caption,
formattedCaption = formattedCaption,
progressCallback = progressCallback,
replyParameters = replyParameters
)
}
override suspend fun sendVideo(
@ -508,8 +518,17 @@ class RustMatrixRoom(
caption: String?,
formattedCaption: String?,
progressCallback: ProgressCallback?,
replyParameters: ReplyParameters?,
): Result<MediaUploadHandler> {
return liveTimeline.sendVideo(file, thumbnailFile, videoInfo, caption, formattedCaption, progressCallback)
return liveTimeline.sendVideo(
file = file,
thumbnailFile = thumbnailFile,
videoInfo = videoInfo,
caption = caption,
formattedCaption = formattedCaption,
progressCallback = progressCallback,
replyParameters = replyParameters
)
}
override suspend fun sendAudio(
@ -518,6 +537,7 @@ class RustMatrixRoom(
caption: String?,
formattedCaption: String?,
progressCallback: ProgressCallback?,
replyParameters: ReplyParameters?,
): Result<MediaUploadHandler> {
return liveTimeline.sendAudio(
file = file,
@ -525,6 +545,7 @@ class RustMatrixRoom(
caption = caption,
formattedCaption = formattedCaption,
progressCallback = progressCallback,
replyParameters = replyParameters,
)
}
@ -534,16 +555,44 @@ class RustMatrixRoom(
caption: String?,
formattedCaption: String?,
progressCallback: ProgressCallback?,
replyParameters: ReplyParameters?,
): Result<MediaUploadHandler> {
return liveTimeline.sendFile(
file,
fileInfo,
caption,
formattedCaption,
progressCallback,
file = file,
fileInfo = fileInfo,
caption = caption,
formattedCaption = formattedCaption,
progressCallback = progressCallback,
replyParameters = replyParameters,
)
}
override suspend fun sendVoiceMessage(
file: File,
audioInfo: AudioInfo,
waveform: List<Float>,
progressCallback: ProgressCallback?,
replyParameters: ReplyParameters?,
): Result<MediaUploadHandler> {
return liveTimeline.sendVoiceMessage(
file = file,
audioInfo = audioInfo,
waveform = waveform,
progressCallback = progressCallback,
replyParameters = replyParameters,
)
}
override suspend fun sendLocation(
body: String,
geoUri: String,
description: String?,
zoomLevel: Int?,
assetType: AssetType?,
): Result<Unit> {
return liveTimeline.sendLocation(body, geoUri, description, zoomLevel, assetType)
}
override suspend fun toggleReaction(emoji: String, eventOrTransactionId: EventOrTransactionId): Result<Unit> {
return liveTimeline.toggleReaction(emoji, eventOrTransactionId)
}
@ -631,16 +680,6 @@ class RustMatrixRoom(
}
}
override suspend fun sendLocation(
body: String,
geoUri: String,
description: String?,
zoomLevel: Int?,
assetType: AssetType?,
): Result<Unit> {
return liveTimeline.sendLocation(body, geoUri, description, zoomLevel, assetType)
}
override suspend fun createPoll(
question: String,
answers: List<String>,
@ -674,15 +713,6 @@ class RustMatrixRoom(
return liveTimeline.endPoll(pollStartId, text)
}
override suspend fun sendVoiceMessage(
file: File,
audioInfo: AudioInfo,
waveform: List<Float>,
progressCallback: ProgressCallback?,
): Result<MediaUploadHandler> {
return liveTimeline.sendVoiceMessage(file, audioInfo, waveform, progressCallback)
}
override suspend fun typingNotice(isTyping: Boolean) = withContext(roomDispatcher) {
runCatching {
innerRoom.typingNotice(isTyping)

View file

@ -0,0 +1,16 @@
/*
* Copyright 2025 New Vector Ltd.
*
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
* Please see LICENSE files in the repository root for full details.
*/
package io.element.android.libraries.matrix.impl.room.message
import io.element.android.libraries.matrix.api.room.message.ReplyParameters
fun ReplyParameters.map() = org.matrix.rustcomponents.sdk.ReplyParameters(
eventId = inReplyToEventId.value,
enforceThread = enforceThreadReply,
replyWithinThread = replyWithinThread,
)

View file

@ -22,6 +22,7 @@ import io.element.android.libraries.matrix.api.room.IntentionalMention
import io.element.android.libraries.matrix.api.room.MatrixRoom
import io.element.android.libraries.matrix.api.room.isDm
import io.element.android.libraries.matrix.api.room.location.AssetType
import io.element.android.libraries.matrix.api.room.message.ReplyParameters
import io.element.android.libraries.matrix.api.timeline.MatrixTimelineItem
import io.element.android.libraries.matrix.api.timeline.ReceiptType
import io.element.android.libraries.matrix.api.timeline.Timeline
@ -35,6 +36,7 @@ import io.element.android.libraries.matrix.impl.media.toMSC3246range
import io.element.android.libraries.matrix.impl.poll.toInner
import io.element.android.libraries.matrix.impl.room.RoomContentForwarder
import io.element.android.libraries.matrix.impl.room.location.toInner
import io.element.android.libraries.matrix.impl.room.message.map
import io.element.android.libraries.matrix.impl.timeline.item.event.EventTimelineItemMapper
import io.element.android.libraries.matrix.impl.timeline.item.event.TimelineEventContentMapper
import io.element.android.libraries.matrix.impl.timeline.item.virtual.VirtualTimelineItemMapper
@ -328,7 +330,7 @@ class RustTimeline(
}
override suspend fun replyMessage(
eventId: EventId,
replyParameters: ReplyParameters,
body: String,
htmlBody: String?,
intentionalMentions: List<IntentionalMention>,
@ -336,7 +338,10 @@ class RustTimeline(
): Result<Unit> = withContext(dispatcher) {
runCatching {
val msg = MessageEventContent.from(body, htmlBody, intentionalMentions)
inner.sendReply(msg, eventId.value)
inner.sendReply(
msg = msg,
replyParams = replyParameters.map(),
)
}
}
@ -347,6 +352,7 @@ class RustTimeline(
caption: String?,
formattedCaption: String?,
progressCallback: ProgressCallback?,
replyParameters: ReplyParameters?,
): Result<MediaUploadHandler> {
val useSendQueue = featureFlagsService.isFeatureEnabled(FeatureFlags.MediaUploadOnSendQueue)
return sendAttachment(listOfNotNull(file, thumbnailFile)) {
@ -359,6 +365,7 @@ class RustTimeline(
},
useSendQueue = useSendQueue,
mentions = null,
replyParams = replyParameters?.map(),
),
thumbnailPath = thumbnailFile?.path,
imageInfo = imageInfo.map(),
@ -374,6 +381,7 @@ class RustTimeline(
caption: String?,
formattedCaption: String?,
progressCallback: ProgressCallback?,
replyParameters: ReplyParameters?,
): Result<MediaUploadHandler> {
val useSendQueue = featureFlagsService.isFeatureEnabled(FeatureFlags.MediaUploadOnSendQueue)
return sendAttachment(listOfNotNull(file, thumbnailFile)) {
@ -386,6 +394,7 @@ class RustTimeline(
},
useSendQueue = useSendQueue,
mentions = null,
replyParams = replyParameters?.map(),
),
thumbnailPath = thumbnailFile?.path,
videoInfo = videoInfo.map(),
@ -400,6 +409,7 @@ class RustTimeline(
caption: String?,
formattedCaption: String?,
progressCallback: ProgressCallback?,
replyParameters: ReplyParameters?,
): Result<MediaUploadHandler> {
val useSendQueue = featureFlagsService.isFeatureEnabled(FeatureFlags.MediaUploadOnSendQueue)
return sendAttachment(listOf(file)) {
@ -412,6 +422,7 @@ class RustTimeline(
},
useSendQueue = useSendQueue,
mentions = null,
replyParams = replyParameters?.map(),
),
audioInfo = audioInfo.map(),
progressWatcher = progressCallback?.toProgressWatcher()
@ -425,6 +436,7 @@ class RustTimeline(
caption: String?,
formattedCaption: String?,
progressCallback: ProgressCallback?,
replyParameters: ReplyParameters?,
): Result<MediaUploadHandler> {
val useSendQueue = featureFlagsService.isFeatureEnabled(FeatureFlags.MediaUploadOnSendQueue)
return sendAttachment(listOf(file)) {
@ -437,6 +449,7 @@ class RustTimeline(
},
useSendQueue = useSendQueue,
mentions = null,
replyParams = replyParameters?.map(),
),
fileInfo = fileInfo.map(),
progressWatcher = progressCallback?.toProgressWatcher(),
@ -479,6 +492,32 @@ class RustTimeline(
}
}
override suspend fun sendVoiceMessage(
file: File,
audioInfo: AudioInfo,
waveform: List<Float>,
progressCallback: ProgressCallback?,
replyParameters: ReplyParameters?,
): Result<MediaUploadHandler> {
val useSendQueue = featureFlagsService.isFeatureEnabled(FeatureFlags.MediaUploadOnSendQueue)
return sendAttachment(listOf(file)) {
inner.sendVoiceMessage(
params = UploadParameters(
filename = file.path,
// Maybe allow a caption in the future?
caption = null,
formattedCaption = null,
useSendQueue = useSendQueue,
mentions = null,
replyParams = replyParameters?.map(),
),
audioInfo = audioInfo.map(),
waveform = waveform.toMSC3246range(),
progressWatcher = progressCallback?.toProgressWatcher(),
)
}
}
override suspend fun createPoll(
question: String,
answers: List<String>,
@ -542,30 +581,6 @@ class RustTimeline(
}
}
override suspend fun sendVoiceMessage(
file: File,
audioInfo: AudioInfo,
waveform: List<Float>,
progressCallback: ProgressCallback?,
): Result<MediaUploadHandler> {
val useSendQueue = featureFlagsService.isFeatureEnabled(FeatureFlags.MediaUploadOnSendQueue)
return sendAttachment(listOf(file)) {
inner.sendVoiceMessage(
params = UploadParameters(
filename = file.path,
// Maybe allow a caption in the future?
caption = null,
formattedCaption = null,
useSendQueue = useSendQueue,
mentions = null,
),
audioInfo = audioInfo.map(),
waveform = waveform.toMSC3246range(),
progressWatcher = progressCallback?.toProgressWatcher(),
)
}
}
private fun sendAttachment(files: List<File>, handle: () -> SendAttachmentJoinHandle): Result<MediaUploadHandler> {
return runCatching {
MediaUploadHandlerImpl(files, handle())

View file

@ -15,6 +15,7 @@ class VirtualTimelineItemMapper {
return when (virtualTimelineItem) {
is RustVirtualTimelineItem.DateDivider -> VirtualTimelineItem.DayDivider(virtualTimelineItem.ts.toLong())
RustVirtualTimelineItem.ReadMarker -> VirtualTimelineItem.ReadMarker
RustVirtualTimelineItem.TimelineStart -> VirtualTimelineItem.RoomBeginning
}
}
}

View file

@ -7,8 +7,6 @@
package io.element.android.libraries.matrix.impl.timeline.postprocessor
import androidx.annotation.VisibleForTesting
import io.element.android.libraries.matrix.api.core.UniqueId
import io.element.android.libraries.matrix.api.core.UserId
import io.element.android.libraries.matrix.api.timeline.MatrixTimelineItem
import io.element.android.libraries.matrix.api.timeline.Timeline
@ -32,55 +30,59 @@ class RoomBeginningPostProcessor(private val mode: Timeline.Mode) {
return when {
items.isEmpty() -> items
mode == Timeline.Mode.PINNED_EVENTS -> items
isDm -> processForDM(items, roomCreator)
isDm -> processForDM(items, roomCreator, hasMoreToLoadBackwards)
hasMoreToLoadBackwards -> items
else -> processForRoom(items)
}
}
private fun processForRoom(items: List<MatrixTimelineItem>): List<MatrixTimelineItem> {
val roomBeginningItem = createRoomBeginningItem()
return listOf(roomBeginningItem) + items
// No changes needed, timeline start item is already added by the SDK
return items
}
private fun processForDM(items: List<MatrixTimelineItem>, roomCreator: UserId?): List<MatrixTimelineItem> {
private fun processForDM(items: List<MatrixTimelineItem>, roomCreator: UserId?, hasMoreToLoadBackwards: Boolean): List<MatrixTimelineItem> {
val roomBeginningItemIndex = if (!hasMoreToLoadBackwards) {
items.indexOfFirst { it is MatrixTimelineItem.Virtual && it.virtual is VirtualTimelineItem.RoomBeginning }.takeIf { it >= 0 }
} else {
null
}
// Find room creation event.
// This is usually the first MatrixTimelineItem.Event (so index 1, index 0 is a date)
val roomCreationEventIndex = items.indexOfFirst {
val stateEventContent = (it as? MatrixTimelineItem.Event)?.event?.content as? StateContent
stateEventContent?.content is OtherState.RoomCreate
}
}.takeIf { it >= 0 }
// If the parameter roomCreator is null, the creator is the sender of the RoomCreate Event.
val roomCreatorUserId = roomCreator ?: (items.getOrNull(roomCreationEventIndex) as? MatrixTimelineItem.Event)?.event?.sender
val roomCreatorUserId = roomCreator ?: roomCreationEventIndex?.let {
(items.getOrNull(it) as? MatrixTimelineItem.Event)?.event?.sender
}
// Find self-join event for the room creator.
// This is usually the second MatrixTimelineItem.Event (so index 2)
val selfUserJoinedEventIndex = roomCreatorUserId?.let { creatorUserId ->
items.indexOfFirst {
val stateEventContent = (it as? MatrixTimelineItem.Event)?.event?.content as? RoomMembershipContent
stateEventContent?.change == MembershipChange.JOINED && stateEventContent.userId == creatorUserId
}
} ?: -1
}.takeIf { it >= 0 }
}
if (roomCreationEventIndex == -1 && selfUserJoinedEventIndex == -1) {
val indicesToRemove = listOfNotNull(
roomBeginningItemIndex,
roomCreationEventIndex,
selfUserJoinedEventIndex,
)
if (indicesToRemove.isEmpty()) {
// Nothing to do, return the list as is
return items
}
// Remove items at the indices we found
val newItems = items.toMutableList()
if (selfUserJoinedEventIndex in newItems.indices) {
newItems.removeAt(selfUserJoinedEventIndex)
}
if (roomCreationEventIndex in newItems.indices) {
newItems.removeAt(roomCreationEventIndex)
indicesToRemove.sortedDescending().forEach { index ->
newItems.removeAt(index)
}
return newItems
}
@VisibleForTesting
fun createRoomBeginningItem(): MatrixTimelineItem.Virtual {
return MatrixTimelineItem.Virtual(
uniqueId = UniqueId("RoomBeginning"),
virtual = VirtualTimelineItem.RoomBeginning
)
}
}

View file

@ -10,12 +10,11 @@ package io.element.android.libraries.matrix.impl.auth
import com.google.common.truth.Truth.assertThat
import io.element.android.libraries.matrix.api.auth.OidcConfig
import org.junit.Test
import java.io.File
class OidcConfigurationProviderTest {
@Test
fun get() {
val result = OidcConfigurationProvider(File("/base")).get()
val result = OidcConfigurationProvider().get()
assertThat(result.redirectUri).isEqualTo(OidcConfig.REDIRECT_URI)
}
}

View file

@ -48,7 +48,7 @@ class RustMatrixAuthenticationServiceTest {
sessionStore = sessionStore,
rustMatrixClientFactory = rustMatrixClientFactory,
passphraseGenerator = FakePassphraseGenerator(),
oidcConfigurationProvider = OidcConfigurationProvider(baseDirectory),
oidcConfigurationProvider = OidcConfigurationProvider(),
)
}
}

View file

@ -30,7 +30,7 @@ class FakeRustClientBuilder : ClientBuilder(NoPointer) {
override fun roomDecryptionTrustRequirement(trustRequirement: TrustRequirement) = this
override fun disableSslVerification() = this
override fun homeserverUrl(url: String) = this
override fun passphrase(passphrase: String?) = this
override fun sessionPassphrase(passphrase: String?) = this
override fun proxy(url: String) = this
override fun requestConfig(config: RequestConfig) = this
override fun roomKeyRecipientStrategy(strategy: CollectStrategy) = this

View file

@ -19,6 +19,10 @@ import io.element.android.libraries.matrix.test.A_USER_ID_2
import io.element.android.libraries.matrix.test.timeline.aMessageContent
import io.element.android.libraries.matrix.test.timeline.anEventTimelineItem
internal val timelineStartEvent = MatrixTimelineItem.Virtual(
uniqueId = UniqueId("timeline_start"),
virtual = VirtualTimelineItem.RoomBeginning,
)
internal val roomCreateEvent = MatrixTimelineItem.Event(
uniqueId = UniqueId("m.room.create"),
event = anEventTimelineItem(sender = A_USER_ID, content = StateContent("", OtherState.RoomCreate))

View file

@ -50,8 +50,9 @@ class RoomBeginningPostProcessorTest {
}
@Test
fun `processor removes room creation event and self-join event from DM timeline`() {
fun `processor removes timeline start, room creation event and self-join event from DM timeline`() {
val timelineItems = listOf(
timelineStartEvent,
roomCreateEvent,
roomCreatorJoinEvent,
)
@ -98,43 +99,6 @@ class RoomBeginningPostProcessorTest {
assertThat(processedItems).isEqualTo(expected)
}
@Test
fun `processor will add beginning of room item if it's not a DM`() {
val timelineItems = listOf(
roomCreateEvent,
roomCreatorJoinEvent,
)
val processor = RoomBeginningPostProcessor(Timeline.Mode.LIVE)
val processedItems = processor.process(timelineItems, isDm = false, roomCreator = A_USER_ID, hasMoreToLoadBackwards = false)
assertThat(processedItems).isEqualTo(
listOf(processor.createRoomBeginningItem()) + timelineItems
)
}
@Test
fun `processor will not add beginning of room item if it's not a DM but the room has more to load`() {
val timelineItems = listOf(
roomCreateEvent,
roomCreatorJoinEvent,
)
val processor = RoomBeginningPostProcessor(Timeline.Mode.LIVE)
val processedItems = processor.process(timelineItems, isDm = false, roomCreator = A_USER_ID, hasMoreToLoadBackwards = true)
assertThat(processedItems).isEqualTo(timelineItems)
}
@Test
fun `processor will add beginning of room item if it's not a DM, when the parameter roomCreator is null`() {
val timelineItems = listOf(
roomCreateEvent,
roomCreatorJoinEvent,
)
val processor = RoomBeginningPostProcessor(Timeline.Mode.LIVE)
val processedItems = processor.process(timelineItems, isDm = false, roomCreator = null, hasMoreToLoadBackwards = false)
assertThat(processedItems).isEqualTo(
listOf(processor.createRoomBeginningItem()) + timelineItems
)
}
@Test
fun `processor removes items event it's not at the start of the timeline`() {
val timelineItems = listOf(