code review

This commit is contained in:
Valere 2022-11-23 19:08:17 +01:00
parent d759f26db6
commit e5d3206b6f
14 changed files with 366 additions and 100 deletions

View file

@ -19,7 +19,7 @@ import com.squareup.moshi.Json
import com.squareup.moshi.JsonClass
/**
* <code>
* ```
* {
* "m.annotation": {
* "chunk": [
@ -43,7 +43,7 @@ import com.squareup.moshi.JsonClass
* "count": 1
* }
* }
* </code>
* ```
*/
@JsonClass(generateAdapter = true)

View file

@ -16,6 +16,9 @@
package org.matrix.android.sdk.api.session.events.model
import org.matrix.android.sdk.api.session.room.model.message.MessageRelationContent
import org.matrix.android.sdk.api.session.room.model.relation.RelationDefaultContent
data class ValidDecryptedEvent(
val type: String,
val eventId: String,
@ -29,25 +32,6 @@ data class ValidDecryptedEvent(
val algorithm: String,
)
fun Event.toValidDecryptedEvent(): ValidDecryptedEvent? {
if (!this.isEncrypted()) return null
val decryptedContent = this.getDecryptedContent() ?: return null
val eventId = this.eventId ?: return null
val roomId = this.roomId ?: return null
val type = this.getDecryptedType() ?: return null
val senderKey = this.getSenderKey() ?: return null
val algorithm = this.content?.get("algorithm") as? String ?: return null
return ValidDecryptedEvent(
type = type,
eventId = eventId,
clearContent = decryptedContent,
prevContent = this.prevContent,
originServerTs = this.originServerTs ?: 0,
cryptoSenderKey = senderKey,
roomId = roomId,
unsignedData = this.unsignedData,
redacts = this.redacts,
algorithm = algorithm
)
fun ValidDecryptedEvent.getRelationContent(): RelationDefaultContent? {
return clearContent.toModel<MessageRelationContent?>()?.relatesTo
}

View file

@ -0,0 +1,45 @@
/*
* Copyright 2020 The Matrix.org Foundation C.I.C.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.matrix.android.sdk.internal.database.mapper
import org.matrix.android.sdk.api.session.room.model.EditAggregatedSummary
import org.matrix.android.sdk.internal.database.model.EditAggregatedSummaryEntity
import org.matrix.android.sdk.internal.database.model.EditionOfEvent
internal object EditAggregatedSummaryEntityMapper {
fun map(summary: EditAggregatedSummaryEntity?): EditAggregatedSummary? {
summary ?: return null
/**
* The most recent event is determined by comparing origin_server_ts;
* if two or more replacement events have identical origin_server_ts,
* the event with the lexicographically largest event_id is treated as more recent.
*/
val latestEdition = summary.editions.sortedWith(compareBy<EditionOfEvent> { it.timestamp }.thenBy { it.eventId })
.lastOrNull() ?: return null
val editEvent = latestEdition.event
return EditAggregatedSummary(
latestEdit = editEvent?.asDomain(),
sourceEvents = summary.editions.filter { editionOfEvent -> !editionOfEvent.isLocalEcho }
.map { editionOfEvent -> editionOfEvent.eventId },
localEchos = summary.editions.filter { editionOfEvent -> editionOfEvent.isLocalEcho }
.map { editionOfEvent -> editionOfEvent.eventId },
lastEditTs = latestEdition.timestamp
)
}
}

View file

