From 8b34caa0b1b773b2e62c433ec0e2f0ea48d91cb8 Mon Sep 17 00:00:00 2001
From: Benoit Marty <benoit@matrix.org>
Date: Mon, 1 Mar 2021 11:51:56 +0100
Subject: [PATCH 01/17] Move capability section above Server name and version
 section

---
 .../settings/homeserver/HomeserverSettingsController.kt         | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/vector/src/main/java/im/vector/app/features/settings/homeserver/HomeserverSettingsController.kt b/vector/src/main/java/im/vector/app/features/settings/homeserver/HomeserverSettingsController.kt
index e90f711edc..514311315d 100644
--- a/vector/src/main/java/im/vector/app/features/settings/homeserver/HomeserverSettingsController.kt
+++ b/vector/src/main/java/im/vector/app/features/settings/homeserver/HomeserverSettingsController.kt
@@ -48,6 +48,7 @@ class HomeserverSettingsController @Inject constructor(
         data ?: return
 
         buildHeader(data)
+        buildCapabilities(data)
         when (val federationVersion = data.federationVersion) {
             is Loading,
             is Uninitialized ->
@@ -63,7 +64,6 @@ class HomeserverSettingsController @Inject constructor(
             is Success       ->
                 buildFederationVersion(federationVersion())
         }
-        buildCapabilities(data)
     }
 
     private fun buildHeader(state: HomeServerSettingsViewState) {

From d3b2306b3d8c45183bf66ba774dec4044469fdc4 Mon Sep 17 00:00:00 2001
From: Benoit Marty <benoit@matrix.org>
Date: Mon, 1 Mar 2021 14:33:52 +0100
Subject: [PATCH 02/17] Remove unused file

---
 .../main/res/layout/view_image_attachment.xml   | 17 -----------------
 1 file changed, 17 deletions(-)
 delete mode 100644 attachment-viewer/src/main/res/layout/view_image_attachment.xml

diff --git a/attachment-viewer/src/main/res/layout/view_image_attachment.xml b/attachment-viewer/src/main/res/layout/view_image_attachment.xml
deleted file mode 100644
index 3518a4472d..0000000000
--- a/attachment-viewer/src/main/res/layout/view_image_attachment.xml
+++ /dev/null
@@ -1,17 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
-    android:layout_width="match_parent"
-    android:layout_height="match_parent"
-    android:background="@color/design_default_color_primary">
-
-    <TextView
-        android:id="@+id/testPage"
-        android:layout_centerInParent="true"
-        android:layout_width="wrap_content"
-        android:layout_height="wrap_content"
-        android:text="1"
-        android:textSize="80sp"
-        android:textStyle="bold" />
-
-
-</RelativeLayout>
\ No newline at end of file

From 9a635dd906e31c8033626da4e927d951b380d9af Mon Sep 17 00:00:00 2001
From: Benoit Marty <benoit@matrix.org>
Date: Mon, 1 Mar 2021 15:10:27 +0100
Subject: [PATCH 03/17] Move VerificationState to a dedicated file and in the
 correct package

---
 .../sdk/api/crypto/VerificationState.kt       | 25 +++++++++++++++++++
 .../room/model/ReferencesAggregatedContent.kt |  2 +-
 .../EventRelationsAggregationProcessor.kt     |  9 +------
 .../factory/VerificationItemFactory.kt        |  2 +-
 .../helper/MessageInformationDataFactory.kt   |  2 +-
 .../timeline/item/MessageInformationData.kt   |  2 +-
 .../timeline/item/VerificationRequestItem.kt  |  2 +-
 7 files changed, 31 insertions(+), 13 deletions(-)
 create mode 100644 matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/crypto/VerificationState.kt

diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/crypto/VerificationState.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/crypto/VerificationState.kt
new file mode 100644
index 0000000000..8a3515740b
--- /dev/null
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/crypto/VerificationState.kt
@@ -0,0 +1,25 @@
+/*
+ * Copyright (c) 2021 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.api.crypto
+
+enum class VerificationState {
+    REQUEST,
+    WAITING,
+    CANCELED_BY_ME,
+    CANCELED_BY_OTHER,
+    DONE
+}
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/ReferencesAggregatedContent.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/ReferencesAggregatedContent.kt
index 0947c96bb0..664d042e18 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/ReferencesAggregatedContent.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/ReferencesAggregatedContent.kt
@@ -17,7 +17,7 @@ package org.matrix.android.sdk.api.session.room.model
 
 import com.squareup.moshi.Json
 import com.squareup.moshi.JsonClass
-import org.matrix.android.sdk.internal.session.room.VerificationState
+import org.matrix.android.sdk.api.crypto.VerificationState
 
 /**
  * Contains an aggregated summary info of the references.
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/EventRelationsAggregationProcessor.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/EventRelationsAggregationProcessor.kt
index d090ba5296..eefab74136 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/EventRelationsAggregationProcessor.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/EventRelationsAggregationProcessor.kt
@@ -16,6 +16,7 @@
 package org.matrix.android.sdk.internal.session.room
 
 import io.realm.Realm
+import org.matrix.android.sdk.api.crypto.VerificationState
 import org.matrix.android.sdk.api.session.events.model.AggregatedAnnotation
 import org.matrix.android.sdk.api.session.events.model.Event
 import org.matrix.android.sdk.api.session.events.model.EventType
@@ -50,14 +51,6 @@ import org.matrix.android.sdk.internal.session.EventInsertLiveProcessor
 import timber.log.Timber
 import javax.inject.Inject
 
-enum class VerificationState {
-    REQUEST,
-    WAITING,
-    CANCELED_BY_ME,
-    CANCELED_BY_OTHER,
-    DONE
-}
-
 fun VerificationState.isCanceled(): Boolean {
     return this == VerificationState.CANCELED_BY_ME || this == VerificationState.CANCELED_BY_OTHER
 }
diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/VerificationItemFactory.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/VerificationItemFactory.kt
index 0b623d78f1..eb539d2b8a 100644
--- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/VerificationItemFactory.kt
+++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/VerificationItemFactory.kt
@@ -26,6 +26,7 @@ import im.vector.app.features.home.room.detail.timeline.helper.MessageInformatio
 import im.vector.app.features.home.room.detail.timeline.helper.MessageItemAttributesFactory
 import im.vector.app.features.home.room.detail.timeline.item.StatusTileTimelineItem
 import im.vector.app.features.home.room.detail.timeline.item.StatusTileTimelineItem_
+import org.matrix.android.sdk.api.crypto.VerificationState
 import org.matrix.android.sdk.api.session.Session
 import org.matrix.android.sdk.api.session.crypto.verification.CancelCode
 import org.matrix.android.sdk.api.session.crypto.verification.safeValueOf
@@ -35,7 +36,6 @@ import org.matrix.android.sdk.api.session.events.model.toModel
 import org.matrix.android.sdk.api.session.room.model.message.MessageRelationContent
 import org.matrix.android.sdk.api.session.room.model.message.MessageVerificationCancelContent
 import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent
-import org.matrix.android.sdk.internal.session.room.VerificationState
 import javax.inject.Inject
 
 /**
diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/MessageInformationDataFactory.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/MessageInformationDataFactory.kt
index 802c177197..951a4d3fa0 100644
--- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/MessageInformationDataFactory.kt
+++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/MessageInformationDataFactory.kt
@@ -26,6 +26,7 @@ import im.vector.app.features.home.room.detail.timeline.item.ReactionInfoData
 import im.vector.app.features.home.room.detail.timeline.item.ReadReceiptData
 import im.vector.app.features.home.room.detail.timeline.item.ReferencesInfoData
 import im.vector.app.features.settings.VectorPreferences
+import org.matrix.android.sdk.api.crypto.VerificationState
 import org.matrix.android.sdk.api.extensions.orFalse
 import org.matrix.android.sdk.api.session.Session
 import org.matrix.android.sdk.api.session.events.model.EventType
@@ -37,7 +38,6 @@ import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent
 import org.matrix.android.sdk.api.session.room.timeline.getLastMessageContent
 import org.matrix.android.sdk.api.session.room.timeline.hasBeenEdited
 import org.matrix.android.sdk.internal.crypto.model.event.EncryptedEventContent
-import org.matrix.android.sdk.internal.session.room.VerificationState
 import javax.inject.Inject
 
 /**
diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageInformationData.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageInformationData.kt
index 38575f0cc9..48bd4db94c 100644
--- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageInformationData.kt
+++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageInformationData.kt
@@ -18,9 +18,9 @@ package im.vector.app.features.home.room.detail.timeline.item
 
 import android.os.Parcelable
 import kotlinx.parcelize.Parcelize
+import org.matrix.android.sdk.api.crypto.VerificationState
 import org.matrix.android.sdk.api.session.room.send.SendState
 import org.matrix.android.sdk.api.util.MatrixItem
-import org.matrix.android.sdk.internal.session.room.VerificationState
 
 @Parcelize
 data class MessageInformationData(
diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/VerificationRequestItem.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/VerificationRequestItem.kt
index c7a279979b..9ec1d825df 100644
--- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/VerificationRequestItem.kt
+++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/VerificationRequestItem.kt
@@ -35,8 +35,8 @@ import im.vector.app.features.home.AvatarRenderer
 import im.vector.app.features.home.room.detail.RoomDetailAction
 import im.vector.app.features.home.room.detail.timeline.MessageColorProvider
 import im.vector.app.features.home.room.detail.timeline.TimelineEventController
+import org.matrix.android.sdk.api.crypto.VerificationState
 import org.matrix.android.sdk.api.session.crypto.verification.VerificationService
-import org.matrix.android.sdk.internal.session.room.VerificationState
 
 @EpoxyModelClass(layout = R.layout.item_timeline_event_base_state)
 abstract class VerificationRequestItem : AbsBaseMessageItem<VerificationRequestItem.Holder>() {

From c7e7bf4d2c6debc327e0ddd86da0bcf9fd099c1a Mon Sep 17 00:00:00 2001
From: Benoit Marty <benoit@matrix.org>
Date: Mon, 1 Mar 2021 15:14:03 +0100
Subject: [PATCH 04/17] Move VerificationState?.toState() to a dedicated file
 and in the correct package

---
 .../sdk/api/crypto/VerificationState.kt       |  4 +++
 .../verification/VerificationStateExt.kt      | 36 +++++++++++++++++++
 .../EventRelationsAggregationProcessor.kt     | 20 +----------
 3 files changed, 41 insertions(+), 19 deletions(-)
 create mode 100644 matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/verification/VerificationStateExt.kt

diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/crypto/VerificationState.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/crypto/VerificationState.kt
index 8a3515740b..54276a6b51 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/crypto/VerificationState.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/crypto/VerificationState.kt
@@ -23,3 +23,7 @@ enum class VerificationState {
     CANCELED_BY_OTHER,
     DONE
 }
+
+fun VerificationState.isCanceled(): Boolean {
+    return this == VerificationState.CANCELED_BY_ME || this == VerificationState.CANCELED_BY_OTHER
+}
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/verification/VerificationStateExt.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/verification/VerificationStateExt.kt
new file mode 100644
index 0000000000..b5fba0d54c
--- /dev/null
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/verification/VerificationStateExt.kt
@@ -0,0 +1,36 @@
+/*
+ * Copyright (c) 2021 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.crypto.verification
+
+import org.matrix.android.sdk.api.crypto.VerificationState
+import org.matrix.android.sdk.api.crypto.isCanceled
+
+// State transition with control
+internal fun VerificationState?.toState(newState: VerificationState): VerificationState {
+    // Cancel is always prioritary ?
+    // Eg id i found that mac or keys mismatch and send a cancel and the other send a done, i have to
+    // consider as canceled
+    if (newState.isCanceled()) {
+        return newState
+    }
+    // never move out of cancel
+    if (this?.isCanceled() == true) {
+        return this
+    }
+    return newState
+}
+
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/EventRelationsAggregationProcessor.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/EventRelationsAggregationProcessor.kt
index eefab74136..8df865b104 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/EventRelationsAggregationProcessor.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/EventRelationsAggregationProcessor.kt
@@ -32,6 +32,7 @@ import org.matrix.android.sdk.api.session.room.model.message.MessagePollResponse
 import org.matrix.android.sdk.api.session.room.model.message.MessageRelationContent
 import org.matrix.android.sdk.api.session.room.model.relation.ReactionContent
 import org.matrix.android.sdk.internal.crypto.model.event.EncryptedEventContent
+import org.matrix.android.sdk.internal.crypto.verification.toState
 import org.matrix.android.sdk.internal.database.mapper.ContentMapper
 import org.matrix.android.sdk.internal.database.mapper.EventMapper
 import org.matrix.android.sdk.internal.database.model.EditAggregatedSummaryEntity
@@ -51,25 +52,6 @@ import org.matrix.android.sdk.internal.session.EventInsertLiveProcessor
 import timber.log.Timber
 import javax.inject.Inject
 
-fun VerificationState.isCanceled(): Boolean {
-    return this == VerificationState.CANCELED_BY_ME || this == VerificationState.CANCELED_BY_OTHER
-}
-
-// State transition with control
-private fun VerificationState?.toState(newState: VerificationState): VerificationState {
-    // Cancel is always prioritary ?
-    // Eg id i found that mac or keys mismatch and send a cancel and the other send a done, i have to
-    // consider as canceled
-    if (newState.isCanceled()) {
-        return newState
-    }
-    // never move out of cancel
-    if (this?.isCanceled() == true) {
-        return this
-    }
-    return newState
-}
-
 internal class EventRelationsAggregationProcessor @Inject constructor(@UserId private val userId: String)
     : EventInsertLiveProcessor {
 

From bdec23f740608915538b0d164b05f86926918d2c Mon Sep 17 00:00:00 2001
From: Benoit Marty <benoit@matrix.org>
Date: Mon, 1 Mar 2021 15:03:47 +0100
Subject: [PATCH 05/17] Rework edition of event management

---
 .../EventAnnotationsSummaryEntityQuery.kt     |  2 +-
 .../EventRelationsAggregationProcessor.kt     | 44 +++++++++++++------
 2 files changed, 31 insertions(+), 15 deletions(-)

diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/query/EventAnnotationsSummaryEntityQuery.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/query/EventAnnotationsSummaryEntityQuery.kt
index 9a298b7e79..b96fc54a42 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/query/EventAnnotationsSummaryEntityQuery.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/query/EventAnnotationsSummaryEntityQuery.kt
@@ -50,5 +50,5 @@ internal fun EventAnnotationsSummaryEntity.Companion.create(realm: Realm, roomId
 
 internal fun EventAnnotationsSummaryEntity.Companion.getOrCreate(realm: Realm, roomId: String, eventId: String): EventAnnotationsSummaryEntity {
     return EventAnnotationsSummaryEntity.where(realm, eventId).findFirst()
-            ?: EventAnnotationsSummaryEntity.create(realm, roomId, eventId).apply { this.roomId = roomId }
+            ?: EventAnnotationsSummaryEntity.create(realm, roomId, eventId)
 }
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/EventRelationsAggregationProcessor.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/EventRelationsAggregationProcessor.kt
index 8df865b104..dd6b16de59 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/EventRelationsAggregationProcessor.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/EventRelationsAggregationProcessor.kt
@@ -191,29 +191,45 @@ internal class EventRelationsAggregationProcessor @Inject constructor(@UserId pr
     // OPT OUT serer aggregation until API mature enough
     private val SHOULD_HANDLE_SERVER_AGREGGATION = false
 
-    private fun handleReplace(realm: Realm, event: Event, content: MessageContent, roomId: String, isLocalEcho: Boolean, relatedEventId: String? = null) {
+    private fun handleReplace(realm: Realm,
+                              event: Event,
+                              content: MessageContent,
+                              roomId: String,
+                              isLocalEcho: Boolean,
+                              relatedEventId: String? = null) {
         val eventId = event.eventId ?: return
         val targetEventId = relatedEventId ?: content.relatesTo?.eventId ?: return
         val newContent = content.newContent ?: return
+
+        // Check that the sender is the same
+        val editedEvent = EventEntity.where(realm, targetEventId).findFirst()
+        if (editedEvent == null) {
+            // We do not know yet about the edited event
+        } else if (editedEvent.sender != event.senderId) {
+            // Edited by someone else, ignore
+            Timber.w("Ignore edition by someone else")
+            return
+        }
+
         // ok, this is a replace
-        val existing = EventAnnotationsSummaryEntity.getOrCreate(realm, roomId, targetEventId)
+        val eventAnnotationsSummaryEntity = EventAnnotationsSummaryEntity.getOrCreate(realm, roomId, targetEventId)
 
         // we have it
-        val existingSummary = existing.editSummary
+        val existingSummary = eventAnnotationsSummaryEntity.editSummary
         if (existingSummary == null) {
             Timber.v("###REPLACE new edit summary for $targetEventId, creating one (localEcho:$isLocalEcho)")
             // create the edit summary
-            val editSummary = realm.createObject(EditAggregatedSummaryEntity::class.java)
-            editSummary.aggregatedContent = ContentMapper.map(newContent)
-            if (isLocalEcho) {
-                editSummary.lastEditTs = 0
-                editSummary.sourceLocalEchoEvents.add(eventId)
-            } else {
-                editSummary.lastEditTs = event.originServerTs ?: 0
-                editSummary.sourceEvents.add(eventId)
-            }
-
-            existing.editSummary = editSummary
+            eventAnnotationsSummaryEntity.editSummary = realm.createObject(EditAggregatedSummaryEntity::class.java)
+                    .also { editSummary ->
+                        editSummary.aggregatedContent = ContentMapper.map(newContent)
+                        if (isLocalEcho) {
+                            editSummary.lastEditTs = 0
+                            editSummary.sourceLocalEchoEvents.add(eventId)
+                        } else {
+                            editSummary.lastEditTs = event.originServerTs ?: 0
+                            editSummary.sourceEvents.add(eventId)
+                        }
+                    }
         } else {
             if (existingSummary.sourceEvents.contains(eventId)) {
                 // ignore this event, we already know it (??)

From 1bfd78753a825d8739b98fb0030eeb3fa705d567 Mon Sep 17 00:00:00 2001
From: Benoit Marty <benoit@matrix.org>
Date: Mon, 1 Mar 2021 17:52:56 +0100
Subject: [PATCH 06/17] Ensure we do not edit an Event from another room

---
 .../database/helper/ChunkEntityHelper.kt      |  2 +-
 .../EventAnnotationsSummaryEntityQuery.kt     | 18 ++++---------
 .../EventRelationsAggregationProcessor.kt     | 25 ++++++++-----------
 .../room/relation/DefaultRelationService.kt   |  4 +--
 .../relation/FindReactionEventForUndoTask.kt  |  8 +++---
 .../room/relation/UpdateQuickReactionTask.kt  | 14 +++++------
 6 files changed, 30 insertions(+), 41 deletions(-)

diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/helper/ChunkEntityHelper.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/helper/ChunkEntityHelper.kt
index b4935cfdcc..c2b729e593 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/helper/ChunkEntityHelper.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/helper/ChunkEntityHelper.kt
@@ -97,7 +97,7 @@ internal fun ChunkEntity.addTimelineEvent(roomId: String,
         this.root = eventEntity
         this.eventId = eventId
         this.roomId = roomId
-        this.annotations = EventAnnotationsSummaryEntity.where(realm, eventId).findFirst()
+        this.annotations = EventAnnotationsSummaryEntity.where(realm, roomId, eventId).findFirst()
         this.readReceipts = readReceiptsSummaryEntity
         this.displayIndex = displayIndex
         val roomMemberContent = roomMemberContentsByUser[senderId]
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/query/EventAnnotationsSummaryEntityQuery.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/query/EventAnnotationsSummaryEntityQuery.kt
index b96fc54a42..c3cae3d268 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/query/EventAnnotationsSummaryEntityQuery.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/query/EventAnnotationsSummaryEntityQuery.kt
@@ -23,18 +23,10 @@ import io.realm.Realm
 import io.realm.RealmQuery
 import io.realm.kotlin.where
 
-internal fun EventAnnotationsSummaryEntity.Companion.where(realm: Realm, eventId: String): RealmQuery<EventAnnotationsSummaryEntity> {
-    val query = realm.where<EventAnnotationsSummaryEntity>()
-    query.equalTo(EventAnnotationsSummaryEntityFields.EVENT_ID, eventId)
-    return query
-}
-
-internal fun EventAnnotationsSummaryEntity.Companion.whereInRoom(realm: Realm, roomId: String?): RealmQuery<EventAnnotationsSummaryEntity> {
-    val query = realm.where<EventAnnotationsSummaryEntity>()
-    if (roomId != null) {
-        query.equalTo(EventAnnotationsSummaryEntityFields.ROOM_ID, roomId)
-    }
-    return query
+internal fun EventAnnotationsSummaryEntity.Companion.where(realm: Realm, roomId: String, eventId: String): RealmQuery<EventAnnotationsSummaryEntity> {
+    return realm.where<EventAnnotationsSummaryEntity>()
+            .equalTo(EventAnnotationsSummaryEntityFields.ROOM_ID, roomId)
+            .equalTo(EventAnnotationsSummaryEntityFields.EVENT_ID, eventId)
 }
 
 internal fun EventAnnotationsSummaryEntity.Companion.create(realm: Realm, roomId: String, eventId: String): EventAnnotationsSummaryEntity {
@@ -49,6 +41,6 @@ internal fun EventAnnotationsSummaryEntity.Companion.create(realm: Realm, roomId
 }
 
 internal fun EventAnnotationsSummaryEntity.Companion.getOrCreate(realm: Realm, roomId: String, eventId: String): EventAnnotationsSummaryEntity {
-    return EventAnnotationsSummaryEntity.where(realm, eventId).findFirst()
+    return EventAnnotationsSummaryEntity.where(realm, roomId, eventId).findFirst()
             ?: EventAnnotationsSummaryEntity.create(realm, roomId, eventId)
 }
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/EventRelationsAggregationProcessor.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/EventRelationsAggregationProcessor.kt
index dd6b16de59..c274ccb244 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/EventRelationsAggregationProcessor.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/EventRelationsAggregationProcessor.kt
@@ -93,13 +93,11 @@ internal class EventRelationsAggregationProcessor @Inject constructor(@UserId pr
                         Timber.v("###REACTION Agreggation in room $roomId for event ${event.eventId}")
                         handleInitialAggregatedRelations(event, roomId, event.unsignedData.relations.annotations, realm)
 
-                        EventAnnotationsSummaryEntity.where(realm, event.eventId
-                                ?: "").findFirst()?.let {
-                            TimelineEventEntity.where(realm, roomId = roomId, eventId = event.eventId
-                                    ?: "").findFirst()?.let { tet ->
-                                tet.annotations = it
-                            }
-                        }
+                        EventAnnotationsSummaryEntity.where(realm, roomId, event.eventId ?: "").findFirst()
+                                ?.let {
+                                    TimelineEventEntity.where(realm, roomId = roomId, eventId = event.eventId ?: "").findFirst()
+                                            ?.let { tet -> tet.annotations = it }
+                                }
                     }
 
                     val content: MessageContent? = event.content.toModel()
@@ -281,7 +279,7 @@ internal class EventRelationsAggregationProcessor @Inject constructor(@UserId pr
         val eventTimestamp = event.originServerTs ?: return
 
         // ok, this is a poll response
-        var existing = EventAnnotationsSummaryEntity.where(realm, targetEventId).findFirst()
+        var existing = EventAnnotationsSummaryEntity.where(realm, roomId, targetEventId).findFirst()
         if (existing == null) {
             Timber.v("## POLL creating new relation summary for $targetEventId")
             existing = EventAnnotationsSummaryEntity.create(realm, roomId, targetEventId)
@@ -361,7 +359,7 @@ internal class EventRelationsAggregationProcessor @Inject constructor(@UserId pr
             aggregation.chunk?.forEach {
                 if (it.type == EventType.REACTION) {
                     val eventId = event.eventId ?: ""
-                    val existing = EventAnnotationsSummaryEntity.where(realm, eventId).findFirst()
+                    val existing = EventAnnotationsSummaryEntity.where(realm, roomId, eventId).findFirst()
                     if (existing == null) {
                         val eventSummary = EventAnnotationsSummaryEntity.create(realm, roomId, eventId)
                         val sum = realm.createObject(ReactionAggregatedSummaryEntity::class.java)
@@ -445,7 +443,7 @@ internal class EventRelationsAggregationProcessor @Inject constructor(@UserId pr
      */
     private fun handleRedactionOfReplace(redacted: EventEntity, relatedEventId: String, realm: Realm) {
         Timber.d("Handle redaction of m.replace")
-        val eventSummary = EventAnnotationsSummaryEntity.where(realm, relatedEventId).findFirst()
+        val eventSummary = EventAnnotationsSummaryEntity.where(realm, redacted.roomId, relatedEventId).findFirst()
         if (eventSummary == null) {
             Timber.w("Redaction of a replace targeting an unknown event $relatedEventId")
             return
@@ -475,16 +473,15 @@ internal class EventRelationsAggregationProcessor @Inject constructor(@UserId pr
         }
     }
 
-    fun handleReactionRedact(eventToPrune: EventEntity, realm: Realm, userId: String) {
+    private fun handleReactionRedact(eventToPrune: EventEntity, realm: Realm, userId: String) {
         Timber.v("REDACTION of reaction ${eventToPrune.eventId}")
         // delete a reaction, need to update the annotation summary if any
-        val reactionContent: ReactionContent = EventMapper.map(eventToPrune).content.toModel()
-                ?: return
+        val reactionContent: ReactionContent = EventMapper.map(eventToPrune).content.toModel() ?: return
         val eventThatWasReacted = reactionContent.relatesTo?.eventId ?: return
 
         val reactionKey = reactionContent.relatesTo.key
         Timber.v("REMOVE reaction for key $reactionKey")
-        val summary = EventAnnotationsSummaryEntity.where(realm, eventThatWasReacted).findFirst()
+        val summary = EventAnnotationsSummaryEntity.where(realm, eventToPrune.roomId, eventThatWasReacted).findFirst()
         if (summary != null) {
             summary.reactionsSummary.where()
                     .equalTo(ReactionAggregatedSummaryEntityFields.KEY, reactionKey)
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/relation/DefaultRelationService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/relation/DefaultRelationService.kt
index b7caf62865..bbcad3a4fe 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/relation/DefaultRelationService.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/relation/DefaultRelationService.kt
@@ -159,7 +159,7 @@ internal class DefaultRelationService @AssistedInject constructor(
 
     override fun getEventAnnotationsSummary(eventId: String): EventAnnotationsSummary? {
         return monarchy.fetchCopyMap(
-                { EventAnnotationsSummaryEntity.where(it, eventId).findFirst() },
+                { EventAnnotationsSummaryEntity.where(it, roomId, eventId).findFirst() },
                 { entity, _ ->
                     entity.asDomain()
                 }
@@ -168,7 +168,7 @@ internal class DefaultRelationService @AssistedInject constructor(
 
     override fun getEventAnnotationsSummaryLive(eventId: String): LiveData<Optional<EventAnnotationsSummary>> {
         val liveData = monarchy.findAllMappedWithChanges(
-                { EventAnnotationsSummaryEntity.where(it, eventId) },
+                { EventAnnotationsSummaryEntity.where(it, roomId, eventId) },
                 { it.asDomain() }
         )
         return Transformations.map(liveData) { results ->
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/relation/FindReactionEventForUndoTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/relation/FindReactionEventForUndoTask.kt
index fa6db2ee37..863ae4f5ce 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/relation/FindReactionEventForUndoTask.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/relation/FindReactionEventForUndoTask.kt
@@ -45,16 +45,16 @@ internal class DefaultFindReactionEventForUndoTask @Inject constructor(
 
     override suspend fun execute(params: FindReactionEventForUndoTask.Params): FindReactionEventForUndoTask.Result {
         val eventId = Realm.getInstance(monarchy.realmConfiguration).use { realm ->
-            getReactionToRedact(realm, params.reaction, params.eventId)?.eventId
+            getReactionToRedact(realm, params)?.eventId
         }
         return FindReactionEventForUndoTask.Result(eventId)
     }
 
-    private fun getReactionToRedact(realm: Realm, reaction: String, eventId: String): EventEntity? {
-        val summary = EventAnnotationsSummaryEntity.where(realm, eventId).findFirst() ?: return null
+    private fun getReactionToRedact(realm: Realm, params: FindReactionEventForUndoTask.Params): EventEntity? {
+        val summary = EventAnnotationsSummaryEntity.where(realm, params.roomId, params.eventId).findFirst() ?: return null
 
         val rase = summary.reactionsSummary.where()
-                .equalTo(ReactionAggregatedSummaryEntityFields.KEY, reaction)
+                .equalTo(ReactionAggregatedSummaryEntityFields.KEY, params.reaction)
                 .findFirst() ?: return null
 
         // want to find the event originated by me!
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/relation/UpdateQuickReactionTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/relation/UpdateQuickReactionTask.kt
index 1f68a700ad..32d6c5aa7e 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/relation/UpdateQuickReactionTask.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/relation/UpdateQuickReactionTask.kt
@@ -47,22 +47,22 @@ internal class DefaultUpdateQuickReactionTask @Inject constructor(@SessionDataba
     override suspend fun execute(params: UpdateQuickReactionTask.Params): UpdateQuickReactionTask.Result {
         var res: Pair<String?, List<String>?>? = null
         monarchy.doWithRealm { realm ->
-            res = updateQuickReaction(realm, params.reaction, params.oppositeReaction, params.eventId)
+            res = updateQuickReaction(realm, params)
         }
         return UpdateQuickReactionTask.Result(res?.first, res?.second.orEmpty())
     }
 
-    private fun updateQuickReaction(realm: Realm, reaction: String, oppositeReaction: String, eventId: String): Pair<String?, List<String>?> {
+    private fun updateQuickReaction(realm: Realm, params: UpdateQuickReactionTask.Params): Pair<String?, List<String>?> {
         // the emoji reaction has been selected, we need to check if we have reacted it or not
-        val existingSummary = EventAnnotationsSummaryEntity.where(realm, eventId).findFirst()
-                ?: return Pair(reaction, null)
+        val existingSummary = EventAnnotationsSummaryEntity.where(realm, params.roomId, params.eventId).findFirst()
+                ?: return Pair(params.reaction, null)
 
         // Ok there is already reactions on this event, have we reacted to it
         val aggregationForReaction = existingSummary.reactionsSummary.where()
-                .equalTo(ReactionAggregatedSummaryEntityFields.KEY, reaction)
+                .equalTo(ReactionAggregatedSummaryEntityFields.KEY, params.reaction)
                 .findFirst()
         val aggregationForOppositeReaction = existingSummary.reactionsSummary.where()
-                .equalTo(ReactionAggregatedSummaryEntityFields.KEY, oppositeReaction)
+                .equalTo(ReactionAggregatedSummaryEntityFields.KEY, params.oppositeReaction)
                 .findFirst()
 
         if (aggregationForReaction == null || !aggregationForReaction.addedByMe) {
@@ -72,7 +72,7 @@ internal class DefaultUpdateQuickReactionTask @Inject constructor(@SessionDataba
                 val entity = EventEntity.where(realm, it).findFirst()
                 if (entity?.sender == userId) entity.eventId else null
             }
-            return Pair(reaction, toRedact)
+            return Pair(params.reaction, toRedact)
         } else {
             // I already added it, so i need to undo it (like a toggle)
             // find all m.redaction coming from me to readact them

From 097668b762d7426c4e1ca175f1e09ac0c9e2e4ce Mon Sep 17 00:00:00 2001
From: Benoit Marty <benoit@matrix.org>
Date: Mon, 1 Mar 2021 19:59:55 +0100
Subject: [PATCH 07/17] Rework edition of event management - step 2

---
 CHANGES.md                                    |  2 +-
 .../database/RealmSessionStoreMigration.kt    | 30 ++++++-
 .../database/helper/ChunkEntityHelper.kt      |  1 +
 .../mapper/EventAnnotationsSummaryMapper.kt   | 55 ++----------
 .../model/EditAggregatedSummaryEntity.kt      | 19 +++--
 .../model/EventAnnotationsSummaryEntity.kt    | 16 ++++
 .../database/model/SessionRealmModule.kt      |  1 +
 .../EventRelationsAggregationProcessor.kt     | 84 ++++++++-----------
 8 files changed, 102 insertions(+), 106 deletions(-)

diff --git a/CHANGES.md b/CHANGES.md
index 5125d158ec..d9c2c01bea 100644
--- a/CHANGES.md
+++ b/CHANGES.md
@@ -25,7 +25,7 @@ Test:
  -
 
 Other changes:
- -
+ - Rework edition of event management
 
 Changes in Element 1.1.0 (2021-02-19)
 ===================================================
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/RealmSessionStoreMigration.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/RealmSessionStoreMigration.kt
index 57002b5a60..820588b1ab 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/RealmSessionStoreMigration.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/RealmSessionStoreMigration.kt
@@ -18,6 +18,8 @@ package org.matrix.android.sdk.internal.database
 
 import io.realm.DynamicRealm
 import io.realm.RealmMigration
+import org.matrix.android.sdk.internal.database.model.EditAggregatedSummaryEntityFields
+import org.matrix.android.sdk.internal.database.model.EditionOfEventFields
 import org.matrix.android.sdk.internal.database.model.HomeServerCapabilitiesEntityFields
 import org.matrix.android.sdk.internal.database.model.PendingThreePidEntityFields
 import org.matrix.android.sdk.internal.database.model.PreviewUrlCacheEntityFields
@@ -30,7 +32,7 @@ import javax.inject.Inject
 class RealmSessionStoreMigration @Inject constructor() : RealmMigration {
 
     companion object {
-        const val SESSION_STORE_SCHEMA_VERSION = 7L
+        const val SESSION_STORE_SCHEMA_VERSION = 8L
     }
 
     override fun migrate(realm: DynamicRealm, oldVersion: Long, newVersion: Long) {
@@ -43,6 +45,7 @@ class RealmSessionStoreMigration @Inject constructor() : RealmMigration {
         if (oldVersion <= 4) migrateTo5(realm)
         if (oldVersion <= 5) migrateTo6(realm)
         if (oldVersion <= 6) migrateTo7(realm)
+        if (oldVersion <= 7) migrateTo8(realm)
     }
 
     private fun migrateTo1(realm: DynamicRealm) {
@@ -122,4 +125,29 @@ class RealmSessionStoreMigration @Inject constructor() : RealmMigration {
                 }
                 ?.removeField("areAllMembersLoaded")
     }
+
+    private fun migrateTo8(realm: DynamicRealm) {
+        Timber.d("Step 7 -> 8")
+
+        val editionOfEventSchema = realm.schema.create("EditionOfEvent")
+                .apply {
+                    // setEmbedded does not return `this`...
+                    isEmbedded = true
+                }
+                .addField(EditionOfEventFields.CONTENT, String::class.java)
+                .addField(EditionOfEventFields.EVENT_ID, String::class.java)
+                .setRequired(EditionOfEventFields.EVENT_ID, true)
+                .addField(EditionOfEventFields.SENDER_ID, String::class.java)
+                .setRequired(EditionOfEventFields.SENDER_ID, true)
+                .addField(EditionOfEventFields.TIMESTAMP, Long::class.java)
+                .addField(EditionOfEventFields.IS_LOCAL_ECHO, Boolean::class.java)
+
+
+        realm.schema.get("EditAggregatedSummaryEntity")
+                ?.removeField("aggregatedContent")
+                ?.removeField("sourceEvents")
+                ?.removeField("lastEditTs")
+                ?.removeField("sourceLocalEchoEvents")
+                ?.addRealmListField(EditAggregatedSummaryEntityFields.EDITIONS.`$`, editionOfEventSchema)
+    }
 }
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/helper/ChunkEntityHelper.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/helper/ChunkEntityHelper.kt
index c2b729e593..e262b40419 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/helper/ChunkEntityHelper.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/helper/ChunkEntityHelper.kt
@@ -98,6 +98,7 @@ internal fun ChunkEntity.addTimelineEvent(roomId: String,
         this.eventId = eventId
         this.roomId = roomId
         this.annotations = EventAnnotationsSummaryEntity.where(realm, roomId, eventId).findFirst()
+                ?.also { it.cleanUp(eventEntity.sender) }
         this.readReceipts = readReceiptsSummaryEntity
         this.displayIndex = displayIndex
         val roomMemberContent = roomMemberContentsByUser[senderId]
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/mapper/EventAnnotationsSummaryMapper.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/mapper/EventAnnotationsSummaryMapper.kt
index 9ed2664068..f4d3d74150 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/mapper/EventAnnotationsSummaryMapper.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/mapper/EventAnnotationsSummaryMapper.kt
@@ -20,11 +20,7 @@ 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.EditAggregatedSummaryEntity
 import org.matrix.android.sdk.internal.database.model.EventAnnotationsSummaryEntity
-import org.matrix.android.sdk.internal.database.model.ReactionAggregatedSummaryEntity
-import org.matrix.android.sdk.internal.database.model.ReferencesAggregatedSummaryEntity
-import io.realm.RealmList
 
 internal object EventAnnotationsSummaryMapper {
     fun map(annotationsSummary: EventAnnotationsSummaryEntity): EventAnnotationsSummary {
@@ -41,11 +37,14 @@ internal object EventAnnotationsSummaryMapper {
                     )
                 },
                 editSummary = annotationsSummary.editSummary?.let {
+                    val latestEdition = it.editions.maxByOrNull { editionOfEvent -> editionOfEvent.timestamp }
                     EditAggregatedSummary(
-                            ContentMapper.map(it.aggregatedContent),
-                            it.sourceEvents.toList(),
-                            it.sourceLocalEchoEvents.toList(),
-                            it.lastEditTs
+                            ContentMapper.map(latestEdition?.content),
+                            it.editions.filter { editionOfEvent -> !editionOfEvent.isLocalEcho }
+                                    .map { editionOfEvent -> editionOfEvent.eventId },
+                            it.editions.filter { editionOfEvent -> editionOfEvent.isLocalEcho }
+                                    .map { editionOfEvent -> editionOfEvent.eventId },
+                            latestEdition?.timestamp ?: 0L
                     )
                 },
                 referencesAggregatedSummary = annotationsSummary.referencesSummaryEntity?.let {
@@ -62,46 +61,6 @@ internal object EventAnnotationsSummaryMapper {
 
         )
     }
-
-    fun map(annotationsSummary: EventAnnotationsSummary, roomId: String): EventAnnotationsSummaryEntity {
-        val eventAnnotationsSummaryEntity = EventAnnotationsSummaryEntity()
-        eventAnnotationsSummaryEntity.eventId = annotationsSummary.eventId
-        eventAnnotationsSummaryEntity.roomId = roomId
-        eventAnnotationsSummaryEntity.editSummary = annotationsSummary.editSummary?.let {
-            EditAggregatedSummaryEntity(
-                    ContentMapper.map(it.aggregatedContent),
-                    RealmList<String>().apply { addAll(it.sourceEvents) },
-                    RealmList<String>().apply { addAll(it.localEchos) },
-                    it.lastEditTs
-            )
-        }
-        eventAnnotationsSummaryEntity.reactionsSummary = annotationsSummary.reactionsSummary.let {
-            RealmList<ReactionAggregatedSummaryEntity>().apply {
-                addAll(it.map {
-                    ReactionAggregatedSummaryEntity(
-                            it.key,
-                            it.count,
-                            it.addedByMe,
-                            it.firstTimestamp,
-                            RealmList<String>().apply { addAll(it.sourceEvents) },
-                            RealmList<String>().apply { addAll(it.localEchoEvents) }
-                    )
-                })
-            }
-        }
-        eventAnnotationsSummaryEntity.referencesSummaryEntity = annotationsSummary.referencesAggregatedSummary?.let {
-            ReferencesAggregatedSummaryEntity(
-                    it.eventId,
-                    ContentMapper.map(it.content),
-                    RealmList<String>().apply { addAll(it.sourceEvents) },
-                    RealmList<String>().apply { addAll(it.localEchos) }
-            )
-        }
-        eventAnnotationsSummaryEntity.pollResponseSummary = annotationsSummary.pollResponseSummary?.let {
-            PollResponseAggregatedSummaryEntityMapper.map(it)
-        }
-        return eventAnnotationsSummaryEntity
-    }
 }
 
 internal fun EventAnnotationsSummaryEntity.asDomain(): EventAnnotationsSummary {
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/EditAggregatedSummaryEntity.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/EditAggregatedSummaryEntity.kt
index 604afc1ab1..0ed927a6b8 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/EditAggregatedSummaryEntity.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/EditAggregatedSummaryEntity.kt
@@ -17,17 +17,24 @@ package org.matrix.android.sdk.internal.database.model
 
 import io.realm.RealmList
 import io.realm.RealmObject
+import io.realm.annotations.RealmClass
 
 /**
- * Keep the latest state of edition of a message
+ * Keep all the editions of a message
  */
 internal open class EditAggregatedSummaryEntity(
-        var aggregatedContent: String? = null,
-        // The list of the eventIDs used to build the summary (might be out of sync if chunked received from message chunk)
-        var sourceEvents: RealmList<String> = RealmList(),
-        var sourceLocalEchoEvents: RealmList<String> = RealmList(),
-        var lastEditTs: Long = 0
+        // The list of the editions used to build the summary (might be out of sync if chunked received from message chunk)
+        var editions: RealmList<EditionOfEvent> = RealmList()
 ) : RealmObject() {
 
     companion object
 }
+
+@RealmClass(embedded = true)
+internal open class EditionOfEvent(
+        var senderId: String = "",
+        var eventId: String = "",
+        var content: String? = null,
+        var timestamp: Long = 0,
+        var isLocalEcho: Boolean = false
+) : RealmObject()
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/EventAnnotationsSummaryEntity.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/EventAnnotationsSummaryEntity.kt
index 33f26d439f..3e88130420 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/EventAnnotationsSummaryEntity.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/EventAnnotationsSummaryEntity.kt
@@ -18,6 +18,7 @@ package org.matrix.android.sdk.internal.database.model
 import io.realm.RealmList
 import io.realm.RealmObject
 import io.realm.annotations.PrimaryKey
+import timber.log.Timber
 
 internal open class EventAnnotationsSummaryEntity(
         @PrimaryKey
@@ -29,6 +30,21 @@ internal open class EventAnnotationsSummaryEntity(
         var pollResponseSummary: PollResponseAggregatedSummaryEntity? = null
 ) : RealmObject() {
 
+    /**
+     * Cleanup undesired editions, done by users different from the originalEventSender
+     */
+    fun cleanUp(originalEventSenderId: String?) {
+        originalEventSenderId ?: return
+
+        editSummary?.editions?.filter {
+            it.senderId != originalEventSenderId
+        }
+                ?.forEach {
+                    Timber.w("Deleting an edition from ${it.senderId} of event sent by $originalEventSenderId")
+                    it.deleteFromRealm()
+                }
+    }
+
     companion object
 }
 
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/SessionRealmModule.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/SessionRealmModule.kt
index bca2c42c9e..6e6096cf8a 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/SessionRealmModule.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/SessionRealmModule.kt
@@ -43,6 +43,7 @@ import io.realm.annotations.RealmModule
             EventAnnotationsSummaryEntity::class,
             ReactionAggregatedSummaryEntity::class,
             EditAggregatedSummaryEntity::class,
+            EditionOfEvent::class,
             PollResponseAggregatedSummaryEntity::class,
             ReferencesAggregatedSummaryEntity::class,
             PushRulesEntity::class,
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/EventRelationsAggregationProcessor.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/EventRelationsAggregationProcessor.kt
index c274ccb244..8269643d39 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/EventRelationsAggregationProcessor.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/EventRelationsAggregationProcessor.kt
@@ -36,6 +36,7 @@ import org.matrix.android.sdk.internal.crypto.verification.toState
 import org.matrix.android.sdk.internal.database.mapper.ContentMapper
 import org.matrix.android.sdk.internal.database.mapper.EventMapper
 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.EventAnnotationsSummaryEntity
 import org.matrix.android.sdk.internal.database.model.EventEntity
 import org.matrix.android.sdk.internal.database.model.EventInsertType
@@ -219,49 +220,48 @@ internal class EventRelationsAggregationProcessor @Inject constructor(@UserId pr
             // create the edit summary
             eventAnnotationsSummaryEntity.editSummary = realm.createObject(EditAggregatedSummaryEntity::class.java)
                     .also { editSummary ->
-                        editSummary.aggregatedContent = ContentMapper.map(newContent)
-                        if (isLocalEcho) {
-                            editSummary.lastEditTs = 0
-                            editSummary.sourceLocalEchoEvents.add(eventId)
-                        } else {
-                            editSummary.lastEditTs = event.originServerTs ?: 0
-                            editSummary.sourceEvents.add(eventId)
-                        }
+                        editSummary.editions.add(
+                                EditionOfEvent(
+                                        senderId = event.senderId ?: "",
+                                        eventId = event.eventId,
+                                        content = ContentMapper.map(newContent),
+                                        timestamp = if (isLocalEcho) 0 else event.originServerTs ?: 0,
+                                        isLocalEcho = isLocalEcho
+                                )
+                        )
                     }
         } else {
-            if (existingSummary.sourceEvents.contains(eventId)) {
+            if (existingSummary.editions.any { it.eventId == eventId }) {
                 // ignore this event, we already know it (??)
                 Timber.v("###REPLACE ignoring event for summary, it's known $eventId")
                 return
             }
             val txId = event.unsignedData?.transactionId
             // is it a remote echo?
-            if (!isLocalEcho && existingSummary.sourceLocalEchoEvents.contains(txId)) {
+            if (!isLocalEcho && existingSummary.editions.any { it.eventId == txId }) {
                 // ok it has already been managed
                 Timber.v("###REPLACE Receiving remote echo of edit (edit already done)")
-                existingSummary.sourceLocalEchoEvents.remove(txId)
-                existingSummary.sourceEvents.add(event.eventId)
-            } else if (
-                    isLocalEcho // do not rely on ts for local echo, take it
-                    || event.originServerTs ?: 0 >= existingSummary.lastEditTs
-            ) {
-                Timber.v("###REPLACE Computing aggregated edit summary (isLocalEcho:$isLocalEcho)")
-                if (!isLocalEcho) {
-                    // Do not take local echo originServerTs here, could mess up ordering (keep old ts)
-                    existingSummary.lastEditTs = event.originServerTs ?: System.currentTimeMillis()
-                }
-                existingSummary.aggregatedContent = ContentMapper.map(newContent)
-                if (isLocalEcho) {
-                    existingSummary.sourceLocalEchoEvents.add(eventId)
-                } else {
-                    existingSummary.sourceEvents.add(eventId)
+                existingSummary.editions.firstOrNull { it.eventId == txId }?.let {
+                    it.eventId = event.eventId
+                    it.timestamp = event.originServerTs ?: System.currentTimeMillis()
+                    it.isLocalEcho = false
                 }
             } else {
-                // ignore this event for the summary (back paginate)
-                if (!isLocalEcho) {
-                    existingSummary.sourceEvents.add(eventId)
-                }
-                Timber.v("###REPLACE ignoring event for summary, it's to old $eventId")
+                Timber.v("###REPLACE Computing aggregated edit summary (isLocalEcho:$isLocalEcho)")
+                existingSummary.editions.add(
+                        EditionOfEvent(
+                                senderId = event.senderId ?: "",
+                                eventId = event.eventId,
+                                content = ContentMapper.map(newContent),
+                                timestamp = if (isLocalEcho) {
+                                    System.currentTimeMillis()
+                                } else {
+                                    // Do not take local echo originServerTs here, could mess up ordering (keep old ts)
+                                    event.originServerTs ?: System.currentTimeMillis()
+                                },
+                                isLocalEcho = isLocalEcho
+                        )
+                )
             }
         }
     }
@@ -448,29 +448,13 @@ internal class EventRelationsAggregationProcessor @Inject constructor(@UserId pr
             Timber.w("Redaction of a replace targeting an unknown event $relatedEventId")
             return
         }
-        val sourceEvents = eventSummary.editSummary?.sourceEvents
-        val sourceToDiscard = sourceEvents?.indexOf(redacted.eventId)
+        val sourceToDiscard = eventSummary.editSummary?.editions?.firstOrNull {it.eventId == redacted.eventId }
         if (sourceToDiscard == null) {
             Timber.w("Redaction of a replace that was not known in aggregation $sourceToDiscard")
             return
         }
-        // Need to remove this event from the redaction list and compute new aggregation state
-        sourceEvents.removeAt(sourceToDiscard)
-        val previousEdit = sourceEvents.mapNotNull { EventEntity.where(realm, it).findFirst() }.sortedBy { it.originServerTs }.lastOrNull()
-        if (previousEdit == null) {
-            // revert to original
-            eventSummary.editSummary?.deleteFromRealm()
-        } else {
-            // I have the last event
-            ContentMapper.map(previousEdit.content)?.toModel<MessageContent>()?.newContent?.let { newContent ->
-                eventSummary.editSummary?.lastEditTs = previousEdit.originServerTs
-                        ?: System.currentTimeMillis()
-                eventSummary.editSummary?.aggregatedContent = ContentMapper.map(newContent)
-            } ?: run {
-                Timber.e("Failed to udate edited summary")
-                // TODO how to reccover that
-            }
-        }
+        // Need to remove this event from the edition list
+        sourceToDiscard.deleteFromRealm()
     }
 
     private fun handleReactionRedact(eventToPrune: EventEntity, realm: Realm, userId: String) {

From fc468564dcb22115b6b1bb5d879ee77fbb164292 Mon Sep 17 00:00:00 2001
From: Benoit Marty <benoit@matrix.org>
Date: Tue, 2 Mar 2021 10:46:11 +0100
Subject: [PATCH 08/17] Extract state from view model

---
 .../edithistory/ViewEditHistoryViewModel.kt   | 16 +--------
 .../edithistory/ViewEditHistoryViewState.kt   | 33 +++++++++++++++++++
 2 files changed, 34 insertions(+), 15 deletions(-)
 create mode 100644 vector/src/main/java/im/vector/app/features/home/room/detail/timeline/edithistory/ViewEditHistoryViewState.kt

diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/edithistory/ViewEditHistoryViewModel.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/edithistory/ViewEditHistoryViewModel.kt
index fff1c8a0ff..02fb8c66f2 100644
--- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/edithistory/ViewEditHistoryViewModel.kt
+++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/edithistory/ViewEditHistoryViewModel.kt
@@ -15,23 +15,19 @@
  */
 package im.vector.app.features.home.room.detail.timeline.edithistory
 
-import com.airbnb.mvrx.Async
 import com.airbnb.mvrx.Fail
 import com.airbnb.mvrx.FragmentViewModelContext
 import com.airbnb.mvrx.Loading
-import com.airbnb.mvrx.MvRxState
 import com.airbnb.mvrx.MvRxViewModelFactory
 import com.airbnb.mvrx.Success
-import com.airbnb.mvrx.Uninitialized
 import com.airbnb.mvrx.ViewModelContext
 import dagger.assisted.Assisted
-import dagger.assisted.AssistedInject
 import dagger.assisted.AssistedFactory
+import dagger.assisted.AssistedInject
 import im.vector.app.core.date.VectorDateFormatter
 import im.vector.app.core.platform.EmptyAction
 import im.vector.app.core.platform.EmptyViewEvents
 import im.vector.app.core.platform.VectorViewModel
-import im.vector.app.features.home.room.detail.timeline.action.TimelineEventFragmentArgs
 import org.matrix.android.sdk.api.MatrixCallback
 import org.matrix.android.sdk.api.session.Session
 import org.matrix.android.sdk.api.session.crypto.MXCryptoError
@@ -41,16 +37,6 @@ import org.matrix.android.sdk.internal.crypto.algorithms.olm.OlmDecryptionResult
 import timber.log.Timber
 import java.util.UUID
 
-data class ViewEditHistoryViewState(
-        val eventId: String,
-        val roomId: String,
-        val isOriginalAReply: Boolean = false,
-        val editList: Async<List<Event>> = Uninitialized)
-    : MvRxState {
-
-    constructor(args: TimelineEventFragmentArgs) : this(roomId = args.roomId, eventId = args.eventId)
-}
-
 class ViewEditHistoryViewModel @AssistedInject constructor(@Assisted
                                                            initialState: ViewEditHistoryViewState,
                                                            val session: Session,
diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/edithistory/ViewEditHistoryViewState.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/edithistory/ViewEditHistoryViewState.kt
new file mode 100644
index 0000000000..62f08eef7f
--- /dev/null
+++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/edithistory/ViewEditHistoryViewState.kt
@@ -0,0 +1,33 @@
+/*
+ * Copyright (c) 2021 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 im.vector.app.features.home.room.detail.timeline.edithistory
+
+import com.airbnb.mvrx.Async
+import com.airbnb.mvrx.MvRxState
+import com.airbnb.mvrx.Uninitialized
+import im.vector.app.features.home.room.detail.timeline.action.TimelineEventFragmentArgs
+import org.matrix.android.sdk.api.session.events.model.Event
+
+data class ViewEditHistoryViewState(
+        val eventId: String,
+        val roomId: String,
+        val isOriginalAReply: Boolean = false,
+        val editList: Async<List<Event>> = Uninitialized)
+    : MvRxState {
+
+    constructor(args: TimelineEventFragmentArgs) : this(roomId = args.roomId, eventId = args.eventId)
+}

From d2b39e5cb86ac29bb87c559273d9e73cdebd2b69 Mon Sep 17 00:00:00 2001
From: Benoit Marty <benoit@matrix.org>
Date: Tue, 2 Mar 2021 10:52:03 +0100
Subject: [PATCH 09/17] Convert RelationService to suspend (#2449)

---
 .../room/model/relation/RelationService.kt    |  2 +-
 .../room/relation/DefaultRelationService.kt   |  9 +--
 .../edithistory/ViewEditHistoryViewModel.kt   | 70 ++++++++++---------
 3 files changed, 39 insertions(+), 42 deletions(-)

diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/relation/RelationService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/relation/RelationService.kt
index 49aa95924c..4638dd204a 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/relation/RelationService.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/relation/RelationService.kt
@@ -93,7 +93,7 @@ interface RelationService {
     /**
      * Get the edit history of the given event
      */
-    fun fetchEditHistory(eventId: String, callback: MatrixCallback<List<Event>>)
+    suspend fun fetchEditHistory(eventId: String): List<Event>
 
     /**
      * Reply to an event in the timeline (must be in same room)
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/relation/DefaultRelationService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/relation/DefaultRelationService.kt
index bbcad3a4fe..da0cf45946 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/relation/DefaultRelationService.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/relation/DefaultRelationService.kt
@@ -140,13 +140,8 @@ internal class DefaultRelationService @AssistedInject constructor(
         return eventSenderProcessor.postEvent(event, cryptoSessionInfoProvider.isRoomEncrypted(roomId))
     }
 
-    override fun fetchEditHistory(eventId: String, callback: MatrixCallback<List<Event>>) {
-        val params = FetchEditHistoryTask.Params(roomId, eventId)
-        fetchEditHistoryTask
-                .configureWith(params) {
-                    this.callback = callback
-                }
-                .executeBy(taskExecutor)
+    override suspend fun fetchEditHistory(eventId: String): List<Event> {
+        return fetchEditHistoryTask.execute(FetchEditHistoryTask.Params(roomId, eventId))
     }
 
     override fun replyToMessage(eventReplied: TimelineEvent, replyText: CharSequence, autoMarkdown: Boolean): Cancelable? {
diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/edithistory/ViewEditHistoryViewModel.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/edithistory/ViewEditHistoryViewModel.kt
index 02fb8c66f2..061743e163 100644
--- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/edithistory/ViewEditHistoryViewModel.kt
+++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/edithistory/ViewEditHistoryViewModel.kt
@@ -15,6 +15,7 @@
  */
 package im.vector.app.features.home.room.detail.timeline.edithistory
 
+import androidx.lifecycle.viewModelScope
 import com.airbnb.mvrx.Fail
 import com.airbnb.mvrx.FragmentViewModelContext
 import com.airbnb.mvrx.Loading
@@ -28,10 +29,9 @@ import im.vector.app.core.date.VectorDateFormatter
 import im.vector.app.core.platform.EmptyAction
 import im.vector.app.core.platform.EmptyViewEvents
 import im.vector.app.core.platform.VectorViewModel
-import org.matrix.android.sdk.api.MatrixCallback
+import kotlinx.coroutines.launch
 import org.matrix.android.sdk.api.session.Session
 import org.matrix.android.sdk.api.session.crypto.MXCryptoError
-import org.matrix.android.sdk.api.session.events.model.Event
 import org.matrix.android.sdk.api.session.events.model.isReply
 import org.matrix.android.sdk.internal.crypto.algorithms.olm.OlmDecryptionResult
 import timber.log.Timber
@@ -68,48 +68,50 @@ class ViewEditHistoryViewModel @AssistedInject constructor(@Assisted
 
     private fun loadHistory() {
         setState { copy(editList = Loading()) }
-        room.fetchEditHistory(eventId, object : MatrixCallback<List<Event>> {
-            override fun onFailure(failure: Throwable) {
+
+        viewModelScope.launch {
+            val data = try {
+                room.fetchEditHistory(eventId)
+            } catch (failure: Throwable) {
                 setState {
                     copy(editList = Fail(failure))
                 }
+                return@launch
             }
 
-            override fun onSuccess(data: List<Event>) {
-                var originalIsReply = false
+            var originalIsReply = false
 
-                val events = data.map { event ->
-                    val timelineID = event.roomId + UUID.randomUUID().toString()
-                    event.also {
-                        // We need to check encryption
-                        if (it.isEncrypted() && it.mxDecryptionResult == null) {
-                            // for now decrypt sync
-                            try {
-                                val result = session.cryptoService().decryptEvent(it, timelineID)
-                                it.mxDecryptionResult = OlmDecryptionResult(
-                                        payload = result.clearEvent,
-                                        senderKey = result.senderCurve25519Key,
-                                        keysClaimed = result.claimedEd25519Key?.let { k -> mapOf("ed25519" to k) },
-                                        forwardingCurve25519KeyChain = result.forwardingCurve25519KeyChain
-                                )
-                            } catch (e: MXCryptoError) {
-                                Timber.w("Failed to decrypt event in history")
-                            }
-                        }
-
-                        if (event.eventId == it.eventId) {
-                            originalIsReply = it.isReply()
+            val events = data.map { event ->
+                val timelineID = event.roomId + UUID.randomUUID().toString()
+                event.also {
+                    // We need to check encryption
+                    if (it.isEncrypted() && it.mxDecryptionResult == null) {
+                        // for now decrypt sync
+                        try {
+                            val result = session.cryptoService().decryptEvent(it, timelineID)
+                            it.mxDecryptionResult = OlmDecryptionResult(
+                                    payload = result.clearEvent,
+                                    senderKey = result.senderCurve25519Key,
+                                    keysClaimed = result.claimedEd25519Key?.let { k -> mapOf("ed25519" to k) },
+                                    forwardingCurve25519KeyChain = result.forwardingCurve25519KeyChain
+                            )
+                        } catch (e: MXCryptoError) {
+                            Timber.w("Failed to decrypt event in history")
                         }
                     }
-                }
-                setState {
-                    copy(
-                            editList = Success(events),
-                            isOriginalAReply = originalIsReply
-                    )
+
+                    if (event.eventId == it.eventId) {
+                        originalIsReply = it.isReply()
+                    }
                 }
             }
-        })
+            setState {
+                copy(
+                        editList = Success(events),
+                        isOriginalAReply = originalIsReply
+                )
+            }
+        }
     }
 
     override fun handle(action: EmptyAction) {

From 7c0acc8ccf3e63ad43cff6477de69a28505c53d1 Mon Sep 17 00:00:00 2001
From: Benoit Marty <benoit@matrix.org>
Date: Tue, 2 Mar 2021 10:54:37 +0100
Subject: [PATCH 10/17] Simplify code

---
 .../edithistory/ViewEditHistoryViewModel.kt   | 34 +++++++++----------
 1 file changed, 16 insertions(+), 18 deletions(-)

diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/edithistory/ViewEditHistoryViewModel.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/edithistory/ViewEditHistoryViewModel.kt
index 061743e163..1307341446 100644
--- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/edithistory/ViewEditHistoryViewModel.kt
+++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/edithistory/ViewEditHistoryViewModel.kt
@@ -83,26 +83,24 @@ class ViewEditHistoryViewModel @AssistedInject constructor(@Assisted
 
             val events = data.map { event ->
                 val timelineID = event.roomId + UUID.randomUUID().toString()
-                event.also {
-                    // We need to check encryption
-                    if (it.isEncrypted() && it.mxDecryptionResult == null) {
-                        // for now decrypt sync
-                        try {
-                            val result = session.cryptoService().decryptEvent(it, timelineID)
-                            it.mxDecryptionResult = OlmDecryptionResult(
-                                    payload = result.clearEvent,
-                                    senderKey = result.senderCurve25519Key,
-                                    keysClaimed = result.claimedEd25519Key?.let { k -> mapOf("ed25519" to k) },
-                                    forwardingCurve25519KeyChain = result.forwardingCurve25519KeyChain
-                            )
-                        } catch (e: MXCryptoError) {
-                            Timber.w("Failed to decrypt event in history")
-                        }
+                // We need to check encryption
+                if (event.isEncrypted() && event.mxDecryptionResult == null) {
+                    // for now decrypt sync
+                    try {
+                        val result = session.cryptoService().decryptEvent(event, timelineID)
+                        event.mxDecryptionResult = OlmDecryptionResult(
+                                payload = result.clearEvent,
+                                senderKey = result.senderCurve25519Key,
+                                keysClaimed = result.claimedEd25519Key?.let { k -> mapOf("ed25519" to k) },
+                                forwardingCurve25519KeyChain = result.forwardingCurve25519KeyChain
+                        )
+                    } catch (e: MXCryptoError) {
+                        Timber.w("Failed to decrypt event in history")
                     }
+                }
 
-                    if (event.eventId == it.eventId) {
-                        originalIsReply = it.isReply()
-                    }
+                if (event.eventId == event.eventId) {
+                    originalIsReply = event.isReply()
                 }
             }
             setState {

From 237545622fe1952b1aaeedd979cca649e103f43d Mon Sep 17 00:00:00 2001
From: Benoit Marty <benoit@matrix.org>
Date: Tue, 2 Mar 2021 10:55:22 +0100
Subject: [PATCH 11/17] Fix a bug I can see thanks to rework

---
 .../detail/timeline/edithistory/ViewEditHistoryViewModel.kt     | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/edithistory/ViewEditHistoryViewModel.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/edithistory/ViewEditHistoryViewModel.kt
index 1307341446..3ce4a44812 100644
--- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/edithistory/ViewEditHistoryViewModel.kt
+++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/edithistory/ViewEditHistoryViewModel.kt
@@ -99,7 +99,7 @@ class ViewEditHistoryViewModel @AssistedInject constructor(@Assisted
                     }
                 }
 
-                if (event.eventId == event.eventId) {
+                if (event.eventId == eventId) {
                     originalIsReply = event.isReply()
                 }
             }

From f5fad8a0828e4491ce479e228b14622435d4aa55 Mon Sep 17 00:00:00 2001
From: Benoit Marty <benoit@matrix.org>
Date: Tue, 2 Mar 2021 11:00:29 +0100
Subject: [PATCH 12/17] Make it compiles

---
 .../detail/timeline/edithistory/ViewEditHistoryViewModel.kt   | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/edithistory/ViewEditHistoryViewModel.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/edithistory/ViewEditHistoryViewModel.kt
index 3ce4a44812..af814f5856 100644
--- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/edithistory/ViewEditHistoryViewModel.kt
+++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/edithistory/ViewEditHistoryViewModel.kt
@@ -81,7 +81,7 @@ class ViewEditHistoryViewModel @AssistedInject constructor(@Assisted
 
             var originalIsReply = false
 
-            val events = data.map { event ->
+            data.forEach { event ->
                 val timelineID = event.roomId + UUID.randomUUID().toString()
                 // We need to check encryption
                 if (event.isEncrypted() && event.mxDecryptionResult == null) {
@@ -105,7 +105,7 @@ class ViewEditHistoryViewModel @AssistedInject constructor(@Assisted
             }
             setState {
                 copy(
-                        editList = Success(events),
+                        editList = Success(data),
                         isOriginalAReply = originalIsReply
                 )
             }

From c33af6de6a9f1adda23cfbf33cadc177a96d0265 Mon Sep 17 00:00:00 2001
From: Benoit Marty <benoit@matrix.org>
Date: Tue, 2 Mar 2021 11:11:28 +0100
Subject: [PATCH 13/17] Do not show edition from other users

---
 .../api/session/room/model/relation/RelationService.kt   | 3 +++
 .../session/room/relation/FetchEditHistoryTask.kt        | 9 ++++++---
 2 files changed, 9 insertions(+), 3 deletions(-)

diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/relation/RelationService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/relation/RelationService.kt
index 4638dd204a..b83391fa06 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/relation/RelationService.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/relation/RelationService.kt
@@ -92,6 +92,9 @@ interface RelationService {
 
     /**
      * Get the edit history of the given event
+     * The return list will contain the original event and all the editions of this event, done by the
+     * same sender, sorted in the reverse order (so the original event is the latest element, and the
+     * latest edition is the first element of the list)
      */
     suspend fun fetchEditHistory(eventId: String): List<Event>
 
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/relation/FetchEditHistoryTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/relation/FetchEditHistoryTask.kt
index 854585ca29..f1c8fd49a4 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/relation/FetchEditHistoryTask.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/relation/FetchEditHistoryTask.kt
@@ -49,8 +49,11 @@ internal class DefaultFetchEditHistoryTask @Inject constructor(
             )
         }
 
-        val events = response.chunks.toMutableList()
-        response.originalEvent?.let { events.add(it) }
-        return events
+        // Filter out edition form other users
+        val originalSenderId = response.originalEvent?.senderId
+        val events = response.chunks.filter {
+            it.senderId == originalSenderId
+        }
+        return events + listOfNotNull(response.originalEvent)
     }
 }

From 95395945f21397bef9bba407a1b2a3f79eb0e3b2 Mon Sep 17 00:00:00 2001
From: Benoit Marty <benoit@matrix.org>
Date: Tue, 2 Mar 2021 11:18:06 +0100
Subject: [PATCH 14/17] Also filter redacted events

---
 .../session/room/relation/FetchEditHistoryTask.kt         | 8 ++++----
 1 file changed, 4 insertions(+), 4 deletions(-)

diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/relation/FetchEditHistoryTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/relation/FetchEditHistoryTask.kt
index f1c8fd49a4..f9fd5f9348 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/relation/FetchEditHistoryTask.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/relation/FetchEditHistoryTask.kt
@@ -49,11 +49,11 @@ internal class DefaultFetchEditHistoryTask @Inject constructor(
             )
         }
 
-        // Filter out edition form other users
+        // Filter out edition form other users, and redacted editions
         val originalSenderId = response.originalEvent?.senderId
-        val events = response.chunks.filter {
-            it.senderId == originalSenderId
-        }
+        val events = response.chunks
+                .filter { it.senderId == originalSenderId }
+                .filter { !it.isRedacted() }
         return events + listOfNotNull(response.originalEvent)
     }
 }

From 5d69a1ab919457fe0e4e8358720de2d38807cb2f Mon Sep 17 00:00:00 2001
From: Benoit Marty <benoit@matrix.org>
Date: Tue, 2 Mar 2021 11:30:30 +0100
Subject: [PATCH 15/17] Fix a last bug when all editions has been deleted

---
 .../mapper/EventAnnotationsSummaryMapper.kt   | 23 ++++++++++---------
 1 file changed, 12 insertions(+), 11 deletions(-)

diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/mapper/EventAnnotationsSummaryMapper.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/mapper/EventAnnotationsSummaryMapper.kt
index f4d3d74150..08d98ce3b8 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/mapper/EventAnnotationsSummaryMapper.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/mapper/EventAnnotationsSummaryMapper.kt
@@ -36,17 +36,18 @@ internal object EventAnnotationsSummaryMapper {
                             it.sourceLocalEcho.toList()
                     )
                 },
-                editSummary = annotationsSummary.editSummary?.let {
-                    val latestEdition = it.editions.maxByOrNull { editionOfEvent -> editionOfEvent.timestamp }
-                    EditAggregatedSummary(
-                            ContentMapper.map(latestEdition?.content),
-                            it.editions.filter { editionOfEvent -> !editionOfEvent.isLocalEcho }
-                                    .map { editionOfEvent -> editionOfEvent.eventId },
-                            it.editions.filter { editionOfEvent -> editionOfEvent.isLocalEcho }
-                                    .map { editionOfEvent -> editionOfEvent.eventId },
-                            latestEdition?.timestamp ?: 0L
-                    )
-                },
+                editSummary = annotationsSummary.editSummary
+                        ?.let {
+                            val latestEdition = it.editions.maxByOrNull { editionOfEvent -> editionOfEvent.timestamp } ?: return@let null
+                            EditAggregatedSummary(
+                                    aggregatedContent = ContentMapper.map(latestEdition.content),
+                                    sourceEvents = it.editions.filter { editionOfEvent -> !editionOfEvent.isLocalEcho }
+                                            .map { editionOfEvent -> editionOfEvent.eventId },
+                                    localEchos = it.editions.filter { editionOfEvent -> editionOfEvent.isLocalEcho }
+                                            .map { editionOfEvent -> editionOfEvent.eventId },
+                                    lastEditTs = latestEdition.timestamp
+                            )
+                        },
                 referencesAggregatedSummary = annotationsSummary.referencesSummaryEntity?.let {
                     ReferencesAggregatedSummary(
                             it.eventId,

From a8ba125bd2ad7f78483df16107ec063dc8d18db1 Mon Sep 17 00:00:00 2001
From: Benoit Marty <benoit@matrix.org>
Date: Tue, 2 Mar 2021 11:37:13 +0100
Subject: [PATCH 16/17] Rename val for code clarity, and use facility

---
 .../sdk/api/session/room/model/EditAggregatedSummary.kt  | 2 +-
 .../sdk/api/session/room/timeline/TimelineEvent.kt       | 3 +--
 .../database/mapper/EventAnnotationsSummaryMapper.kt     | 2 +-
 .../app/features/home/room/detail/RoomDetailViewModel.kt | 9 +++------
 .../detail/timeline/action/MessageActionsViewModel.kt    | 3 +--
 5 files changed, 7 insertions(+), 12 deletions(-)

diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/EditAggregatedSummary.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/EditAggregatedSummary.kt
index 10fb81dc7f..67bab626cb 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/EditAggregatedSummary.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/EditAggregatedSummary.kt
@@ -18,7 +18,7 @@ package org.matrix.android.sdk.api.session.room.model
 import org.matrix.android.sdk.api.session.events.model.Content
 
 data class EditAggregatedSummary(
-        val aggregatedContent: Content? = null,
+        val latestContent: Content? = null,
         // The list of the eventIDs used to build the summary (might be out of sync if chunked received from message chunk)
         val sourceEvents: List<String>,
         val localEchos: List<String>,
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/timeline/TimelineEvent.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/timeline/TimelineEvent.kt
index 53f0e5a8d3..7010a80233 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/timeline/TimelineEvent.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/timeline/TimelineEvent.kt
@@ -123,8 +123,7 @@ fun TimelineEvent.getLastMessageContent(): MessageContent? {
     return if (root.getClearType() == EventType.STICKER) {
         root.getClearContent().toModel<MessageStickerContent>()
     } else {
-        annotations?.editSummary?.aggregatedContent?.toModel()
-                ?: root.getClearContent().toModel()
+        (annotations?.editSummary?.latestContent ?: root.getClearContent()).toModel()
     }
 }
 
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/mapper/EventAnnotationsSummaryMapper.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/mapper/EventAnnotationsSummaryMapper.kt
index 08d98ce3b8..4a26b4c4bf 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/mapper/EventAnnotationsSummaryMapper.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/mapper/EventAnnotationsSummaryMapper.kt
@@ -40,7 +40,7 @@ internal object EventAnnotationsSummaryMapper {
                         ?.let {
                             val latestEdition = it.editions.maxByOrNull { editionOfEvent -> editionOfEvent.timestamp } ?: return@let null
                             EditAggregatedSummary(
-                                    aggregatedContent = ContentMapper.map(latestEdition.content),
+                                    latestContent = ContentMapper.map(latestEdition.content),
                                     sourceEvents = it.editions.filter { editionOfEvent -> !editionOfEvent.isLocalEcho }
                                             .map { editionOfEvent -> editionOfEvent.eventId },
                                     localEchos = it.editions.filter { editionOfEvent -> editionOfEvent.isLocalEcho }
diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewModel.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewModel.kt
index 601891b15a..bb700bcb0d 100644
--- a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewModel.kt
+++ b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewModel.kt
@@ -89,6 +89,7 @@ import org.matrix.android.sdk.api.session.room.read.ReadService
 import org.matrix.android.sdk.api.session.room.send.UserDraft
 import org.matrix.android.sdk.api.session.room.timeline.Timeline
 import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent
+import org.matrix.android.sdk.api.session.room.timeline.getLastMessageContent
 import org.matrix.android.sdk.api.session.room.timeline.getRelationContent
 import org.matrix.android.sdk.api.session.room.timeline.getTextEditableContent
 import org.matrix.android.sdk.api.session.widgets.model.Widget
@@ -785,9 +786,7 @@ class RoomDetailViewModel @AssistedInject constructor(
                             room.editReply(state.sendMode.timelineEvent, it, action.text.toString())
                         }
                     } else {
-                        val messageContent: MessageContent? =
-                                state.sendMode.timelineEvent.annotations?.editSummary?.aggregatedContent.toModel()
-                                        ?: state.sendMode.timelineEvent.root.getClearContent().toModel()
+                        val messageContent = state.sendMode.timelineEvent.getLastMessageContent()
                         val existingBody = messageContent?.body ?: ""
                         if (existingBody != action.text) {
                             room.editTextMessage(state.sendMode.timelineEvent.root.eventId ?: "",
@@ -802,9 +801,7 @@ class RoomDetailViewModel @AssistedInject constructor(
                     popDraft()
                 }
                 is SendMode.QUOTE -> {
-                    val messageContent: MessageContent? =
-                            state.sendMode.timelineEvent.annotations?.editSummary?.aggregatedContent.toModel()
-                                    ?: state.sendMode.timelineEvent.root.getClearContent().toModel()
+                    val messageContent = state.sendMode.timelineEvent.getLastMessageContent()
                     val textMsg = messageContent?.body
 
                     val finalText = legacyRiotQuoteText(textMsg, action.text.toString())
diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/action/MessageActionsViewModel.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/action/MessageActionsViewModel.kt
index 1697d9250e..363899c4f9 100644
--- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/action/MessageActionsViewModel.kt
+++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/action/MessageActionsViewModel.kt
@@ -229,8 +229,7 @@ class MessageActionsViewModel @AssistedInject constructor(@Assisted
     }
 
     private fun actionsForEvent(timelineEvent: TimelineEvent, actionPermissions: ActionPermissions): List<EventSharedAction> {
-        val messageContent: MessageContent? = timelineEvent.annotations?.editSummary?.aggregatedContent.toModel()
-                ?: timelineEvent.root.getClearContent().toModel()
+        val messageContent = timelineEvent.getLastMessageContent()
         val msgType = messageContent?.msgType
 
         return arrayListOf<EventSharedAction>().apply {

From b027e43615d4bbbd46333e844256156e98532a14 Mon Sep 17 00:00:00 2001
From: Benoit Marty <benoitm@matrix.org>
Date: Tue, 2 Mar 2021 11:41:20 +0100
Subject: [PATCH 17/17] Cleanup

---
 .../sdk/api/session/room/model/relation/RelationService.kt      | 1 -
 .../sdk/internal/crypto/verification/VerificationStateExt.kt    | 1 -
 .../android/sdk/internal/database/RealmSessionStoreMigration.kt | 1 -
 .../internal/session/room/EventRelationsAggregationProcessor.kt | 2 +-
 .../vector/app/features/home/room/detail/RoomDetailViewModel.kt | 1 -
 5 files changed, 1 insertion(+), 5 deletions(-)

diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/relation/RelationService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/relation/RelationService.kt
index b83391fa06..2c0e378efb 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/relation/RelationService.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/relation/RelationService.kt
@@ -16,7 +16,6 @@
 package org.matrix.android.sdk.api.session.room.model.relation
 
 import androidx.lifecycle.LiveData
-import org.matrix.android.sdk.api.MatrixCallback
 import org.matrix.android.sdk.api.session.events.model.Event
 import org.matrix.android.sdk.api.session.room.model.EventAnnotationsSummary
 import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/verification/VerificationStateExt.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/verification/VerificationStateExt.kt
index b5fba0d54c..0617f32c24 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/verification/VerificationStateExt.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/verification/VerificationStateExt.kt
@@ -33,4 +33,3 @@ internal fun VerificationState?.toState(newState: VerificationState): Verificati
     }
     return newState
 }
-
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/RealmSessionStoreMigration.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/RealmSessionStoreMigration.kt
index 820588b1ab..c7fe7ab447 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/RealmSessionStoreMigration.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/RealmSessionStoreMigration.kt
@@ -142,7 +142,6 @@ class RealmSessionStoreMigration @Inject constructor() : RealmMigration {
                 .addField(EditionOfEventFields.TIMESTAMP, Long::class.java)
                 .addField(EditionOfEventFields.IS_LOCAL_ECHO, Boolean::class.java)
 
-
         realm.schema.get("EditAggregatedSummaryEntity")
                 ?.removeField("aggregatedContent")
                 ?.removeField("sourceEvents")
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/EventRelationsAggregationProcessor.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/EventRelationsAggregationProcessor.kt
index 8269643d39..60440c6359 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/EventRelationsAggregationProcessor.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/EventRelationsAggregationProcessor.kt
@@ -448,7 +448,7 @@ internal class EventRelationsAggregationProcessor @Inject constructor(@UserId pr
             Timber.w("Redaction of a replace targeting an unknown event $relatedEventId")
             return
         }
-        val sourceToDiscard = eventSummary.editSummary?.editions?.firstOrNull {it.eventId == redacted.eventId }
+        val sourceToDiscard = eventSummary.editSummary?.editions?.firstOrNull { it.eventId == redacted.eventId }
         if (sourceToDiscard == null) {
             Timber.w("Redaction of a replace that was not known in aggregation $sourceToDiscard")
             return
diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewModel.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewModel.kt
index bb700bcb0d..4247ffdd54 100644
--- a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewModel.kt
+++ b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewModel.kt
@@ -79,7 +79,6 @@ import org.matrix.android.sdk.api.session.room.model.Membership
 import org.matrix.android.sdk.api.session.room.model.PowerLevelsContent
 import org.matrix.android.sdk.api.session.room.model.RoomMemberSummary
 import org.matrix.android.sdk.api.session.room.model.RoomSummary
-import org.matrix.android.sdk.api.session.room.model.message.MessageContent
 import org.matrix.android.sdk.api.session.room.model.message.MessageType
 import org.matrix.android.sdk.api.session.room.model.message.OptionItem
 import org.matrix.android.sdk.api.session.room.model.message.getFileUrl