@ -16,11 +16,9 @@
package org.matrix.android.sdk.internal.database.mapper
import org.matrix.android.sdk.api.session.room.model.EditAggregatedSummary
import org.matrix.android.sdk.api.session.room.model.EventAnnotationsSummary
import org.matrix.android.sdk.api.session.room.model.ReactionAggregatedSummary
import org.matrix.android.sdk.api.session.room.model.ReferencesAggregatedSummary
import org.matrix.android.sdk.internal.database.model.EditionOfEvent
import org.matrix.android.sdk.internal.database.model.EventAnnotationsSummaryEntity
internal object EventAnnotationsSummaryMapper {
@ -36,27 +34,7 @@ internal object EventAnnotationsSummaryMapper {
it.sourceLocalEcho.toList()
)
},
editSummary = annotationsSummary.editSummary
?.let { summary ->
/**
* The most recent event is determined by comparing origin_server_ts;
* if two or more replacement events have identical origin_server_ts,
* the event with the lexicographically largest event_id is treated as more recent.
*/
val latestEdition = summary.editions.sortedWith(compareBy<EditionOfEvent> { it.timestamp }.thenBy { it.eventId })
.lastOrNull() ?: return@let null
// get the event and validate?
val editEvent = latestEdition.event
EditAggregatedSummary(
latestEdit = editEvent?.asDomain(),
sourceEvents = summary.editions.filter { editionOfEvent -> !editionOfEvent.isLocalEcho }
.map { editionOfEvent -> editionOfEvent.eventId },
localEchos = summary.editions.filter { editionOfEvent -> editionOfEvent.isLocalEcho }
.map { editionOfEvent -> editionOfEvent.eventId },
lastEditTs = latestEdition.timestamp
)
},
editSummary = EditAggregatedSummaryEntityMapper.map(annotationsSummary.editSummary),
referencesAggregatedSummary = annotationsSummary.referencesSummaryEntity?.let {
ReferencesAggregatedSummary(
ContentMapper.map(it.content),

View file

@ -25,11 +25,11 @@ internal class MigrateSessionTo008(realm: DynamicRealm) : RealmMigrator(realm, 8
override fun doMigrate(realm: DynamicRealm) {
val editionOfEventSchema = realm.schema.create("EditionOfEvent")
.addField("content"/**EditionOfEventFields.CONTENT*/, String::class.java)
.addField("content", String::class.java)
.addField(EditionOfEventFields.EVENT_ID, String::class.java)
.setRequired(EditionOfEventFields.EVENT_ID, true)
.addField("senderId" /*EditionOfEventFields.SENDER_ID*/, String::class.java)
.setRequired("senderId" /*EditionOfEventFields.SENDER_ID*/, true)
.addField("senderId", String::class.java)
.setRequired("senderId", true)
.addField(EditionOfEventFields.TIMESTAMP, Long::class.java)
.addField(EditionOfEventFields.IS_LOCAL_ECHO, Boolean::class.java)

View file

@ -17,6 +17,7 @@
package org.matrix.android.sdk.internal.session.events
import org.matrix.android.sdk.api.session.events.model.Event
import org.matrix.android.sdk.api.session.events.model.ValidDecryptedEvent
import org.matrix.android.sdk.api.session.events.model.toModel
import org.matrix.android.sdk.api.session.room.model.RoomMemberContent
@ -33,3 +34,32 @@ internal fun Event.getFixedRoomMemberContent(): RoomMemberContent? {
content
}
}
fun Event.toValidDecryptedEvent(): ValidDecryptedEvent? {
if (!this.isEncrypted()) return null
val decryptedContent = this.getDecryptedContent() ?: return null
val eventId = this.eventId ?: return null
val roomId = this.roomId ?: return null
val type = this.getDecryptedType() ?: return null
val senderKey = this.getSenderKey() ?: return null
val algorithm = this.content?.get("algorithm") as? String ?: return null
// copy the relation as it's in clear in the encrypted content
val updatedContent = this.content.get("m.relates_to")?.let {
decryptedContent.toMutableMap().apply {
put("m.relates_to", it)
}
} ?: decryptedContent
return ValidDecryptedEvent(
type = type,
eventId = eventId,
clearContent = updatedContent,
prevContent = this.prevContent,
originServerTs = this.originServerTs ?: 0,
cryptoSenderKey = senderKey,
roomId = roomId,
unsignedData = this.unsignedData,
redacts = this.redacts,
algorithm = algorithm
)
}

View file

@ -17,11 +17,14 @@
package org.matrix.android.sdk.internal.session.room
import org.matrix.android.sdk.api.session.events.model.Event
import org.matrix.android.sdk.api.session.events.model.LocalEcho
import org.matrix.android.sdk.api.session.events.model.RelationType
import org.matrix.android.sdk.api.session.events.model.getRelationContent
import org.matrix.android.sdk.api.session.events.model.toModel
import org.matrix.android.sdk.api.session.events.model.toValidDecryptedEvent
import org.matrix.android.sdk.api.session.room.model.message.MessageContent
import org.matrix.android.sdk.internal.crypto.store.IMXCryptoStore
import org.matrix.android.sdk.internal.session.events.toValidDecryptedEvent
import timber.log.Timber
import javax.inject.Inject
internal class EventEditValidator @Inject constructor(val cryptoStore: IMXCryptoStore) {
@ -47,12 +50,18 @@ internal class EventEditValidator @Inject constructor(val cryptoStore: IMXCrypto
* If the original event was encrypted, the replacement should be too.
*/
fun validateEdit(originalEvent: Event?, replaceEvent: Event): EditValidity {
Timber.v("###REPLACE valide event $originalEvent replaced $replaceEvent")
// we might not know the original event at that time. In this case we can't perform the validation
// Edits should be revalidated when the original event is received
if (originalEvent == null) {
return EditValidity.Unknown
}
if (LocalEcho.isLocalEchoId(replaceEvent.eventId.orEmpty())) {
// Don't validate local echo
return EditValidity.Unknown
}
if (originalEvent.roomId != replaceEvent.roomId) {
return EditValidity.Invalid("original event and replacement event must have the same room_id")
}
@ -71,7 +80,7 @@ internal class EventEditValidator @Inject constructor(val cryptoStore: IMXCrypto
val originalCryptoSenderId = cryptoStore.deviceWithIdentityKey(originalDecrypted.cryptoSenderKey)?.userId
val editCryptoSenderId = cryptoStore.deviceWithIdentityKey(replaceDecrypted.cryptoSenderKey)?.userId
if (originalDecrypted.clearContent.toModel<MessageContent>()?.relatesTo?.type == RelationType.REPLACE) {
if (originalDecrypted.getRelationContent()?.type == RelationType.REPLACE) {
return EditValidity.Invalid("The original event must not, itself, have a rel_type of m.replace ")
}
@ -96,7 +105,7 @@ internal class EventEditValidator @Inject constructor(val cryptoStore: IMXCrypto
return EditValidity.Invalid("replacement event must have an m.new_content property")
}
} else {
if (originalEvent.content.toModel<MessageContent>()?.relatesTo?.type == RelationType.REPLACE) {
if (originalEvent.getRelationContent()?.type == RelationType.REPLACE) {
return EditValidity.Invalid("The original event must not, itself, have a rel_type of m.replace ")
}

View file

@ -23,6 +23,7 @@ import org.matrix.android.sdk.api.session.events.model.Event
import org.matrix.android.sdk.api.session.events.model.EventType
import org.matrix.android.sdk.api.session.events.model.LocalEcho
import org.matrix.android.sdk.api.session.events.model.RelationType
import org.matrix.android.sdk.api.session.events.model.content.EncryptedEventContent
import org.matrix.android.sdk.api.session.events.model.getRelationContent
import org.matrix.android.sdk.api.session.events.model.toContent
import org.matrix.android.sdk.api.session.events.model.toModel
@ -81,13 +82,12 @@ internal class EventRelationsAggregationProcessor @Inject constructor(
EventType.REDACTION,
EventType.REACTION,
// The aggregator handles verification events but just to render tiles in the timeline
// It's not participating in verfication it's self, just timeline display
// It's not participating in verification itself, just timeline display
EventType.KEY_VERIFICATION_DONE,
EventType.KEY_VERIFICATION_CANCEL,
EventType.KEY_VERIFICATION_ACCEPT,
EventType.KEY_VERIFICATION_START,
EventType.KEY_VERIFICATION_MAC,
// TODO Add ?
EventType.KEY_VERIFICATION_READY,
EventType.KEY_VERIFICATION_KEY,
EventType.ENCRYPTED
@ -168,28 +168,28 @@ internal class EventRelationsAggregationProcessor @Inject constructor(
// As for now Live event processors are not receiving UTD events.
// They will get an update if the event is decrypted later
EventType.ENCRYPTED -> {
// // Relation type is in clear, it might be possible to do some things?
// // Notice that if the event is decrypted later, process be called again
// val encryptedEventContent = event.content.toModel<EncryptedEventContent>()
// when (encryptedEventContent?.relatesTo?.type) {
// RelationType.REPLACE -> {
// Timber.v("###REPLACE in room $roomId for event ${event.eventId}")
// // A replace!
// handleReplace(realm, event, roomId, isLocalEcho, encryptedEventContent.relatesTo.eventId)
// }
// RelationType.RESPONSE -> {
// // can we / should we do we something for UTD response??
// Timber.w("## UTD response in room $roomId related to ${encryptedEventContent.relatesTo.eventId}")
// }
// RelationType.REFERENCE -> {
// // can we / should we do we something for UTD reference??
// Timber.w("## UTD reference in room $roomId related to ${encryptedEventContent.relatesTo.eventId}")
// }
// RelationType.ANNOTATION -> {
// // can we / should we do we something for UTD annotation??
// Timber.w("## UTD annotation in room $roomId related to ${encryptedEventContent.relatesTo.eventId}")
// }
// }
// Relation type is in clear, it might be possible to do some things?
// Notice that if the event is decrypted later, process be called again
val encryptedEventContent = event.content.toModel<EncryptedEventContent>()
when (encryptedEventContent?.relatesTo?.type) {
RelationType.REPLACE -> {
Timber.v("###REPLACE in room $roomId for event ${event.eventId}")
// A replace!
handleReplace(realm, event, roomId, isLocalEcho, encryptedEventContent.relatesTo.eventId)
}
RelationType.RESPONSE -> {
// can we / should we do we something for UTD response??
Timber.w("## UTD response in room $roomId related to ${encryptedEventContent.relatesTo.eventId}")
}
RelationType.REFERENCE -> {
// can we / should we do we something for UTD reference??
Timber.w("## UTD reference in room $roomId related to ${encryptedEventContent.relatesTo.eventId}")
}
RelationType.ANNOTATION -> {
// can we / should we do we something for UTD annotation??
Timber.w("## UTD annotation in room $roomId related to ${encryptedEventContent.relatesTo.eventId}")
}
}
}
EventType.REDACTION -> {
val eventToPrune = event.redacts?.let { EventEntity.where(realm, eventId = it).findFirst() }
@ -256,7 +256,7 @@ internal class EventRelationsAggregationProcessor @Inject constructor(
relatedEventId: String?
) {
val eventId = event.eventId ?: return
val targetEventId = relatedEventId ?: return // ?: content.relatesTo?.eventId ?: return
val targetEventId = relatedEventId ?: return
val editedEvent = EventEntity.where(realm, targetEventId).findFirst()
when (val validity = editValidator.validateEdit(editedEvent?.asDomain(), event)) {
@ -301,15 +301,16 @@ internal class EventRelationsAggregationProcessor @Inject constructor(
// ok it has already been managed
Timber.v("###REPLACE Receiving remote echo of edit (edit already done)")
existingSummary.editions.firstOrNull { it.eventId == txId }?.let {
it.eventId = event.eventId
it.eventId = eventId
it.timestamp = event.originServerTs ?: clock.epochMillis()
it.isLocalEcho = false
it.event = EventEntity.where(realm, eventId).findFirst()
}
} else {
Timber.v("###REPLACE Computing aggregated edit summary (isLocalEcho:$isLocalEcho)")
existingSummary.editions.add(
EditionOfEvent(
eventId = event.eventId,
eventId = eventId,
event = EventEntity.where(realm, eventId).findFirst(),
timestamp = if (isLocalEcho) {
clock.epochMillis()
@ -343,7 +344,8 @@ internal class EventRelationsAggregationProcessor @Inject constructor(
* @param editions list of edition of event
*/
private fun handleThreadSummaryEdition(
editedEvent: EventEntity?, replaceEvent: TimelineEventEntity?,
editedEvent: EventEntity?,
replaceEvent: TimelineEventEntity?,
editions: List<EditionOfEvent>?
) {
replaceEvent ?: return

View file

@ -0,0 +1,78 @@
/*
* Copyright (c) 2022 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.matrix.android.sdk.internal.database.mapper
import io.mockk.every
import io.mockk.mockk
import io.realm.RealmList
import org.amshove.kluent.shouldBe
import org.amshove.kluent.shouldNotBe
import org.junit.Test
import org.matrix.android.sdk.internal.database.model.EditAggregatedSummaryEntity
import org.matrix.android.sdk.internal.database.model.EditionOfEvent
import org.matrix.android.sdk.internal.database.model.EventEntity
class EditAggregationSummaryMapperTest {
@Test
fun test() {
val edits = RealmList<EditionOfEvent>(
EditionOfEvent(
timestamp = 0L,
eventId = "e0",
isLocalEcho = false,
event = mockEvent("e0")
),
EditionOfEvent(
timestamp = 1L,
eventId = "e1",
isLocalEcho = false,
event = mockEvent("e1")
),
EditionOfEvent(
timestamp = 30L,
eventId = "e2",
isLocalEcho = true,
event = mockEvent("e2")
)
)
val fakeSummaryEntity = mockk<EditAggregatedSummaryEntity> {
every { editions } returns edits
}
val mapped = EditAggregatedSummaryEntityMapper.map(fakeSummaryEntity)
mapped shouldNotBe null
mapped!!.sourceEvents.size shouldBe 2
mapped.localEchos.size shouldBe 1
mapped.localEchos.first() shouldBe "e2"
mapped.lastEditTs shouldBe 30L
mapped.latestEdit?.eventId shouldBe "e2"
}
private fun mockEvent(eventId: String): EventEntity {
return EventEntity().apply {
this.eventId = eventId
this.content = """
{
"body" : "Hello",
"msgtype": "text"
}
""".trimIndent()
}
}
}

View file

@ -0,0 +1,134 @@
/*
* Copyright (c) 2022 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.matrix.android.sdk.internal.session.event
import org.amshove.kluent.shouldBe
import org.amshove.kluent.shouldNotBe
import org.junit.Test
import org.matrix.android.sdk.api.crypto.MXCRYPTO_ALGORITHM_MEGOLM
import org.matrix.android.sdk.api.session.crypto.model.OlmDecryptionResult
import org.matrix.android.sdk.api.session.events.model.Event
import org.matrix.android.sdk.api.session.events.model.EventType
import org.matrix.android.sdk.api.session.events.model.content.EncryptedEventContent
import org.matrix.android.sdk.api.session.events.model.toContent
import org.matrix.android.sdk.api.session.events.model.toModel
import org.matrix.android.sdk.api.session.room.model.message.MessageTextContent
import org.matrix.android.sdk.internal.session.events.toValidDecryptedEvent
class ValidDecryptedEventTest {
val fakeEvent = Event(
type = EventType.ENCRYPTED,
eventId = "\$eventId",
roomId = "!fakeRoom",
content = EncryptedEventContent(
algorithm = MXCRYPTO_ALGORITHM_MEGOLM,
ciphertext = "AwgBEpACQEKOkd4Gp0+gSXG4M+btcrnPgsF23xs/lUmS2I4YjmqF...",
sessionId = "TO2G4u2HlnhtbIJk",
senderKey = "5e3EIqg3JfooZnLQ2qHIcBarbassQ4qXblai0",
deviceId = "FAKEE"
).toContent()
)
@Test
fun `A failed to decrypt message should give a null validated decrypted event`() {
fakeEvent.toValidDecryptedEvent() shouldBe null
}
@Test
fun `Mismatch sender key detection`() {
val decryptedEvent = fakeEvent
.apply {
mxDecryptionResult = OlmDecryptionResult(
payload = mapOf(
"type" to EventType.MESSAGE,
"content" to mapOf(
"body" to "some message",
"msgtype" to "m.text"
),
),
senderKey = "the_real_sender_key",
)
}
val validDecryptedEvent = decryptedEvent.toValidDecryptedEvent()
validDecryptedEvent shouldNotBe null
fakeEvent.content!!["senderKey"] shouldNotBe "the_real_sender_key"
validDecryptedEvent!!.cryptoSenderKey shouldBe "the_real_sender_key"
}
@Test
fun `Mixed content event should be detected`() {
val mixedEvent = Event(
type = EventType.ENCRYPTED,
eventId = "\$eventd ",
roomId = "!fakeRoo",
content = mapOf(
"algorithm" to "m.megolm.v1.aes-sha2",
"ciphertext" to "AwgBEpACQEKOkd4Gp0+gSXG4M+btcrnPgsF23xs/lUmS2I4YjmqF...",
"sessionId" to "TO2G4u2HlnhtbIJk",
"senderKey" to "5e3EIqg3JfooZnLQ2qHIcBarbassQ4qXblai0",
"deviceId" to "FAKEE",
"body" to "some message",
"msgtype" to "m.text"
).toContent()
)
val unValidatedContent = mixedEvent.getClearContent().toModel<MessageTextContent>()
unValidatedContent?.body shouldBe "some message"
mixedEvent.toValidDecryptedEvent()?.clearContent?.toModel<MessageTextContent>() shouldBe null
}
@Test
fun `Basic field validation`() {
val decryptedEvent = fakeEvent
.apply {
mxDecryptionResult = OlmDecryptionResult(
payload = mapOf(
"type" to EventType.MESSAGE,
"content" to mapOf(
"body" to "some message",
"msgtype" to "m.text"
),
),
senderKey = "the_real_sender_key",
)
}
decryptedEvent.toValidDecryptedEvent() shouldNotBe null
decryptedEvent.copy(roomId = null).toValidDecryptedEvent() shouldBe null
decryptedEvent.copy(eventId = null).toValidDecryptedEvent() shouldBe null
}
@Test
fun `A clear event is not a valid decrypted event`() {
val mockTextEvent = Event(
type = EventType.MESSAGE,
eventId = "eventId",
roomId = "!fooe:example.com",
content = mapOf(
"body" to "some message",
"msgtype" to "m.text"
),
originServerTs = 1000,
senderId = "@anne:example.com",
)
mockTextEvent.toValidDecryptedEvent() shouldBe null
}
}

View file

@ -26,7 +26,7 @@ import org.matrix.android.sdk.api.session.events.model.Event
import org.matrix.android.sdk.api.session.events.model.EventType
import org.matrix.android.sdk.internal.crypto.store.IMXCryptoStore
class EditValidationTest {
class EventEditValidatorTest {
private val mockTextEvent = Event(
type = EventType.MESSAGE,
@ -180,17 +180,23 @@ class EditValidationTest {
validator
.validateEdit(
encryptedEvent.copy().apply {
encryptedEvent.copy(
content = encryptedEvent.content!!.toMutableMap().apply {
put(
"m.relates_to",
mapOf(
"rel_type" to "m.replace",
"event_id" to mockTextEvent.eventId
)
)
}
).apply {
mxDecryptionResult = encryptedEditEvent.mxDecryptionResult!!.copy(
payload = mapOf(
"type" to EventType.MESSAGE,
"content" to mapOf(
"body" to "some message",
"msgtype" to "m.text",
"m.relates_to" to mapOf(
"rel_type" to "m.replace",
"event_id" to mockTextEvent.eventId
)
),
)
)

View file

@ -19,7 +19,6 @@ package org.matrix.android.sdk.test.fakes
import io.mockk.coEvery
import io.mockk.mockk
import io.mockk.mockkStatic
import io.mockk.slot
import io.realm.Realm
import io.realm.RealmConfiguration
import org.matrix.android.sdk.internal.database.awaitTransaction
@ -33,8 +32,7 @@ internal class FakeRealmConfiguration {
val instance = mockk<RealmConfiguration>()
fun <T> givenAwaitTransaction(realm: Realm) {
val transaction = slot<(Realm) -> T>()
coEvery { awaitTransaction(instance, capture(transaction)) } answers {
coEvery { awaitTransaction(instance, any<(Realm) -> T>()) } answers {
secondArg<(Realm) -> T>().invoke(realm)
}
}

View file

@ -161,7 +161,7 @@ class MessageItemFactory @Inject constructor(
val callback = params.callback
event.root.eventId ?: return null
roomId = event.roomId
val informationData = messageInformationDataFactory.create(params, params.event.annotations?.editSummary?.latestEdit)
val informationData = messageInformationDataFactory.create(params)
val threadDetails = if (params.isFromThreadTimeline()) null else event.root.threadDetails
if (event.root.isRedacted()) {

View file

@ -37,7 +37,6 @@ import org.matrix.android.sdk.api.session.events.model.getMsgType
import org.matrix.android.sdk.api.session.events.model.isAttachmentMessage
import org.matrix.android.sdk.api.session.events.model.isSticker
import org.matrix.android.sdk.api.session.events.model.toModel
import org.matrix.android.sdk.api.session.events.model.toValidDecryptedEvent
import org.matrix.android.sdk.api.session.room.model.ReferencesAggregatedContent
import org.matrix.android.sdk.api.session.room.model.RoomSummary
import org.matrix.android.sdk.api.session.room.model.message.MessageType
@ -45,6 +44,7 @@ import org.matrix.android.sdk.api.session.room.model.message.MessageVerification
import org.matrix.android.sdk.api.session.room.send.SendState
import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent
import org.matrix.android.sdk.api.session.room.timeline.hasBeenEdited
import org.matrix.android.sdk.internal.session.events.toValidDecryptedEvent
import javax.inject.Inject
/**
@ -57,7 +57,7 @@ class MessageInformationDataFactory @Inject constructor(
private val reactionsSummaryFactory: ReactionsSummaryFactory
) {
fun create(params: TimelineItemFactoryParams, lastEdit: Event? = null): MessageInformationData {
fun create(params: TimelineItemFactoryParams): MessageInformationData {
val event = params.event
val nextDisplayableEvent = params.nextDisplayableEvent
val prevDisplayableEvent = params.prevDisplayableEvent
@ -74,14 +74,8 @@ class MessageInformationDataFactory @Inject constructor(
prevDisplayableEvent?.root?.localDateTime()?.toLocalDate() != date.toLocalDate()
val time = dateFormatter.format(event.root.originServerTs, DateFormatKind.MESSAGE_SIMPLE)
val e2eDecoration = getE2EDecoration(roomSummary, lastEdit ?: event.root)
val senderId = if (event.isEncrypted()) {
event.root.toValidDecryptedEvent()?.let {
session.cryptoService().deviceWithIdentityKey(it.cryptoSenderKey, it.algorithm)?.userId
} ?: event.root.senderId.orEmpty()
} else {
event.root.senderId.orEmpty()
}
val e2eDecoration = getE2EDecoration(roomSummary, params.lastEdit ?: event.root)
val senderId = getSenderId(event)
// SendState Decoration
val sendStateDecoration = if (isSentByMe) {
getSendStateDecoration(
@ -139,6 +133,14 @@ class MessageInformationDataFactory @Inject constructor(
)
}
private fun getSenderId(event: TimelineEvent) = if (event.isEncrypted()) {
event.root.toValidDecryptedEvent()?.let {
session.cryptoService().deviceWithIdentityKey(it.cryptoSenderKey, it.algorithm)?.userId
} ?: event.root.senderId.orEmpty()
} else {
event.root.senderId.orEmpty()
}
private fun getSendStateDecoration(
event: TimelineEvent,
lastSentEventWithoutReadReceipts: String?,