From 4e3ad91c6776afc93ef8ee4f1c10a1e9598d5253 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Wed, 10 Mar 2021 22:05:31 +0100 Subject: [PATCH 01/34] Version++ --- vector/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vector/build.gradle b/vector/build.gradle index 8bcc5bc02b..d3d98026a7 100644 --- a/vector/build.gradle +++ b/vector/build.gradle @@ -14,7 +14,7 @@ kapt { // Note: 2 digits max for each value ext.versionMajor = 1 ext.versionMinor = 1 -ext.versionPatch = 1 +ext.versionPatch = 2 static def getGitTimestamp() { def cmd = 'git show -s --format=%ct' From 507eba3ddb24cd37958461a433dc15bec0900652 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 10 Mar 2021 21:08:12 +0000 Subject: [PATCH 02/34] Bump barista from 3.8.0 to 3.9.0 Bumps barista from 3.8.0 to 3.9.0. Signed-off-by: dependabot[bot] --- vector/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vector/build.gradle b/vector/build.gradle index d3d98026a7..94fa820346 100644 --- a/vector/build.gradle +++ b/vector/build.gradle @@ -476,7 +476,7 @@ dependencies { // Plant Timber tree for test androidTestImplementation 'net.lachlanmckee:timber-junit-rule:1.0.1' // "The one who serves a great Espresso" - androidTestImplementation('com.schibsted.spain:barista:3.8.0') { + androidTestImplementation('com.schibsted.spain:barista:3.9.0') { exclude group: 'org.jetbrains.kotlin' } } From cbfb5d3fcc6f1fb13466a9aca0476eb2267800d3 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Thu, 11 Mar 2021 10:58:07 +0100 Subject: [PATCH 03/34] Fix color and bottom margin And improve a11y --- .../bottomsheet/BottomSheetMessagePreviewItem.kt | 1 + .../layout/item_bottom_sheet_message_preview.xml | 16 +++++++++------- 2 files changed, 10 insertions(+), 7 deletions(-) diff --git a/vector/src/main/java/im/vector/app/core/epoxy/bottomsheet/BottomSheetMessagePreviewItem.kt b/vector/src/main/java/im/vector/app/core/epoxy/bottomsheet/BottomSheetMessagePreviewItem.kt index cf14cc3557..a323ce995b 100644 --- a/vector/src/main/java/im/vector/app/core/epoxy/bottomsheet/BottomSheetMessagePreviewItem.kt +++ b/vector/src/main/java/im/vector/app/core/epoxy/bottomsheet/BottomSheetMessagePreviewItem.kt @@ -57,6 +57,7 @@ abstract class BottomSheetMessagePreviewItem : VectorEpoxyModel + android:layout_height="wrap_content" + tools:background="#1FF00FF0"> @@ -45,15 +46,14 @@ android:layout_marginEnd="@dimen/layout_horizontal_margin" android:textColor="?riotx_text_secondary" android:textSize="12sp" - app:layout_constraintEnd_toEndOf="parent" app:layout_constraintBottom_toBottomOf="@id/bottom_sheet_message_preview_sender" + app:layout_constraintEnd_toEndOf="parent" tools:text="Friday 8pm" /> + From 4b8c59a23b2fad3150ee189aebabab18f09b03a8 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Thu, 11 Mar 2021 14:14:55 +0100 Subject: [PATCH 04/34] Fix crash on ViewSource of event --- vector/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vector/build.gradle b/vector/build.gradle index d3d98026a7..c568a49fb3 100644 --- a/vector/build.gradle +++ b/vector/build.gradle @@ -434,7 +434,7 @@ dependencies { implementation "androidx.emoji:emoji-appcompat:1.1.0" - implementation 'com.github.BillCarsonFr:JsonViewer:0.5' + implementation 'com.github.BillCarsonFr:JsonViewer:0.6' // WebRTC // org.webrtc:google-webrtc is for development purposes only From a407ed19037865876f195a5f771ecfaca51a68d7 Mon Sep 17 00:00:00 2001 From: ganfra Date: Thu, 11 Mar 2021 15:21:20 +0100 Subject: [PATCH 05/34] Fix live timeline event race condition --- .../room/timeline/DefaultTimelineService.kt | 2 +- .../room/timeline/LiveTimelineEvent.kt | 70 ++++++++----------- .../session/room/timeline/TimelineInput.kt | 5 -- .../internal/session/sync/RoomSyncHandler.kt | 1 - 4 files changed, 32 insertions(+), 46 deletions(-) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/DefaultTimelineService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/DefaultTimelineService.kt index d000bbeb50..bb774022d4 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/DefaultTimelineService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/DefaultTimelineService.kt @@ -87,7 +87,7 @@ internal class DefaultTimelineService @AssistedInject constructor(@Assisted priv } override fun getTimeLineEventLive(eventId: String): LiveData> { - return LiveTimelineEvent(timelineInput, monarchy, taskExecutor.executorScope, timelineEventMapper, roomId, eventId) + return LiveTimelineEvent(monarchy, taskExecutor.executorScope, timelineEventMapper, roomId, eventId) } override fun getAttachmentMessages(): List { diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/LiveTimelineEvent.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/LiveTimelineEvent.kt index 3c0f101e11..eb4900553b 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/LiveTimelineEvent.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/LiveTimelineEvent.kt @@ -18,8 +18,9 @@ package org.matrix.android.sdk.internal.session.room.timeline import androidx.lifecycle.LiveData import androidx.lifecycle.MediatorLiveData -import androidx.lifecycle.Transformations import com.zhuinden.monarchy.Monarchy +import io.realm.Realm +import io.realm.RealmQuery import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch @@ -29,66 +30,57 @@ import org.matrix.android.sdk.api.util.Optional import org.matrix.android.sdk.api.util.toOptional import org.matrix.android.sdk.internal.database.mapper.TimelineEventMapper import org.matrix.android.sdk.internal.database.model.TimelineEventEntity +import org.matrix.android.sdk.internal.database.model.TimelineEventEntityFields import org.matrix.android.sdk.internal.database.query.where -import java.util.concurrent.atomic.AtomicBoolean /** * This class takes care of handling case where local echo is replaced by the synced event in the db. */ -internal class LiveTimelineEvent(private val timelineInput: TimelineInput, - private val monarchy: Monarchy, +internal class LiveTimelineEvent(private val monarchy: Monarchy, private val coroutineScope: CoroutineScope, private val timelineEventMapper: TimelineEventMapper, private val roomId: String, private val eventId: String) - : TimelineInput.Listener, - MediatorLiveData>() { - - private var queryLiveData: LiveData>? = null - - // If we are listening to local echo, we want to be aware when event is synced - private var shouldObserveSync = AtomicBoolean(LocalEcho.isLocalEchoId(eventId)) + : MediatorLiveData>() { init { - buildAndObserveQuery(eventId) + buildAndObserveQuery() } + private var initialLiveData: LiveData>? = null + // Makes sure it's made on the main thread - private fun buildAndObserveQuery(eventIdToObserve: String) = coroutineScope.launch(Dispatchers.Main) { - queryLiveData?.also { - removeSource(it) - } + private fun buildAndObserveQuery() = coroutineScope.launch(Dispatchers.Main) { val liveData = monarchy.findAllMappedWithChanges( - { TimelineEventEntity.where(it, roomId = roomId, eventId = eventIdToObserve) }, + { TimelineEventEntity.where(it, roomId = roomId, eventId = eventId) }, { timelineEventMapper.map(it) } ) - queryLiveData = Transformations.map(liveData) { events -> - events.firstOrNull().toOptional() - }.also { - addSource(it) { newValue -> value = newValue } + addSource(liveData) { newValue -> + value = newValue.firstOrNull().toOptional() + } + initialLiveData = liveData + if (LocalEcho.isLocalEchoId(eventId)) { + observeTimelineEventWithTxId() } } - override fun onLocalEchoSynced(roomId: String, localEchoEventId: String, syncedEventId: String) { - if (this.roomId == roomId && localEchoEventId == this.eventId) { - timelineInput.listeners.remove(this) - shouldObserveSync.set(false) - // rebuild the query with the new eventId - buildAndObserveQuery(syncedEventId) + private fun observeTimelineEventWithTxId() { + val liveData = monarchy.findAllMappedWithChanges( + { it.queryTimelineEventWithTxId() }, + { timelineEventMapper.map(it) } + ) + addSource(liveData) { newValue -> + val optionalValue = newValue.firstOrNull().toOptional() + if (optionalValue.hasValue()) { + initialLiveData?.also { removeSource(it) } + value = optionalValue + } } } - override fun onActive() { - super.onActive() - if (shouldObserveSync.get()) { - timelineInput.listeners.add(this) - } - } - - override fun onInactive() { - super.onInactive() - if (shouldObserveSync.get()) { - timelineInput.listeners.remove(this) - } + private fun Realm.queryTimelineEventWithTxId(): RealmQuery { + return where(TimelineEventEntity::class.java) + .equalTo(TimelineEventEntityFields.ROOM_ID, roomId) + .like(TimelineEventEntityFields.ROOT.UNSIGNED_DATA, """{*"transaction_id":*"$eventId"*}""") } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/TimelineInput.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/TimelineInput.kt index 8911f265d5..cdc85ea722 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/TimelineInput.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/TimelineInput.kt @@ -35,16 +35,11 @@ internal class TimelineInput @Inject constructor() { listeners.toSet().forEach { it.onNewTimelineEvents(roomId, eventIds) } } - fun onLocalEchoSynced(roomId: String, localEchoEventId: String, syncEventId: String) { - listeners.toSet().forEach { it.onLocalEchoSynced(roomId, localEchoEventId, syncEventId) } - } - val listeners = mutableSetOf() internal interface Listener { fun onLocalEchoCreated(roomId: String, timelineEvent: TimelineEvent) = Unit fun onLocalEchoUpdated(roomId: String, eventId: String, sendState: SendState) = Unit fun onNewTimelineEvents(roomId: String, eventIds: List) = Unit - fun onLocalEchoSynced(roomId: String, localEchoEventId: String, syncedEventId: String) = Unit } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/RoomSyncHandler.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/RoomSyncHandler.kt index 336a83eaad..b2db6320f1 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/RoomSyncHandler.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/RoomSyncHandler.kt @@ -400,7 +400,6 @@ internal class RoomSyncHandler @Inject constructor(private val readReceiptHandle event.mxDecryptionResult = adapter.fromJson(json) } } - timelineInput.onLocalEchoSynced(roomId, it, event.eventId) // Finally delete the local echo sendingEventEntity.deleteOnCascade(true) } else { From 992cc0addd405cc692150f20ca06c7bc31ae740b Mon Sep 17 00:00:00 2001 From: ganfra Date: Thu, 11 Mar 2021 19:34:34 +0100 Subject: [PATCH 06/34] Message state: add sent status in BottomSheet --- .../action/MessageActionsEpoxyController.kt | 17 +++++++++++++---- vector/src/main/res/values/strings.xml | 2 ++ 2 files changed, 15 insertions(+), 4 deletions(-) diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/action/MessageActionsEpoxyController.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/action/MessageActionsEpoxyController.kt index 1e93c29673..4e1492aaba 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/action/MessageActionsEpoxyController.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/action/MessageActionsEpoxyController.kt @@ -73,12 +73,19 @@ class MessageActionsEpoxyController @Inject constructor( text(stringProvider.getString(R.string.unable_to_send_message)) drawableStart(R.drawable.ic_warning_badge) } - } else if (sendState != SendState.SYNCED) { + } else if (sendState?.isSending().orFalse()) { bottomSheetSendStateItem { id("send_state") showProgress(true) text(stringProvider.getString(R.string.event_status_sending_message)) } + } else if (sendState == SendState.SENT) { + bottomSheetSendStateItem { + id("send_state") + showProgress(false) + drawableStart(R.drawable.ic_message_sent) + text(stringProvider.getString(R.string.event_status_sent_message)) + } } when (state.informationData.e2eDecoration) { @@ -124,9 +131,11 @@ class MessageActionsEpoxyController @Inject constructor( } } - // Separator - dividerItem { - id("actions_separator") + if (state.actions.isNotEmpty()) { + // Separator + dividerItem { + id("actions_separator") + } } // Action diff --git a/vector/src/main/res/values/strings.xml b/vector/src/main/res/values/strings.xml index 36ee7898e5..634b91bf90 100644 --- a/vector/src/main/res/values/strings.xml +++ b/vector/src/main/res/values/strings.xml @@ -208,6 +208,8 @@ Initial Sync:\nImporting Communities Initial Sync:\nImporting Account Data + + Message sent Sending message… Clear sending queue From e8d4fab3055eac7a523062381950985e1839f4df Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Fri, 12 Mar 2021 12:17:29 +0100 Subject: [PATCH 07/34] Add template for 1.1.2 --- CHANGES.md | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/CHANGES.md b/CHANGES.md index c4d4c00980..e4ff049550 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,3 +1,30 @@ +Changes in Element 1.1.2 (2021-XX-XX) +=================================================== + +Features ✨: + - + +Improvements 🙌: + - + +Bugfix 🐛: + - + +Translations 🗣: + - + +SDK API changes ⚠️: + - + +Build 🧱: + - + +Test: + - + +Other changes: + - + Changes in Element 1.1.1 (2021-XX-XX) =================================================== From 3777b00ad7448876ee508f260040d9820885ef6f Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Thu, 11 Mar 2021 21:54:33 +0100 Subject: [PATCH 08/34] Store Ephemeral in files to handle them later (no second transaction) --- .../session/sync/InitialSyncStrategy.kt | 4 +- .../session/sync/ReadReceiptHandler.kt | 57 +++++++++++++- .../sync/RoomSyncEphemeralTemporaryStore.kt | 77 +++++++++++++++++++ .../internal/session/sync/RoomSyncHandler.kt | 31 ++------ .../sdk/internal/session/sync/SyncModule.kt | 3 + .../session/sync/SyncResponseHandler.kt | 9 --- .../sdk/internal/session/sync/SyncTask.kt | 26 +++---- .../sync/model/LazyRoomSyncEphemeral.kt | 20 +---- ...DefaultLazyRoomSyncEphemeralJsonAdapter.kt | 21 ++--- .../sync/parsing/InitialSyncResponseParser.kt | 12 ++- 10 files changed, 171 insertions(+), 89 deletions(-) create mode 100644 matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/RoomSyncEphemeralTemporaryStore.kt diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/InitialSyncStrategy.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/InitialSyncStrategy.kt index 297cc213ed..7d93e30191 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/InitialSyncStrategy.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/InitialSyncStrategy.kt @@ -42,9 +42,9 @@ sealed class InitialSyncStrategy { val minSizeToSplit: Long = 1024 * 1024, /** * Limit per room to reach to decide to store a join room ephemeral Events into a file - * Empiric value: 6 kilobytes + * Empiric value: 1 kilobytes */ - val minSizeToStoreInFile: Long = 6 * 1024, + val minSizeToStoreInFile: Long = 1024, /** * Max number of rooms to insert at a time in database (to avoid too much RAM usage) */ diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/ReadReceiptHandler.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/ReadReceiptHandler.kt index a3c5891f68..7379962620 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/ReadReceiptHandler.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/ReadReceiptHandler.kt @@ -16,12 +16,13 @@ package org.matrix.android.sdk.internal.session.sync +import io.realm.Realm +import org.matrix.android.sdk.api.session.events.model.EventType import org.matrix.android.sdk.internal.database.model.ReadReceiptEntity import org.matrix.android.sdk.internal.database.model.ReadReceiptsSummaryEntity import org.matrix.android.sdk.internal.database.query.createUnmanaged import org.matrix.android.sdk.internal.database.query.getOrCreate import org.matrix.android.sdk.internal.database.query.where -import io.realm.Realm import timber.log.Timber import javax.inject.Inject @@ -35,7 +36,9 @@ typealias ReadReceiptContent = Map + * { + * "type": "m.receipt", + * "content": { + * "$ofZhdeinmEReG_X-agD3J2TIhosEPkuvl62HJ8pVMMs": { + * "m.read": { + * "@benoit.marty:matrix.org": { + * "ts": 1610468193999 + * } + * } + * }, + * "$ZMa_qwE_w_ZOj_vAxv7JuJeHCQfYzuQblmIZxkYmNMs": { + * "m.read": { + * "@benoitx:matrix.org": { + * "ts": 1610468049579 + * }, + * "@benoit.marty:matrix.org": { + * "ts": 1609156029466 + * } + * } + * } + * } + * } + * + */ private fun incrementalSyncStrategy(realm: Realm, roomId: String, content: ReadReceiptContent) { + // First check if we have data from init sync to handle + // TODO Rename contentFromInitSync + val initSyncContent = roomSyncEphemeralTemporaryStore.read(roomId) + ?.events + ?.firstOrNull { it.type == EventType.RECEIPT } + ?.let { + @Suppress("UNCHECKED_CAST") + it.content as? ReadReceiptContent + } + ?.also { + Timber.w("INIT_SYNC Copy RR for room $roomId") + } + + initSyncContent?.let { + Timber.w("BOOK Copy RR for room $roomId") + + // TODO Merge with data we just received + // TODO Store that when we enter the timeline + initialSyncStrategy(realm, roomId, it) + roomSyncEphemeralTemporaryStore.delete(roomId) + } + for ((eventId, receiptDict) in content) { val userIdsDict = receiptDict[READ_KEY] ?: continue val readReceiptsSummary = ReadReceiptsSummaryEntity.where(realm, eventId).findFirst() diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/RoomSyncEphemeralTemporaryStore.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/RoomSyncEphemeralTemporaryStore.kt new file mode 100644 index 0000000000..8d53c63bcc --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/RoomSyncEphemeralTemporaryStore.kt @@ -0,0 +1,77 @@ +/* + * 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.session.sync + +import com.squareup.moshi.JsonReader +import okio.buffer +import okio.source +import org.matrix.android.sdk.internal.di.MoshiProvider +import org.matrix.android.sdk.internal.di.SessionFilesDirectory +import org.matrix.android.sdk.internal.session.sync.model.RoomSyncEphemeral +import org.matrix.android.sdk.internal.util.md5 +import timber.log.Timber +import java.io.File +import javax.inject.Inject + +internal interface RoomSyncEphemeralTemporaryStore { + fun write(roomId: String, roomSyncEphemeralJson: String) + fun read(roomId: String): RoomSyncEphemeral? + fun reset() + fun delete(roomId: String) +} + +internal class RoomSyncEphemeralTemporaryStoreFile @Inject constructor( + @SessionFilesDirectory private val fileDirectory: File +) : RoomSyncEphemeralTemporaryStore { + + private val workingDir = File(fileDirectory, "rr") + .also { it.mkdirs() } + + /** + * Write RoomSyncEphemeral to a file + */ + override fun write(roomId: String, roomSyncEphemeralJson: String) { + Timber.w("INIT_SYNC Store RR for room $roomId") + getFile(roomId).writeText(roomSyncEphemeralJson) + } + + /** + * Read RoomSyncEphemeral from a file, or null if there is no file to read + */ + override fun read(roomId: String): RoomSyncEphemeral? { + return getFile(roomId) + .takeIf { it.exists() } + ?.inputStream() + ?.use { pos -> + MoshiProvider.providesMoshi().adapter(RoomSyncEphemeral::class.java) + .fromJson(JsonReader.of(pos.source().buffer())) + } + } + + override fun delete(roomId: String) { + getFile(roomId).delete() + } + + override fun reset() { + workingDir.deleteRecursively() + workingDir.mkdirs() + } + + private fun getFile(roomId: String): File { + return File(workingDir, "${roomId.md5()}.json") + } +} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/RoomSyncHandler.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/RoomSyncHandler.kt index b2db6320f1..99ffa80760 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/RoomSyncHandler.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/RoomSyncHandler.kt @@ -60,6 +60,7 @@ import org.matrix.android.sdk.internal.session.room.timeline.PaginationDirection import org.matrix.android.sdk.internal.session.room.timeline.TimelineInput import org.matrix.android.sdk.internal.session.room.typing.TypingEventContent import org.matrix.android.sdk.internal.session.sync.model.InvitedRoomSync +import org.matrix.android.sdk.internal.session.sync.model.LazyRoomSyncEphemeral import org.matrix.android.sdk.internal.session.sync.model.RoomSync import org.matrix.android.sdk.internal.session.sync.model.RoomSyncAccountData import org.matrix.android.sdk.internal.session.sync.model.RoomsSyncResponse @@ -94,19 +95,6 @@ internal class RoomSyncHandler @Inject constructor(private val readReceiptHandle handleRoomSync(realm, HandlingStrategy.LEFT(roomsSyncResponse.leave), isInitialSync, reporter) } - fun handleInitSyncEphemeral(realm: Realm, - roomsSyncResponse: RoomsSyncResponse) { - roomsSyncResponse.join.forEach { roomSync -> - val ephemeralResult = roomSync.value.ephemeral - ?.roomSyncEphemeral - ?.events - ?.takeIf { it.isNotEmpty() } - ?.let { events -> handleEphemeral(realm, roomSync.key, events, true) } - - roomTypingUsersHandler.handle(realm, roomSync.key, ephemeralResult) - } - } - // PRIVATE METHODS ***************************************************************************** private fun handleRoomSync(realm: Realm, handlingStrategy: HandlingStrategy, isInitialSync: Boolean, reporter: ProgressReporter?) { @@ -124,7 +112,7 @@ internal class RoomSyncHandler @Inject constructor(private val readReceiptHandle emptyList() } else { handlingStrategy.data.mapWithProgress(reporter, InitSyncStep.ImportingAccountJoinedRooms, 0.6f) { - handleJoinedRoom(realm, it.key, it.value, true, insertType, syncLocalTimeStampMillis) + handleJoinedRoom(realm, it.key, it.value, insertType, syncLocalTimeStampMillis) } } } @@ -165,7 +153,6 @@ internal class RoomSyncHandler @Inject constructor(private val readReceiptHandle realm = realm, roomId = it, roomSync = handlingStrategy.data[it] ?: error("Should not happen"), - handleEphemeralEvents = false, insertType = EventInsertType.INITIAL_SYNC, syncLocalTimestampMillis = syncLocalTimeStampMillis ) @@ -177,7 +164,7 @@ internal class RoomSyncHandler @Inject constructor(private val readReceiptHandle } else { // No need to split val rooms = handlingStrategy.data.mapWithProgress(reporter, InitSyncStep.ImportingAccountJoinedRooms, 0.6f) { - handleJoinedRoom(realm, it.key, it.value, false, EventInsertType.INITIAL_SYNC, syncLocalTimeStampMillis) + handleJoinedRoom(realm, it.key, it.value, EventInsertType.INITIAL_SYNC, syncLocalTimeStampMillis) } realm.insertOrUpdate(rooms) } @@ -186,17 +173,15 @@ internal class RoomSyncHandler @Inject constructor(private val readReceiptHandle private fun handleJoinedRoom(realm: Realm, roomId: String, roomSync: RoomSync, - handleEphemeralEvents: Boolean, insertType: EventInsertType, syncLocalTimestampMillis: Long): RoomEntity { Timber.v("Handle join sync for room $roomId") - var ephemeralResult: EphemeralResult? = null - if (handleEphemeralEvents) { - ephemeralResult = roomSync.ephemeral?.roomSyncEphemeral?.events - ?.takeIf { it.isNotEmpty() } - ?.let { handleEphemeral(realm, roomId, it, insertType == EventInsertType.INITIAL_SYNC) } - } + val ephemeralResult = (roomSync.ephemeral as? LazyRoomSyncEphemeral.Parsed) + ?._roomSyncEphemeral + ?.events + ?.takeIf { it.isNotEmpty() } + ?.let { handleEphemeral(realm, roomId, it, insertType == EventInsertType.INITIAL_SYNC) } if (roomSync.accountData?.events?.isNotEmpty() == true) { handleRoomAccountDataEvents(realm, roomId, roomSync.accountData) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/SyncModule.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/SyncModule.kt index 010c029c97..4b31dc4d9b 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/SyncModule.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/SyncModule.kt @@ -37,4 +37,7 @@ internal abstract class SyncModule { @Binds abstract fun bindSyncTask(task: DefaultSyncTask): SyncTask + + @Binds + abstract fun bindRoomSyncEphemeralTemporaryStore(store: RoomSyncEphemeralTemporaryStoreFile): RoomSyncEphemeralTemporaryStore } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/SyncResponseHandler.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/SyncResponseHandler.kt index d17a672485..fab1369aff 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/SyncResponseHandler.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/SyncResponseHandler.kt @@ -128,15 +128,6 @@ internal class SyncResponseHandler @Inject constructor(@SessionDatabase private cryptoSyncHandler.onSyncCompleted(syncResponse) } - suspend fun handleInitSyncSecondTransaction(syncResponse: SyncResponse) { - // Start another transaction to handle the ephemeral events - monarchy.awaitTransaction { realm -> - if (syncResponse.rooms != null) { - roomSyncHandler.handleInitSyncEphemeral(realm, syncResponse.rooms) - } - } - } - /** * At the moment we don't get any group data through the sync, so we poll where every hour. * You can also force to refetch group data using [Group] API. diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/SyncTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/SyncTask.kt index 00060a33b1..d47ca8fa68 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/SyncTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/SyncTask.kt @@ -62,7 +62,8 @@ internal class DefaultSyncTask @Inject constructor( private val globalErrorReceiver: GlobalErrorReceiver, @SessionFilesDirectory private val fileDirectory: File, - private val syncResponseParser: InitialSyncResponseParser + private val syncResponseParser: InitialSyncResponseParser, + private val roomSyncEphemeralTemporaryStore: RoomSyncEphemeralTemporaryStore ) : SyncTask { private val workingDir = File(fileDirectory, "is") @@ -102,13 +103,16 @@ internal class DefaultSyncTask @Inject constructor( if (isInitialSync) { Timber.v("INIT_SYNC with filter: ${requestParams["filter"]}") val initSyncStrategy = initialSyncStrategy - var syncResp: SyncResponse? = null logDuration("INIT_SYNC strategy: $initSyncStrategy") { if (initSyncStrategy is InitialSyncStrategy.Optimized) { + roomSyncEphemeralTemporaryStore.reset() + workingDir.mkdirs() val file = downloadInitSyncResponse(requestParams) - syncResp = reportSubtask(initialSyncProgressService, InitSyncStep.ImportingAccount, 1, 0.7F) { + reportSubtask(initialSyncProgressService, InitSyncStep.ImportingAccount, 1, 0.7F) { handleSyncFile(file, initSyncStrategy) } + // Delete all files + workingDir.deleteRecursively() } else { val syncResponse = logDuration("INIT_SYNC Request") { executeRequest(globalErrorReceiver) { @@ -125,15 +129,6 @@ internal class DefaultSyncTask @Inject constructor( } } initialSyncProgressService.endAll() - - if (initSyncStrategy is InitialSyncStrategy.Optimized) { - logDuration("INIT_SYNC Handle ephemeral") { - syncResponseHandler.handleInitSyncSecondTransaction(syncResp!!) - } - initialSyncStatusRepository.setStep(InitialSyncStatus.STEP_SUCCESS) - // Delete all files - workingDir.deleteRecursively() - } } else { val syncResponse = executeRequest(globalErrorReceiver) { apiCall = syncAPI.sync( @@ -147,7 +142,6 @@ internal class DefaultSyncTask @Inject constructor( } private suspend fun downloadInitSyncResponse(requestParams: Map): File { - workingDir.mkdirs() val workingFile = File(workingDir, "initSync.json") val status = initialSyncStatusRepository.getStep() if (workingFile.exists() && status >= InitialSyncStatus.STEP_DOWNLOADED) { @@ -201,8 +195,8 @@ internal class DefaultSyncTask @Inject constructor( } } - private suspend fun handleSyncFile(workingFile: File, initSyncStrategy: InitialSyncStrategy.Optimized): SyncResponse { - return logDuration("INIT_SYNC handleSyncFile()") { + private suspend fun handleSyncFile(workingFile: File, initSyncStrategy: InitialSyncStrategy.Optimized) { + logDuration("INIT_SYNC handleSyncFile()") { val syncResponse = logDuration("INIT_SYNC Read file and parse") { syncResponseParser.parse(initSyncStrategy, workingFile) } @@ -215,7 +209,7 @@ internal class DefaultSyncTask @Inject constructor( logDuration("INIT_SYNC Database insertion") { syncResponseHandler.handleResponse(syncResponse, null, initialSyncProgressService) } - syncResponse + initialSyncStatusRepository.setStep(InitialSyncStatus.STEP_SUCCESS) } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/model/LazyRoomSyncEphemeral.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/model/LazyRoomSyncEphemeral.kt index 938168b5f4..83006c646b 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/model/LazyRoomSyncEphemeral.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/model/LazyRoomSyncEphemeral.kt @@ -16,28 +16,10 @@ package org.matrix.android.sdk.internal.session.sync.model -import com.squareup.moshi.JsonAdapter import com.squareup.moshi.JsonClass -import com.squareup.moshi.JsonReader -import okio.buffer -import okio.source -import java.io.File @JsonClass(generateAdapter = false) internal sealed class LazyRoomSyncEphemeral { data class Parsed(val _roomSyncEphemeral: RoomSyncEphemeral) : LazyRoomSyncEphemeral() - data class Stored(val roomSyncEphemeralAdapter: JsonAdapter, val file: File) : LazyRoomSyncEphemeral() - - val roomSyncEphemeral: RoomSyncEphemeral - get() { - return when (this) { - is Parsed -> _roomSyncEphemeral - is Stored -> { - // Parse the file now - file.inputStream().use { pos -> - roomSyncEphemeralAdapter.fromJson(JsonReader.of(pos.source().buffer()))!! - } - } - } - } + object Stored : LazyRoomSyncEphemeral() } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/parsing/DefaultLazyRoomSyncEphemeralJsonAdapter.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/parsing/DefaultLazyRoomSyncEphemeralJsonAdapter.kt index ef56802a66..22ac4f911d 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/parsing/DefaultLazyRoomSyncEphemeralJsonAdapter.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/parsing/DefaultLazyRoomSyncEphemeralJsonAdapter.kt @@ -22,11 +22,10 @@ import com.squareup.moshi.JsonReader import com.squareup.moshi.JsonWriter import com.squareup.moshi.ToJson import org.matrix.android.sdk.internal.session.sync.InitialSyncStrategy +import org.matrix.android.sdk.internal.session.sync.RoomSyncEphemeralTemporaryStore import org.matrix.android.sdk.internal.session.sync.model.LazyRoomSyncEphemeral import org.matrix.android.sdk.internal.session.sync.model.RoomSyncEphemeral import timber.log.Timber -import java.io.File -import java.util.concurrent.atomic.AtomicInteger internal class DefaultLazyRoomSyncEphemeralJsonAdapter { @@ -44,20 +43,15 @@ internal class DefaultLazyRoomSyncEphemeralJsonAdapter { } } -internal class SplitLazyRoomSyncJsonAdapter( - private val workingDirectory: File, +internal class SplitLazyRoomSyncEphemeralJsonAdapter( + private val roomSyncEphemeralTemporaryStore: RoomSyncEphemeralTemporaryStore, private val syncStrategy: InitialSyncStrategy.Optimized ) { - private val atomicInteger = AtomicInteger(0) - - private fun createFile(): File { - val index = atomicInteger.getAndIncrement() - return File(workingDirectory, "room_$index.json") - } - @FromJson fun fromJson(reader: JsonReader, delegate: JsonAdapter): LazyRoomSyncEphemeral? { val path = reader.path + val roomId = path.substringAfter("\$.rooms.join.").substringBeforeLast(".ephemeral") + val json = reader.nextSource().inputStream().bufferedReader().use { it.readText() } @@ -65,9 +59,8 @@ internal class SplitLazyRoomSyncJsonAdapter( return if (json.length > limit) { Timber.v("INIT_SYNC $path content length: ${json.length} copy to a file") // Copy the source to a file - val file = createFile() - file.writeText(json) - LazyRoomSyncEphemeral.Stored(delegate, file) + roomSyncEphemeralTemporaryStore.write(roomId, json) + LazyRoomSyncEphemeral.Stored } else { Timber.v("INIT_SYNC $path content length: ${json.length} parse it now") val roomSync = delegate.fromJson(json) ?: return null diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/parsing/InitialSyncResponseParser.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/parsing/InitialSyncResponseParser.kt index ae7b2a4468..bfa9974b77 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/parsing/InitialSyncResponseParser.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/parsing/InitialSyncResponseParser.kt @@ -20,29 +20,33 @@ import com.squareup.moshi.Moshi import okio.buffer import okio.source import org.matrix.android.sdk.internal.session.sync.InitialSyncStrategy +import org.matrix.android.sdk.internal.session.sync.RoomSyncEphemeralTemporaryStore import org.matrix.android.sdk.internal.session.sync.model.SyncResponse import timber.log.Timber import java.io.File import javax.inject.Inject -internal class InitialSyncResponseParser @Inject constructor(private val moshi: Moshi) { +internal class InitialSyncResponseParser @Inject constructor( + private val moshi: Moshi, + private val roomSyncEphemeralTemporaryStore: RoomSyncEphemeralTemporaryStore +) { fun parse(syncStrategy: InitialSyncStrategy.Optimized, workingFile: File): SyncResponse { val syncResponseLength = workingFile.length().toInt() Timber.v("INIT_SYNC Sync file size is $syncResponseLength bytes") val shouldSplit = syncResponseLength >= syncStrategy.minSizeToSplit Timber.v("INIT_SYNC should split in several files: $shouldSplit") - return getMoshi(syncStrategy, workingFile.parentFile!!, shouldSplit) + return getMoshi(syncStrategy, shouldSplit) .adapter(SyncResponse::class.java) .fromJson(workingFile.source().buffer())!! } - private fun getMoshi(syncStrategy: InitialSyncStrategy.Optimized, workingDirectory: File, shouldSplit: Boolean): Moshi { + private fun getMoshi(syncStrategy: InitialSyncStrategy.Optimized, shouldSplit: Boolean): Moshi { // If we don't have to split we'll rely on the already default moshi if (!shouldSplit) return moshi // Otherwise, we create a new adapter for handling Map of Lazy sync return moshi.newBuilder() - .add(SplitLazyRoomSyncJsonAdapter(workingDirectory, syncStrategy)) + .add(SplitLazyRoomSyncEphemeralJsonAdapter(roomSyncEphemeralTemporaryStore, syncStrategy)) .build() } } From fe39c92e25a54ffc5437e58e828cc64bcf03728f Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Thu, 11 Mar 2021 22:24:23 +0100 Subject: [PATCH 09/34] Load RR when opening a timeline --- .../session/room/timeline/DefaultTimeline.kt | 19 +++++- .../room/timeline/DefaultTimelineService.kt | 36 ++++++----- .../session/sync/ReadReceiptHandler.kt | 61 +++++-------------- .../sync/RoomSyncEphemeralTemporaryStore.kt | 2 +- 4 files changed, 54 insertions(+), 64 deletions(-) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/DefaultTimeline.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/DefaultTimeline.kt index d0946abe28..92877950eb 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/DefaultTimeline.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/DefaultTimeline.kt @@ -44,6 +44,7 @@ import org.matrix.android.sdk.internal.database.query.findAllInRoomWithSendState import org.matrix.android.sdk.internal.database.query.where import org.matrix.android.sdk.internal.database.query.whereRoomId import org.matrix.android.sdk.internal.session.room.membership.LoadRoomMembersTask +import org.matrix.android.sdk.internal.session.sync.ReadReceiptHandler import org.matrix.android.sdk.internal.task.TaskExecutor import org.matrix.android.sdk.internal.task.configureWith import org.matrix.android.sdk.internal.util.Debouncer @@ -73,7 +74,8 @@ internal class DefaultTimeline( private val timelineInput: TimelineInput, private val eventDecryptor: TimelineEventDecryptor, private val realmSessionProvider: RealmSessionProvider, - private val loadRoomMembersTask: LoadRoomMembersTask + private val loadRoomMembersTask: LoadRoomMembersTask, + private val readReceiptHandler: ReadReceiptHandler ) : Timeline, TimelineHiddenReadReceipts.Delegate, TimelineInput.Listener, @@ -182,11 +184,26 @@ internal class DefaultTimeline( } .executeBy(taskExecutor) + // Ensure ReadReceipt from init sync are loaded + ensureReadReceiptAreLoaded(realm) + isReady.set(true) } } } + private fun ensureReadReceiptAreLoaded(realm: Realm) { + readReceiptHandler.getContentFromInitSync(roomId) + ?.also { + Timber.w("INIT_SYNC Insert when opening timeline RR for room $roomId") + } + ?.let { readReceiptContent -> + realm.executeTransactionAsync { + readReceiptHandler.handle(it, roomId, readReceiptContent, true) + } + } + } + private fun TimelineSettings.shouldHandleHiddenReadReceipts(): Boolean { return buildReadReceipts && (filters.filterEdits || filters.filterTypes) } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/DefaultTimelineService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/DefaultTimelineService.kt index bb774022d4..c3714a1303 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/DefaultTimelineService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/DefaultTimelineService.kt @@ -17,10 +17,10 @@ package org.matrix.android.sdk.internal.session.room.timeline import androidx.lifecycle.LiveData -import dagger.assisted.Assisted -import dagger.assisted.AssistedInject -import dagger.assisted.AssistedFactory import com.zhuinden.monarchy.Monarchy +import dagger.assisted.Assisted +import dagger.assisted.AssistedFactory +import dagger.assisted.AssistedInject import io.realm.Sort import io.realm.kotlin.where import org.matrix.android.sdk.api.session.events.model.isImageMessage @@ -38,20 +38,23 @@ import org.matrix.android.sdk.internal.database.model.TimelineEventEntityFields import org.matrix.android.sdk.internal.database.query.where import org.matrix.android.sdk.internal.di.SessionDatabase import org.matrix.android.sdk.internal.session.room.membership.LoadRoomMembersTask +import org.matrix.android.sdk.internal.session.sync.ReadReceiptHandler import org.matrix.android.sdk.internal.task.TaskExecutor -internal class DefaultTimelineService @AssistedInject constructor(@Assisted private val roomId: String, - @SessionDatabase private val monarchy: Monarchy, - private val realmSessionProvider: RealmSessionProvider, - private val timelineInput: TimelineInput, - private val taskExecutor: TaskExecutor, - private val contextOfEventTask: GetContextOfEventTask, - private val eventDecryptor: TimelineEventDecryptor, - private val paginationTask: PaginationTask, - private val fetchTokenAndPaginateTask: FetchTokenAndPaginateTask, - private val timelineEventMapper: TimelineEventMapper, - private val readReceiptsSummaryMapper: ReadReceiptsSummaryMapper, - private val loadRoomMembersTask: LoadRoomMembersTask +internal class DefaultTimelineService @AssistedInject constructor( + @Assisted private val roomId: String, + @SessionDatabase private val monarchy: Monarchy, + private val realmSessionProvider: RealmSessionProvider, + private val timelineInput: TimelineInput, + private val taskExecutor: TaskExecutor, + private val contextOfEventTask: GetContextOfEventTask, + private val eventDecryptor: TimelineEventDecryptor, + private val paginationTask: PaginationTask, + private val fetchTokenAndPaginateTask: FetchTokenAndPaginateTask, + private val timelineEventMapper: TimelineEventMapper, + private val readReceiptsSummaryMapper: ReadReceiptsSummaryMapper, + private val loadRoomMembersTask: LoadRoomMembersTask, + private val readReceiptHandler: ReadReceiptHandler ) : TimelineService { @AssistedFactory @@ -74,7 +77,8 @@ internal class DefaultTimelineService @AssistedInject constructor(@Assisted priv eventDecryptor = eventDecryptor, fetchTokenAndPaginateTask = fetchTokenAndPaginateTask, realmSessionProvider = realmSessionProvider, - loadRoomMembersTask = loadRoomMembersTask + loadRoomMembersTask = loadRoomMembersTask, + readReceiptHandler = readReceiptHandler ) } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/ReadReceiptHandler.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/ReadReceiptHandler.kt index 7379962620..c24196ca8f 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/ReadReceiptHandler.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/ReadReceiptHandler.kt @@ -90,55 +90,11 @@ internal class ReadReceiptHandler @Inject constructor( realm.insertOrUpdate(readReceiptSummaries) } - /** - * Example of content: - * - *
-     * {
-     *     "type": "m.receipt",
-     *     "content": {
-     *         "$ofZhdeinmEReG_X-agD3J2TIhosEPkuvl62HJ8pVMMs": {
-     *             "m.read": {
-     *                 "@benoit.marty:matrix.org": {
-     *                     "ts": 1610468193999
-     *                 }
-     *             }
-     *         },
-     *         "$ZMa_qwE_w_ZOj_vAxv7JuJeHCQfYzuQblmIZxkYmNMs": {
-     *             "m.read": {
-     *                 "@benoitx:matrix.org": {
-     *                     "ts": 1610468049579
-     *                 },
-     *                 "@benoit.marty:matrix.org": {
-     *                     "ts": 1609156029466
-     *                 }
-     *             }
-     *         }
-     *     }
-     * }
-     * 
- */ private fun incrementalSyncStrategy(realm: Realm, roomId: String, content: ReadReceiptContent) { // First check if we have data from init sync to handle - // TODO Rename contentFromInitSync - val initSyncContent = roomSyncEphemeralTemporaryStore.read(roomId) - ?.events - ?.firstOrNull { it.type == EventType.RECEIPT } - ?.let { - @Suppress("UNCHECKED_CAST") - it.content as? ReadReceiptContent - } - ?.also { - Timber.w("INIT_SYNC Copy RR for room $roomId") - } - - initSyncContent?.let { - Timber.w("BOOK Copy RR for room $roomId") - - // TODO Merge with data we just received - // TODO Store that when we enter the timeline + getContentFromInitSync(roomId)?.let { + Timber.w("INIT_SYNC Insert during incremental sync RR for room $roomId") initialSyncStrategy(realm, roomId, it) - roomSyncEphemeralTemporaryStore.delete(roomId) } for ((eventId, receiptDict) in content) { @@ -163,4 +119,17 @@ internal class ReadReceiptHandler @Inject constructor( } } } + + fun getContentFromInitSync(roomId: String): ReadReceiptContent? { + return roomSyncEphemeralTemporaryStore.read(roomId) + ?.events + ?.firstOrNull { it.type == EventType.RECEIPT } + ?.let { + @Suppress("UNCHECKED_CAST") + it.content as? ReadReceiptContent + } + ?.also { + roomSyncEphemeralTemporaryStore.delete(roomId) + } + } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/RoomSyncEphemeralTemporaryStore.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/RoomSyncEphemeralTemporaryStore.kt index 8d53c63bcc..5241c6f725 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/RoomSyncEphemeralTemporaryStore.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/RoomSyncEphemeralTemporaryStore.kt @@ -45,7 +45,7 @@ internal class RoomSyncEphemeralTemporaryStoreFile @Inject constructor( * Write RoomSyncEphemeral to a file */ override fun write(roomId: String, roomSyncEphemeralJson: String) { - Timber.w("INIT_SYNC Store RR for room $roomId") + Timber.w("INIT_SYNC Store ephemeral events for room $roomId") getFile(roomId).writeText(roomSyncEphemeralJson) } From 0b0634b53138b941941e312b803d14cb8885b5fb Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Thu, 11 Mar 2021 22:37:56 +0100 Subject: [PATCH 10/34] Ensure tmp file is always deleted when it is read --- .../android/sdk/internal/session/sync/ReadReceiptHandler.kt | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/ReadReceiptHandler.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/ReadReceiptHandler.kt index c24196ca8f..68beddfc5a 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/ReadReceiptHandler.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/ReadReceiptHandler.kt @@ -122,14 +122,12 @@ internal class ReadReceiptHandler @Inject constructor( fun getContentFromInitSync(roomId: String): ReadReceiptContent? { return roomSyncEphemeralTemporaryStore.read(roomId) + ?.also { roomSyncEphemeralTemporaryStore.delete(roomId) } ?.events ?.firstOrNull { it.type == EventType.RECEIPT } ?.let { @Suppress("UNCHECKED_CAST") it.content as? ReadReceiptContent } - ?.also { - roomSyncEphemeralTemporaryStore.delete(roomId) - } } } From 857bfcb9710784ab073a164d7d67eeb151a52a80 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Fri, 12 Mar 2021 12:16:25 +0100 Subject: [PATCH 11/34] Cleanup --- CHANGES.md | 2 +- .../android/sdk/internal/session/sync/ReadReceiptHandler.kt | 6 ++---- .../session/sync/RoomSyncEphemeralTemporaryStore.kt | 2 +- 3 files changed, 4 insertions(+), 6 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index e4ff049550..33bc122e57 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -5,7 +5,7 @@ Features ✨: - Improvements 🙌: - - + - Lazy storage of ReadReceipts Bugfix 🐛: - diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/ReadReceiptHandler.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/ReadReceiptHandler.kt index 68beddfc5a..efa578f2b8 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/ReadReceiptHandler.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/ReadReceiptHandler.kt @@ -121,13 +121,11 @@ internal class ReadReceiptHandler @Inject constructor( } fun getContentFromInitSync(roomId: String): ReadReceiptContent? { + @Suppress("UNCHECKED_CAST") return roomSyncEphemeralTemporaryStore.read(roomId) ?.also { roomSyncEphemeralTemporaryStore.delete(roomId) } ?.events ?.firstOrNull { it.type == EventType.RECEIPT } - ?.let { - @Suppress("UNCHECKED_CAST") - it.content as? ReadReceiptContent - } + ?.content as? ReadReceiptContent } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/RoomSyncEphemeralTemporaryStore.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/RoomSyncEphemeralTemporaryStore.kt index 5241c6f725..b9fd4e3d57 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/RoomSyncEphemeralTemporaryStore.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/RoomSyncEphemeralTemporaryStore.kt @@ -35,7 +35,7 @@ internal interface RoomSyncEphemeralTemporaryStore { } internal class RoomSyncEphemeralTemporaryStoreFile @Inject constructor( - @SessionFilesDirectory private val fileDirectory: File + @SessionFilesDirectory fileDirectory: File ) : RoomSyncEphemeralTemporaryStore { private val workingDir = File(fileDirectory, "rr") From e3d2186c25eeaf2446f55420250c259dd454485e Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Fri, 12 Mar 2021 18:41:31 +0100 Subject: [PATCH 12/34] Rework UpdateTrustWorker, should have better perf. --- .../crypto/crosssigning/UpdateTrustWorker.kt | 326 +++++++++--------- .../android/sdk/internal/util/LogUtil.kt | 12 + 2 files changed, 176 insertions(+), 162 deletions(-) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/crosssigning/UpdateTrustWorker.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/crosssigning/UpdateTrustWorker.kt index 1660bae0b7..a2a90c49cd 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/crosssigning/UpdateTrustWorker.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/crosssigning/UpdateTrustWorker.kt @@ -36,12 +36,14 @@ import org.matrix.android.sdk.internal.crypto.store.db.model.UserEntityFields import org.matrix.android.sdk.internal.database.model.RoomMemberSummaryEntity import org.matrix.android.sdk.internal.database.model.RoomMemberSummaryEntityFields import org.matrix.android.sdk.internal.database.model.RoomSummaryEntity +import org.matrix.android.sdk.internal.database.model.RoomSummaryEntityFields import org.matrix.android.sdk.internal.database.query.where import org.matrix.android.sdk.internal.di.CryptoDatabase import org.matrix.android.sdk.internal.di.SessionDatabase import org.matrix.android.sdk.internal.di.UserId import org.matrix.android.sdk.internal.session.SessionComponent import org.matrix.android.sdk.internal.session.room.membership.RoomMemberHelper +import org.matrix.android.sdk.internal.util.logLimit import org.matrix.android.sdk.internal.worker.SessionSafeCoroutineWorker import org.matrix.android.sdk.internal.worker.SessionWorkerParams import timber.log.Timber @@ -65,11 +67,14 @@ internal class UpdateTrustWorker(context: Context, @Inject lateinit var crossSigningService: DefaultCrossSigningService // It breaks the crypto store contract, but we need to batch things :/ - @CryptoDatabase @Inject lateinit var realmConfiguration: RealmConfiguration - @UserId @Inject lateinit var myUserId: String + @CryptoDatabase + @Inject lateinit var cryptoRealmConfiguration: RealmConfiguration + @SessionDatabase + @Inject lateinit var sessionRealmConfiguration: RealmConfiguration + @UserId + @Inject lateinit var myUserId: String @Inject lateinit var crossSigningKeysMapper: CrossSigningKeysMapper @Inject lateinit var updateTrustWorkerDataRepository: UpdateTrustWorkerDataRepository - @SessionDatabase @Inject lateinit var sessionRealmConfiguration: RealmConfiguration // @Inject lateinit var roomSummaryUpdater: RoomSummaryUpdater @Inject lateinit var cryptoStore: IMXCryptoStore @@ -79,118 +84,115 @@ internal class UpdateTrustWorker(context: Context, } override suspend fun doSafeWork(params: Params): Result { - var userList = params.filename + val userList = params.filename ?.let { updateTrustWorkerDataRepository.getParam(it) } ?.userIds ?: params.updatedUserIds.orEmpty() - if (userList.isEmpty()) { - // This should not happen, but let's avoid go further in case of empty list - cleanup(params) - return Result.success() + // List should not be empty, but let's avoid go further in case of empty list + if (userList.isNotEmpty()) { + // Unfortunately we don't have much info on what did exactly changed (is it the cross signing keys of that user, + // or a new device?) So we check all again :/ + Timber.d("## CrossSigning - Updating trust for users: ${userList.logLimit()}") + + Realm.getInstance(cryptoRealmConfiguration).use { cryptoRealm -> + Realm.getInstance(sessionRealmConfiguration).use { sessionRealm -> + updateTrust(userList, cryptoRealm, sessionRealm) + } + } } - // Unfortunately we don't have much info on what did exactly changed (is it the cross signing keys of that user, - // or a new device?) So we check all again :/ - - Timber.d("## CrossSigning - Updating trust for $userList") + cleanup(params) + return Result.success() + } + private fun updateTrust(userListParam: List, + cRealm: Realm, + sRealm: Realm) { + var userList = userListParam + var myCrossSigningInfo: MXCrossSigningInfo? = null // First we check that the users MSK are trusted by mine // After that we check the trust chain for each devices of each users - Realm.getInstance(realmConfiguration).use { realm -> - realm.executeTransaction { - // By mapping here to model, this object is not live - // I should update it if needed - var myCrossSigningInfo = realm.where(CrossSigningInfoEntity::class.java) - .equalTo(CrossSigningInfoEntityFields.USER_ID, myUserId) - .findFirst()?.let { mapCrossSigningInfoEntity(it) } + cRealm.executeTransaction { cryptoRealm -> + // By mapping here to model, this object is not live + // I should update it if needed + myCrossSigningInfo = getCrossSigningInfo(cryptoRealm, myUserId) - var myTrustResult: UserTrustResult? = null + var myTrustResult: UserTrustResult? = null - if (userList.contains(myUserId)) { - Timber.d("## CrossSigning - Clear all trust as a change on my user was detected") - // i am in the list.. but i don't know exactly the delta of change :/ - // If it's my cross signing keys we should refresh all trust - // do it anyway ? - userList = realm.where(CrossSigningInfoEntity::class.java) - .findAll().mapNotNull { it.userId } - Timber.d("## CrossSigning - Updating trust for all $userList") + if (userList.contains(myUserId)) { + Timber.d("## CrossSigning - Clear all trust as a change on my user was detected") + // i am in the list.. but i don't know exactly the delta of change :/ + // If it's my cross signing keys we should refresh all trust + // do it anyway ? + userList = cryptoRealm.where(CrossSigningInfoEntity::class.java) + .findAll() + .mapNotNull { it.userId } - // check right now my keys and mark it as trusted as other trust depends on it - val myDevices = realm.where() - .equalTo(UserEntityFields.USER_ID, myUserId) - .findFirst() - ?.devices - ?.map { deviceInfo -> - CryptoMapper.mapToModel(deviceInfo) - } - myTrustResult = crossSigningService.checkSelfTrust(myCrossSigningInfo, myDevices).also { - updateCrossSigningKeysTrust(realm, myUserId, it.isVerified()) - // update model reference - myCrossSigningInfo = realm.where(CrossSigningInfoEntity::class.java) - .equalTo(CrossSigningInfoEntityFields.USER_ID, myUserId) - .findFirst()?.let { mapCrossSigningInfoEntity(it) } - } - } + // check right now my keys and mark it as trusted as other trust depends on it + val myDevices = cryptoRealm.where() + .equalTo(UserEntityFields.USER_ID, myUserId) + .findFirst() + ?.devices + ?.map { CryptoMapper.mapToModel(it) } - val otherInfos = userList.map { - it to realm.where(CrossSigningInfoEntity::class.java) - .equalTo(CrossSigningInfoEntityFields.USER_ID, it) - .findFirst()?.let { mapCrossSigningInfoEntity(it) } - } - .toMap() + myTrustResult = crossSigningService.checkSelfTrust(myCrossSigningInfo, myDevices) + updateCrossSigningKeysTrust(cryptoRealm, myUserId, myTrustResult.isVerified()) + // update model reference + myCrossSigningInfo = getCrossSigningInfo(cryptoRealm, myUserId) + } - val trusts = otherInfos.map { infoEntry -> - infoEntry.key to when (infoEntry.key) { - myUserId -> myTrustResult - else -> { - crossSigningService.checkOtherMSKTrusted(myCrossSigningInfo, infoEntry.value).also { - Timber.d("## CrossSigning - user:${infoEntry.key} result:$it") - } + val otherInfos = userList.associateWith { userId -> + getCrossSigningInfo(cryptoRealm, userId) + } + + val trusts = otherInfos.mapValues { entry -> + when (entry.key) { + myUserId -> myTrustResult + else -> { + crossSigningService.checkOtherMSKTrusted(myCrossSigningInfo, entry.value).also { + Timber.d("## CrossSigning - user:${entry.key} result:$it") } } - }.toMap() + } + } - // TODO! if it's me and my keys has changed... I have to reset trust for everyone! - // i have all the new trusts, update DB - trusts.forEach { - val verified = it.value?.isVerified() == true - updateCrossSigningKeysTrust(realm, it.key, verified) + // TODO! if it's me and my keys has changed... I have to reset trust for everyone! + // i have all the new trusts, update DB + trusts.forEach { + val verified = it.value?.isVerified() == true + updateCrossSigningKeysTrust(cryptoRealm, it.key, verified) + } + + // Ok so now we have to check device trust for all these users.. + Timber.v("## CrossSigning - Updating devices cross trust users: ${trusts.keys.logLimit()}") + trusts.keys.forEach { userId -> + val devicesEntities = cryptoRealm.where() + .equalTo(UserEntityFields.USER_ID, userId) + .findFirst() + ?.devices + + val trustMap = devicesEntities?.associateWith { device -> + // get up to date from DB has could have been updated + val otherInfo = getCrossSigningInfo(cryptoRealm, userId) + crossSigningService.checkDeviceTrust(myCrossSigningInfo, otherInfo, CryptoMapper.mapToModel(device)) } - // Ok so now we have to check device trust for all these users.. - Timber.v("## CrossSigning - Updating devices cross trust users ${trusts.keys}") - trusts.keys.forEach { - val devicesEntities = realm.where() - .equalTo(UserEntityFields.USER_ID, it) - .findFirst() - ?.devices - - val trustMap = devicesEntities?.map { device -> - // get up to date from DB has could have been updated - val otherInfo = realm.where(CrossSigningInfoEntity::class.java) - .equalTo(CrossSigningInfoEntityFields.USER_ID, it) - .findFirst()?.let { mapCrossSigningInfoEntity(it) } - device to crossSigningService.checkDeviceTrust(myCrossSigningInfo, otherInfo, CryptoMapper.mapToModel(device)) - }?.toMap() - - // Update trust if needed - devicesEntities?.forEach { device -> - val crossSignedVerified = trustMap?.get(device)?.isCrossSignedVerified() - Timber.d("## CrossSigning - Trust for ${device.userId}|${device.deviceId} : cross verified: ${trustMap?.get(device)}") - if (device.trustLevelEntity?.crossSignedVerified != crossSignedVerified) { - Timber.d("## CrossSigning - Trust change detected for ${device.userId}|${device.deviceId} : cross verified: $crossSignedVerified") - // need to save - val trustEntity = device.trustLevelEntity - if (trustEntity == null) { - realm.createObject(TrustLevelEntity::class.java).let { - it.locallyVerified = false - it.crossSignedVerified = crossSignedVerified - device.trustLevelEntity = it - } - } else { - trustEntity.crossSignedVerified = crossSignedVerified + // Update trust if needed + devicesEntities?.forEach { device -> + val crossSignedVerified = trustMap?.get(device)?.isCrossSignedVerified() + Timber.d("## CrossSigning - Trust for ${device.userId}|${device.deviceId} : cross verified: ${trustMap?.get(device)}") + if (device.trustLevelEntity?.crossSignedVerified != crossSignedVerified) { + Timber.d("## CrossSigning - Trust change detected for ${device.userId}|${device.deviceId} : cross verified: $crossSignedVerified") + // need to save + val trustEntity = device.trustLevelEntity + if (trustEntity == null) { + device.trustLevelEntity = cryptoRealm.createObject(TrustLevelEntity::class.java).also { + it.locallyVerified = false + it.crossSignedVerified = crossSignedVerified } + } else { + trustEntity.crossSignedVerified = crossSignedVerified } } } @@ -201,35 +203,44 @@ internal class UpdateTrustWorker(context: Context, // We can now update room shields? in the session DB? Timber.d("## CrossSigning - Updating shields for impacted rooms...") - Realm.getInstance(sessionRealmConfiguration).use { it -> - it.executeTransaction { realm -> - val distinctRoomIds = realm.where(RoomMemberSummaryEntity::class.java) - .`in`(RoomMemberSummaryEntityFields.USER_ID, userList.toTypedArray()) - .distinct(RoomMemberSummaryEntityFields.ROOM_ID) - .findAll() - .map { it.roomId } - Timber.d("## CrossSigning - ... impacted rooms $distinctRoomIds") - distinctRoomIds.forEach { roomId -> - val roomSummary = RoomSummaryEntity.where(realm, roomId).findFirst() - if (roomSummary?.isEncrypted == true) { - Timber.d("## CrossSigning - Check shield state for room $roomId") - val allActiveRoomMembers = RoomMemberHelper(realm, roomId).getActiveRoomMemberIds() - try { - val updatedTrust = computeRoomShield(allActiveRoomMembers, roomSummary) - if (roomSummary.roomEncryptionTrustLevel != updatedTrust) { - Timber.d("## CrossSigning - Shield change detected for $roomId -> $updatedTrust") - roomSummary.roomEncryptionTrustLevel = updatedTrust - } - } catch (failure: Throwable) { - Timber.e(failure) - } + sRealm.executeTransaction { sessionRealm -> + sessionRealm.where(RoomMemberSummaryEntity::class.java) + .`in`(RoomMemberSummaryEntityFields.USER_ID, userList.toTypedArray()) + .distinct(RoomMemberSummaryEntityFields.ROOM_ID) + .findAll() + .map { it.roomId } + .also { Timber.d("## CrossSigning - ... impacted rooms ${it.logLimit()}") } + .forEach { roomId -> + RoomSummaryEntity.where(sessionRealm, roomId) + .equalTo(RoomSummaryEntityFields.IS_ENCRYPTED, true) + .findFirst() + ?.let { roomSummary -> + Timber.d("## CrossSigning - Check shield state for room $roomId") + val allActiveRoomMembers = RoomMemberHelper(sessionRealm, roomId).getActiveRoomMemberIds() + try { + val updatedTrust = computeRoomShield( + myCrossSigningInfo, + cRealm, + allActiveRoomMembers, + roomSummary + ) + if (roomSummary.roomEncryptionTrustLevel != updatedTrust) { + Timber.d("## CrossSigning - Shield change detected for $roomId -> $updatedTrust") + roomSummary.roomEncryptionTrustLevel = updatedTrust + } + } catch (failure: Throwable) { + Timber.e(failure) + } + } } - } - } } + } - cleanup(params) - return Result.success() + private fun getCrossSigningInfo(cryptoRealm: Realm, userId: String): MXCrossSigningInfo? { + return cryptoRealm.where(CrossSigningInfoEntity::class.java) + .equalTo(CrossSigningInfoEntityFields.USER_ID, userId) + .findFirst() + ?.let { mapCrossSigningInfoEntity(it) } } private fun cleanup(params: Params) { @@ -237,30 +248,34 @@ internal class UpdateTrustWorker(context: Context, ?.let { updateTrustWorkerDataRepository.delete(it) } } - private fun updateCrossSigningKeysTrust(realm: Realm, userId: String, verified: Boolean) { - val xInfoEntity = realm.where(CrossSigningInfoEntity::class.java) + private fun updateCrossSigningKeysTrust(cryptoRealm: Realm, userId: String, verified: Boolean) { + cryptoRealm.where(CrossSigningInfoEntity::class.java) .equalTo(CrossSigningInfoEntityFields.USER_ID, userId) .findFirst() - xInfoEntity?.crossSigningKeys?.forEach { info -> - // optimization to avoid trigger updates when there is no change.. - if (info.trustLevelEntity?.isVerified() != verified) { - Timber.d("## CrossSigning - Trust change for $userId : $verified") - val level = info.trustLevelEntity - if (level == null) { - val newLevel = realm.createObject(TrustLevelEntity::class.java) - newLevel.locallyVerified = verified - newLevel.crossSignedVerified = verified - info.trustLevelEntity = newLevel - } else { - level.locallyVerified = verified - level.crossSignedVerified = verified + ?.crossSigningKeys + ?.forEach { info -> + // optimization to avoid trigger updates when there is no change.. + if (info.trustLevelEntity?.isVerified() != verified) { + Timber.d("## CrossSigning - Trust change for $userId : $verified") + val level = info.trustLevelEntity + if (level == null) { + info.trustLevelEntity = cryptoRealm.createObject(TrustLevelEntity::class.java).also { + it.locallyVerified = verified + it.crossSignedVerified = verified + } + } else { + level.locallyVerified = verified + level.crossSignedVerified = verified + } + } } - } - } } - private fun computeRoomShield(activeMemberUserIds: List, roomSummaryEntity: RoomSummaryEntity): RoomEncryptionTrustLevel { - Timber.d("## CrossSigning - computeRoomShield ${roomSummaryEntity.roomId} -> $activeMemberUserIds") + private fun computeRoomShield(myCrossSigningInfo: MXCrossSigningInfo?, + cryptoRealm: Realm, + activeMemberUserIds: List, + roomSummaryEntity: RoomSummaryEntity): RoomEncryptionTrustLevel { + Timber.d("## CrossSigning - computeRoomShield ${roomSummaryEntity.roomId} -> ${activeMemberUserIds.logLimit()}") // The set of “all users” depends on the type of room: // For regular / topic rooms which have more than 2 members (including yourself) are considered when decorating a room // For 1:1 and group DM rooms, all other users (i.e. excluding yourself) are considered when decorating a room @@ -272,17 +287,8 @@ internal class UpdateTrustWorker(context: Context, val allTrustedUserIds = listToCheck .filter { userId -> - Realm.getInstance(realmConfiguration).use { - it.where(CrossSigningInfoEntity::class.java) - .equalTo(CrossSigningInfoEntityFields.USER_ID, userId) - .findFirst()?.let { mapCrossSigningInfoEntity(it) }?.isTrusted() == true - } + getCrossSigningInfo(cryptoRealm, userId)?.isTrusted() == true } - val myCrossKeys = Realm.getInstance(realmConfiguration).use { - it.where(CrossSigningInfoEntity::class.java) - .equalTo(CrossSigningInfoEntityFields.USER_ID, myUserId) - .findFirst()?.let { mapCrossSigningInfoEntity(it) } - } return if (allTrustedUserIds.isEmpty()) { RoomEncryptionTrustLevel.Default @@ -291,21 +297,17 @@ internal class UpdateTrustWorker(context: Context, // If all devices of all verified users are trusted -> green // else -> black allTrustedUserIds - .mapNotNull { uid -> - Realm.getInstance(realmConfiguration).use { - it.where() - .equalTo(UserEntityFields.USER_ID, uid) - .findFirst() - ?.devices - ?.map { - CryptoMapper.mapToModel(it) - } - } + .mapNotNull { userId -> + cryptoRealm.where() + .equalTo(UserEntityFields.USER_ID, userId) + .findFirst() + ?.devices + ?.map { CryptoMapper.mapToModel(it) } } .flatten() .let { allDevices -> - Timber.v("## CrossSigning - computeRoomShield ${roomSummaryEntity.roomId} devices ${allDevices.map { it.deviceId }}") - if (myCrossKeys != null) { + Timber.v("## CrossSigning - computeRoomShield ${roomSummaryEntity.roomId} devices ${allDevices.map { it.deviceId }.logLimit()}") + if (myCrossSigningInfo != null) { allDevices.any { !it.trustLevel?.crossSigningVerified.orFalse() } } else { // Legacy method diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/util/LogUtil.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/util/LogUtil.kt index fe68b49a5c..bfa723c160 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/util/LogUtil.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/util/LogUtil.kt @@ -19,6 +19,18 @@ package org.matrix.android.sdk.internal.util import org.matrix.android.sdk.BuildConfig import timber.log.Timber +internal fun Collection.logLimit(maxQuantity: Int = 5): String { + return buildString { + append(size) + append(" item(s)") + if (size > maxQuantity) { + append(", first $maxQuantity items") + } + append(": ") + append(this@logLimit.take(maxQuantity)) + } +} + internal suspend fun logDuration(message: String, block: suspend () -> T): T { Timber.v("$message -- BEGIN") From 0c774c098f5b4d5deb9586a1c1658648c2d9e0ff Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Fri, 12 Mar 2021 19:08:57 +0100 Subject: [PATCH 13/34] No op for empty list --- .../sdk/internal/session/room/summary/RoomSummaryUpdater.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/summary/RoomSummaryUpdater.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/summary/RoomSummaryUpdater.kt index fff780fb0c..4a89e1ccb4 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/summary/RoomSummaryUpdater.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/summary/RoomSummaryUpdater.kt @@ -144,7 +144,7 @@ internal class RoomSummaryUpdater @Inject constructor( roomSummaryEntity.otherMemberIds.clear() roomSummaryEntity.otherMemberIds.addAll(otherRoomMembers) - if (roomSummaryEntity.isEncrypted) { + if (roomSummaryEntity.isEncrypted && otherRoomMembers.isNotEmpty()) { // mmm maybe we could only refresh shield instead of checking trust also? crossSigningService.onUsersDeviceUpdate(otherRoomMembers) } From cb0445c785c123714c2fe2daf7c8f883b5abdc98 Mon Sep 17 00:00:00 2001 From: Jonathan de Jong Date: Sun, 14 Mar 2021 19:14:53 +0100 Subject: [PATCH 14/34] Change bold to headers --- .github/ISSUE_TEMPLATE/bug_report.md | 12 ++++++------ .github/ISSUE_TEMPLATE/feature_request.md | 8 ++++---- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md index f2fa4ef02d..d7c3506fa0 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -7,27 +7,27 @@ assignees: '' --- -**Describe the bug** +#### Describe the bug A clear and concise description of what the bug is. -**To Reproduce** +#### To Reproduce Steps to reproduce the behavior: 1. Go to '...' 2. Click on '....' 3. Scroll down to '....' 4. See error -**Expected behavior** +#### Expected behavior A clear and concise description of what you expected to happen. -**Screenshots** +#### Screenshots If applicable, add screenshots to help explain your problem. -**Smartphone (please complete the following information):** +#### Smartphone (please complete the following information): - Device: [e.g. Samsung S6] - OS: [e.g. Android 6.0] -**Additional context** +#### Additional context - App version and store [e.g. 1.0.0 - F-Droid] - Homeserver: [e.g. matrix.org] diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md index 73c93186d7..5f643c26df 100644 --- a/.github/ISSUE_TEMPLATE/feature_request.md +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -7,14 +7,14 @@ assignees: '' --- -**Is your feature request related to a problem? Please describe.** +#### *Is your feature request related to a problem? Please describe. A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] -**Describe the solution you'd like** +#### Describe the solution you'd like. A clear and concise description of what you want to happen. -**Describe alternatives you've considered** +#### Describe alternatives you've considered. A clear and concise description of any alternative solutions or features you've considered. -**Additional context** +#### Additional context Add any other context or screenshots about the feature request here. From 2708b85a209ce53e12ea8a26a073cbd7890ebcf6 Mon Sep 17 00:00:00 2001 From: Jonathan de Jong Date: Sun, 14 Mar 2021 19:17:05 +0100 Subject: [PATCH 15/34] Update CHANGES.md --- CHANGES.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGES.md b/CHANGES.md index e4ff049550..8ad8d3c774 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -23,7 +23,7 @@ Test: - Other changes: - - + - Change formatting on issue templates to proper headings. Changes in Element 1.1.1 (2021-XX-XX) =================================================== From c998f5fd8f037e39788d13664f9a22ef673fa62c Mon Sep 17 00:00:00 2001 From: Jonathan de Jong Date: Sun, 14 Mar 2021 19:20:35 +0100 Subject: [PATCH 16/34] whoops --- .github/ISSUE_TEMPLATE/feature_request.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md index 5f643c26df..da96d461c5 100644 --- a/.github/ISSUE_TEMPLATE/feature_request.md +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -7,7 +7,7 @@ assignees: '' --- -#### *Is your feature request related to a problem? Please describe. +#### Is your feature request related to a problem? Please describe. A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] #### Describe the solution you'd like. From ed662d3adde15b5b8a3e06e3b5629748f3ea1e4e Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Mon, 15 Mar 2021 14:19:23 +0100 Subject: [PATCH 17/34] Fix double RR issue when optimizing init sync --- .../internal/session/room/timeline/DefaultTimeline.kt | 2 +- .../sdk/internal/session/sync/ReadReceiptHandler.kt | 11 +++++++---- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/DefaultTimeline.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/DefaultTimeline.kt index 92877950eb..70d5f31042 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/DefaultTimeline.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/DefaultTimeline.kt @@ -199,7 +199,7 @@ internal class DefaultTimeline( } ?.let { readReceiptContent -> realm.executeTransactionAsync { - readReceiptHandler.handle(it, roomId, readReceiptContent, true) + readReceiptHandler.handle(it, roomId, readReceiptContent, false) } } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/ReadReceiptHandler.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/ReadReceiptHandler.kt index efa578f2b8..a4a48752bc 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/ReadReceiptHandler.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/ReadReceiptHandler.kt @@ -56,9 +56,8 @@ internal class ReadReceiptHandler @Inject constructor( } fun handle(realm: Realm, roomId: String, content: ReadReceiptContent?, isInitialSync: Boolean) { - if (content == null) { - return - } + content ?: return + try { handleReadReceiptContent(realm, roomId, content, isInitialSync) } catch (exception: Exception) { @@ -94,9 +93,13 @@ internal class ReadReceiptHandler @Inject constructor( // First check if we have data from init sync to handle getContentFromInitSync(roomId)?.let { Timber.w("INIT_SYNC Insert during incremental sync RR for room $roomId") - initialSyncStrategy(realm, roomId, it) + doIncrementalSyncStrategy(realm, roomId, it) } + doIncrementalSyncStrategy(realm, roomId, content) + } + + private fun doIncrementalSyncStrategy(realm: Realm, roomId: String, content: ReadReceiptContent) { for ((eventId, receiptDict) in content) { val userIdsDict = receiptDict[READ_KEY] ?: continue val readReceiptsSummary = ReadReceiptsSummaryEntity.where(realm, eventId).findFirst() From c4aadfed337e37a920c69733fb8d107e4f00ec8a Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Mon, 15 Mar 2021 14:35:21 +0100 Subject: [PATCH 18/34] Better API and avoid copying Collection --- .../android/sdk/internal/crypto/MXOlmDevice.kt | 2 +- .../crypto/algorithms/olm/MXOlmDecryption.kt | 2 +- .../sdk/internal/crypto/store/IMXCryptoStore.kt | 2 +- .../sdk/internal/crypto/store/db/RealmCryptoStore.kt | 12 ++++-------- 4 files changed, 7 insertions(+), 11 deletions(-) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/MXOlmDevice.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/MXOlmDevice.kt index b1e91e8d50..b8f1a9abea 100755 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/MXOlmDevice.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/MXOlmDevice.kt @@ -312,7 +312,7 @@ internal class MXOlmDevice @Inject constructor( * @param theirDeviceIdentityKey the Curve25519 identity key for the remote device. * @return a list of known session ids for the device. */ - fun getSessionIds(theirDeviceIdentityKey: String): Set? { + fun getSessionIds(theirDeviceIdentityKey: String): List? { return store.getDeviceSessionIds(theirDeviceIdentityKey) } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/olm/MXOlmDecryption.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/olm/MXOlmDecryption.kt index 541f62de2c..082b86c9da 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/olm/MXOlmDecryption.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/olm/MXOlmDecryption.kt @@ -154,7 +154,7 @@ internal class MXOlmDecryption( * @return payload, if decrypted successfully. */ private fun decryptMessage(message: JsonDict, theirDeviceIdentityKey: String): String? { - val sessionIds = olmDevice.getSessionIds(theirDeviceIdentityKey) ?: emptySet() + val sessionIds = olmDevice.getSessionIds(theirDeviceIdentityKey).orEmpty() val messageBody = message["body"] as? String ?: return null val messageType = when (val typeAsVoid = message["type"]) { diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/IMXCryptoStore.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/IMXCryptoStore.kt index 6f1487f80a..181bd94cc7 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/IMXCryptoStore.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/IMXCryptoStore.kt @@ -259,7 +259,7 @@ internal interface IMXCryptoStore { * @param deviceKey the public key of the other device. * @return A set of sessionId, or null if device is not known */ - fun getDeviceSessionIds(deviceKey: String): Set? + fun getDeviceSessionIds(deviceKey: String): List? /** * Retrieve an end-to-end session between the logged-in user and another diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/RealmCryptoStore.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/RealmCryptoStore.kt index b9213ba758..9ae93d61eb 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/RealmCryptoStore.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/RealmCryptoStore.kt @@ -692,7 +692,7 @@ internal class RealmCryptoStore @Inject constructor( } } - override fun getDeviceSessionIds(deviceKey: String): MutableSet { + override fun getDeviceSessionIds(deviceKey: String): List { return doWithRealm(realmConfiguration) { it.where() .equalTo(OlmSessionEntityFields.DEVICE_KEY, deviceKey) @@ -701,7 +701,6 @@ internal class RealmCryptoStore @Inject constructor( sessionEntity.sessionId } } - .toMutableSet() } override fun storeInboundGroupSessions(sessions: List) { @@ -801,7 +800,7 @@ internal class RealmCryptoStore @Inject constructor( * Note: the result will be only use to export all the keys and not to use the OlmInboundGroupSessionWrapper2, * so there is no need to use or update `inboundGroupSessionToRelease` for native memory management */ - override fun getInboundGroupSessions(): MutableList { + override fun getInboundGroupSessions(): List { return doWithRealm(realmConfiguration) { it.where() .findAll() @@ -809,7 +808,6 @@ internal class RealmCryptoStore @Inject constructor( inboundGroupSessionEntity.getInboundGroupSession() } } - .toMutableList() } override fun removeInboundGroupSession(sessionId: String, senderKey: String) { @@ -964,7 +962,7 @@ internal class RealmCryptoStore @Inject constructor( } } - override fun getRoomsListBlacklistUnverifiedDevices(): MutableList { + override fun getRoomsListBlacklistUnverifiedDevices(): List { return doWithRealm(realmConfiguration) { it.where() .equalTo(CryptoRoomEntityFields.BLACKLIST_UNVERIFIED_DEVICES, true) @@ -973,10 +971,9 @@ internal class RealmCryptoStore @Inject constructor( cryptoRoom.roomId } } - .toMutableList() } - override fun getDeviceTrackingStatuses(): MutableMap { + override fun getDeviceTrackingStatuses(): Map { return doWithRealm(realmConfiguration) { it.where() .findAll() @@ -987,7 +984,6 @@ internal class RealmCryptoStore @Inject constructor( entry.value.deviceTrackingStatus } } - .toMutableMap() } override fun saveDeviceTrackingStatuses(deviceTrackingStatuses: Map) { From 71f2c507994bcf973a4dc1fa94da8d5c95d66bd4 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Mon, 15 Mar 2021 15:02:04 +0100 Subject: [PATCH 19/34] Do what the comment said --- .../android/sdk/internal/crypto/CryptoSessionInfoProvider.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/CryptoSessionInfoProvider.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/CryptoSessionInfoProvider.kt index e8a70615e1..5338e7e92f 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/CryptoSessionInfoProvider.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/CryptoSessionInfoProvider.kt @@ -38,7 +38,7 @@ internal class CryptoSessionInfoProvider @Inject constructor( val encryptionEvent = monarchy.fetchCopied { realm -> EventEntity.whereType(realm, roomId = roomId, type = EventType.STATE_ROOM_ENCRYPTION) .contains(EventEntityFields.CONTENT, "\"algorithm\":\"$MXCRYPTO_ALGORITHM_MEGOLM\"") - .isNotNull(EventEntityFields.STATE_KEY) // should be an empty key + .isEmpty(EventEntityFields.STATE_KEY) .findFirst() } return encryptionEvent != null From 13cbfaf5e798e5d056b81a772cd64bb33cac9591 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Mon, 15 Mar 2021 15:06:20 +0100 Subject: [PATCH 20/34] Do not load room members in e2e after init sync --- CHANGES.md | 2 +- .../internal/crypto/DefaultCryptoService.kt | 11 +---- .../sdk/internal/crypto/DeviceListManager.kt | 44 ++++++++++++------- .../room/membership/LoadRoomMembersTask.kt | 8 ++++ 4 files changed, 39 insertions(+), 26 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index e4ff049550..1e72c8c38e 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -5,7 +5,7 @@ Features ✨: - Improvements 🙌: - - + - Do not load room members in e2e after init sync Bugfix 🐛: - diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/DefaultCryptoService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/DefaultCryptoService.kt index 17d25736eb..2163b2a5e0 100755 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/DefaultCryptoService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/DefaultCryptoService.kt @@ -856,15 +856,8 @@ internal class DefaultCryptoService @Inject constructor( return } cryptoCoroutineScope.launch(coroutineDispatchers.crypto) { - val params = LoadRoomMembersTask.Params(roomId) - try { - loadRoomMembersTask.execute(params) - } catch (throwable: Throwable) { - Timber.e(throwable, "## CRYPTO | onRoomEncryptionEvent ERROR FAILED TO SETUP CRYPTO ") - } finally { - val userIds = getRoomUserIds(roomId) - setEncryptionInRoom(roomId, event.content?.get("algorithm")?.toString(), true, userIds) - } + val userIds = getRoomUserIds(roomId) + setEncryptionInRoom(roomId, event.content?.get("algorithm")?.toString(), true, userIds) } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/DeviceListManager.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/DeviceListManager.kt index 42df6b354b..d9aa6d8db7 100755 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/DeviceListManager.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/DeviceListManager.kt @@ -16,6 +16,7 @@ package org.matrix.android.sdk.internal.crypto +import kotlinx.coroutines.launch import org.matrix.android.sdk.api.MatrixPatterns import org.matrix.android.sdk.api.auth.data.Credentials import org.matrix.android.sdk.internal.crypto.crosssigning.DeviceTrustLevel @@ -28,7 +29,6 @@ import org.matrix.android.sdk.internal.session.SessionScope import org.matrix.android.sdk.internal.session.sync.SyncTokenStore import org.matrix.android.sdk.internal.task.TaskExecutor import org.matrix.android.sdk.internal.util.MatrixCoroutineDispatchers -import kotlinx.coroutines.launch import timber.log.Timber import javax.inject.Inject @@ -39,8 +39,9 @@ internal class DeviceListManager @Inject constructor(private val cryptoStore: IM private val syncTokenStore: SyncTokenStore, private val credentials: Credentials, private val downloadKeysForUsersTask: DownloadKeysForUsersTask, + private val cryptoSessionInfoProvider: CryptoSessionInfoProvider, coroutineDispatchers: MatrixCoroutineDispatchers, - taskExecutor: TaskExecutor) { + private val taskExecutor: TaskExecutor) { interface UserDevicesUpdateListener { fun onUsersDeviceUpdate(userIds: List) @@ -75,8 +76,10 @@ internal class DeviceListManager @Inject constructor(private val cryptoStore: IM // HS not ready for retry private val notReadyToRetryHS = mutableSetOf() + private val cryptoCoroutineContext = coroutineDispatchers.crypto + init { - taskExecutor.executorScope.launch(coroutineDispatchers.crypto) { + taskExecutor.executorScope.launch(cryptoCoroutineContext) { var isUpdated = false val deviceTrackingStatuses = cryptoStore.getDeviceTrackingStatuses().toMutableMap() for ((userId, status) in deviceTrackingStatuses) { @@ -123,28 +126,37 @@ internal class DeviceListManager @Inject constructor(private val cryptoStore: IM } } + fun onRoomMembersLoadedFor(roomId: String) { + taskExecutor.executorScope.launch(cryptoCoroutineContext) { + if (cryptoSessionInfoProvider.isRoomEncrypted(roomId)) { + // It's OK to track also device for invited users + val userIds = cryptoSessionInfoProvider.getRoomUserIds(roomId, true) + startTrackingDeviceList(userIds) + refreshOutdatedDeviceLists() + } + } + } + /** * Mark the cached device list for the given user outdated * flag the given user for device-list tracking, if they are not already. * * @param userIds the user ids list */ - fun startTrackingDeviceList(userIds: List?) { - if (null != userIds) { - var isUpdated = false - val deviceTrackingStatuses = cryptoStore.getDeviceTrackingStatuses().toMutableMap() + fun startTrackingDeviceList(userIds: List) { + var isUpdated = false + val deviceTrackingStatuses = cryptoStore.getDeviceTrackingStatuses().toMutableMap() - for (userId in userIds) { - if (!deviceTrackingStatuses.containsKey(userId) || TRACKING_STATUS_NOT_TRACKED == deviceTrackingStatuses[userId]) { - Timber.v("## CRYPTO | startTrackingDeviceList() : Now tracking device list for $userId") - deviceTrackingStatuses[userId] = TRACKING_STATUS_PENDING_DOWNLOAD - isUpdated = true - } + for (userId in userIds) { + if (!deviceTrackingStatuses.containsKey(userId) || TRACKING_STATUS_NOT_TRACKED == deviceTrackingStatuses[userId]) { + Timber.v("## CRYPTO | startTrackingDeviceList() : Now tracking device list for $userId") + deviceTrackingStatuses[userId] = TRACKING_STATUS_PENDING_DOWNLOAD + isUpdated = true } + } - if (isUpdated) { - cryptoStore.saveDeviceTrackingStatuses(deviceTrackingStatuses) - } + if (isUpdated) { + cryptoStore.saveDeviceTrackingStatuses(deviceTrackingStatuses) } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/membership/LoadRoomMembersTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/membership/LoadRoomMembersTask.kt index 97cfcdaa44..cc491d1cd9 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/membership/LoadRoomMembersTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/membership/LoadRoomMembersTask.kt @@ -22,6 +22,8 @@ import io.realm.kotlin.createObject import kotlinx.coroutines.TimeoutCancellationException import org.matrix.android.sdk.api.session.room.model.Membership import org.matrix.android.sdk.api.session.room.send.SendState +import org.matrix.android.sdk.internal.crypto.CryptoSessionInfoProvider +import org.matrix.android.sdk.internal.crypto.DeviceListManager import org.matrix.android.sdk.internal.database.awaitNotEmptyResult import org.matrix.android.sdk.internal.database.mapper.toEntity import org.matrix.android.sdk.internal.database.model.CurrentStateEventEntity @@ -57,6 +59,8 @@ internal class DefaultLoadRoomMembersTask @Inject constructor( private val syncTokenStore: SyncTokenStore, private val roomSummaryUpdater: RoomSummaryUpdater, private val roomMemberEventHandler: RoomMemberEventHandler, + private val cryptoSessionInfoProvider: CryptoSessionInfoProvider, + private val deviceListManager: DeviceListManager, private val globalErrorReceiver: GlobalErrorReceiver ) : LoadRoomMembersTask { @@ -124,6 +128,10 @@ internal class DefaultLoadRoomMembersTask @Inject constructor( roomEntity.membersLoadStatus = RoomMembersLoadStatusType.LOADED roomSummaryUpdater.update(realm, roomId, updateMembers = true) } + + if (cryptoSessionInfoProvider.isRoomEncrypted(roomId)) { + deviceListManager.onRoomMembersLoadedFor(roomId) + } } private fun getRoomMembersLoadStatus(roomId: String): RoomMembersLoadStatusType { From 75ad6a640b015ed3f43dd3acafb96a17b35d05cd Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Mon, 15 Mar 2021 16:02:09 +0100 Subject: [PATCH 21/34] loglimit --- .../matrix/android/sdk/internal/crypto/DeviceListManager.kt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/DeviceListManager.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/DeviceListManager.kt index d9aa6d8db7..01b4f5b0fc 100755 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/DeviceListManager.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/DeviceListManager.kt @@ -29,6 +29,7 @@ import org.matrix.android.sdk.internal.session.SessionScope import org.matrix.android.sdk.internal.session.sync.SyncTokenStore import org.matrix.android.sdk.internal.task.TaskExecutor import org.matrix.android.sdk.internal.util.MatrixCoroutineDispatchers +import org.matrix.android.sdk.internal.util.logLimit import timber.log.Timber import javax.inject.Inject @@ -319,7 +320,7 @@ internal class DeviceListManager @Inject constructor(private val cryptoStore: IM * @param downloadUsers the user ids list */ private suspend fun doKeyDownloadForUsers(downloadUsers: List): MXUsersDevicesMap { - Timber.v("## CRYPTO | doKeyDownloadForUsers() : doKeyDownloadForUsers $downloadUsers") + Timber.v("## CRYPTO | doKeyDownloadForUsers() : doKeyDownloadForUsers ${downloadUsers.logLimit()}") // get the user ids which did not already trigger a keys download val filteredUsers = downloadUsers.filter { MatrixPatterns.isUserId(it) } if (filteredUsers.isEmpty()) { From 4302d50af93b9a11e48a30ca94a12bd8b5dd2651 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Mon, 15 Mar 2021 16:09:08 +0100 Subject: [PATCH 22/34] We do not display shield in the room list anymore Room member are lazy loaded, so the shield may not be accurate anymore. --- .../app/features/home/room/list/RoomSummaryItemFactory.kt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/vector/src/main/java/im/vector/app/features/home/room/list/RoomSummaryItemFactory.kt b/vector/src/main/java/im/vector/app/features/home/room/list/RoomSummaryItemFactory.kt index 988b3769c4..7d7ed1637f 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/list/RoomSummaryItemFactory.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/list/RoomSummaryItemFactory.kt @@ -92,7 +92,8 @@ class RoomSummaryItemFactory @Inject constructor(private val displayableEventFor return RoomSummaryItem_() .id(roomSummary.roomId) .avatarRenderer(avatarRenderer) - .encryptionTrustLevel(roomSummary.roomEncryptionTrustLevel) + // We do not display shield in the room list anymore + // .encryptionTrustLevel(roomSummary.roomEncryptionTrustLevel) .matrixItem(roomSummary.toMatrixItem()) .lastEventTime(latestEventTime) .typingMessage(typingMessage) From 4f3734f9328b0117e41487b4c690722ed305154e Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Mon, 15 Mar 2021 16:53:19 +0100 Subject: [PATCH 23/34] Ensure message are decrypted in the room list after a clear cache --- CHANGES.md | 2 +- .../sdk/internal/session/room/summary/RoomSummaryUpdater.kt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 1e72c8c38e..8144e0a478 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -8,7 +8,7 @@ Improvements 🙌: - Do not load room members in e2e after init sync Bugfix 🐛: - - + - Ensure message are decrypted in the room list after a clear cache Translations 🗣: - diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/summary/RoomSummaryUpdater.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/summary/RoomSummaryUpdater.kt index 4a89e1ccb4..cd1bb69612 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/summary/RoomSummaryUpdater.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/summary/RoomSummaryUpdater.kt @@ -131,8 +131,8 @@ internal class RoomSummaryUpdater @Inject constructor( // mmm i want to decrypt now or is it ok to do it async? tryOrNull { eventDecryptor.decryptEvent(root.asDomain(), "") - // eventDecryptor.decryptEventAsync(root.asDomain(), "", NoOpMatrixCallback()) } + ?.let { root.setDecryptionResult(it) } } if (updateMembers) { From e7c9fb987cbf8f23735d49c5e9b20848e157b5e3 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Mon, 15 Mar 2021 18:08:33 +0100 Subject: [PATCH 24/34] Use AwaitTransaction (G's review) --- .../crypto/crosssigning/UpdateTrustWorker.kt | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/crosssigning/UpdateTrustWorker.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/crosssigning/UpdateTrustWorker.kt index a2a90c49cd..ad82c03913 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/crosssigning/UpdateTrustWorker.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/crosssigning/UpdateTrustWorker.kt @@ -33,6 +33,7 @@ import org.matrix.android.sdk.internal.crypto.store.db.model.CryptoMapper import org.matrix.android.sdk.internal.crypto.store.db.model.TrustLevelEntity import org.matrix.android.sdk.internal.crypto.store.db.model.UserEntity import org.matrix.android.sdk.internal.crypto.store.db.model.UserEntityFields +import org.matrix.android.sdk.internal.database.awaitTransaction import org.matrix.android.sdk.internal.database.model.RoomMemberSummaryEntity import org.matrix.android.sdk.internal.database.model.RoomMemberSummaryEntityFields import org.matrix.android.sdk.internal.database.model.RoomSummaryEntity @@ -69,8 +70,10 @@ internal class UpdateTrustWorker(context: Context, // It breaks the crypto store contract, but we need to batch things :/ @CryptoDatabase @Inject lateinit var cryptoRealmConfiguration: RealmConfiguration + @SessionDatabase @Inject lateinit var sessionRealmConfiguration: RealmConfiguration + @UserId @Inject lateinit var myUserId: String @Inject lateinit var crossSigningKeysMapper: CrossSigningKeysMapper @@ -96,8 +99,8 @@ internal class UpdateTrustWorker(context: Context, Timber.d("## CrossSigning - Updating trust for users: ${userList.logLimit()}") Realm.getInstance(cryptoRealmConfiguration).use { cryptoRealm -> - Realm.getInstance(sessionRealmConfiguration).use { sessionRealm -> - updateTrust(userList, cryptoRealm, sessionRealm) + Realm.getInstance(sessionRealmConfiguration).use { + updateTrust(userList, cryptoRealm) } } } @@ -106,14 +109,13 @@ internal class UpdateTrustWorker(context: Context, return Result.success() } - private fun updateTrust(userListParam: List, - cRealm: Realm, - sRealm: Realm) { + private suspend fun updateTrust(userListParam: List, + cRealm: Realm) { var userList = userListParam var myCrossSigningInfo: MXCrossSigningInfo? = null // First we check that the users MSK are trusted by mine // After that we check the trust chain for each devices of each users - cRealm.executeTransaction { cryptoRealm -> + awaitTransaction(cryptoRealmConfiguration) { cryptoRealm -> // By mapping here to model, this object is not live // I should update it if needed myCrossSigningInfo = getCrossSigningInfo(cryptoRealm, myUserId) @@ -203,7 +205,7 @@ internal class UpdateTrustWorker(context: Context, // We can now update room shields? in the session DB? Timber.d("## CrossSigning - Updating shields for impacted rooms...") - sRealm.executeTransaction { sessionRealm -> + awaitTransaction(sessionRealmConfiguration) { sessionRealm -> sessionRealm.where(RoomMemberSummaryEntity::class.java) .`in`(RoomMemberSummaryEntityFields.USER_ID, userList.toTypedArray()) .distinct(RoomMemberSummaryEntityFields.ROOM_ID) From dd2a7397a4620b791486596fcf6d8264826e2eed Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Timo=20K=C3=B6sters?= Date: Mon, 15 Mar 2021 22:25:51 +0100 Subject: [PATCH 25/34] fix: make cross signing work with server names containing : Server names are allowed to contain ':' to specify a port, see https://matrix.org/docs/spec/appendices#server-name User ids on the other hand are not allowed to contain ':', even historical user ids, see https://matrix.org/docs/spec/appendices#historical-user-ids Therefore we can use the first instance of ':' to split the user localpart from the server name. --- CHANGES.md | 1 + .../org/matrix/android/sdk/internal/crypto/DeviceListManager.kt | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGES.md b/CHANGES.md index e4ff049550..d70e264cad 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -47,6 +47,7 @@ Bugfix 🐛: - Be robust if Event.type is missing (#2946) - Snappier message send status - Fix MainActivity display (#2927) + - Cross signing now works with servers with an explicit port in the servername Translations 🗣: - All string resources and translations have been moved to the application module. Weblate project for the SDK will be removed. diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/DeviceListManager.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/DeviceListManager.kt index 42df6b354b..92a08dfe60 100755 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/DeviceListManager.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/DeviceListManager.kt @@ -104,7 +104,7 @@ internal class DeviceListManager @Inject constructor(private val cryptoStore: IM if (':' in userId) { try { synchronized(notReadyToRetryHS) { - res = !notReadyToRetryHS.contains(userId.substringAfterLast(':')) + res = !notReadyToRetryHS.contains(userId.substringAfter(':')) } } catch (e: Exception) { Timber.e(e, "## CRYPTO | canRetryKeysDownload() failed") From e8bb3d81ed8f8b8f4b2bf73bf6c05c166b1303d3 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Tue, 16 Mar 2021 12:15:28 +0100 Subject: [PATCH 26/34] Ganfra's review: inject Moshi Moshi --- .../session/sync/RoomSyncEphemeralTemporaryStore.kt | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/RoomSyncEphemeralTemporaryStore.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/RoomSyncEphemeralTemporaryStore.kt index b9fd4e3d57..c6ff71cfcf 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/RoomSyncEphemeralTemporaryStore.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/RoomSyncEphemeralTemporaryStore.kt @@ -17,9 +17,9 @@ package org.matrix.android.sdk.internal.session.sync import com.squareup.moshi.JsonReader +import com.squareup.moshi.Moshi import okio.buffer import okio.source -import org.matrix.android.sdk.internal.di.MoshiProvider import org.matrix.android.sdk.internal.di.SessionFilesDirectory import org.matrix.android.sdk.internal.session.sync.model.RoomSyncEphemeral import org.matrix.android.sdk.internal.util.md5 @@ -35,12 +35,15 @@ internal interface RoomSyncEphemeralTemporaryStore { } internal class RoomSyncEphemeralTemporaryStoreFile @Inject constructor( - @SessionFilesDirectory fileDirectory: File + @SessionFilesDirectory fileDirectory: File, + moshi: Moshi ) : RoomSyncEphemeralTemporaryStore { private val workingDir = File(fileDirectory, "rr") .also { it.mkdirs() } + private val roomSyncEphemeralAdapter = moshi.adapter(RoomSyncEphemeral::class.java) + /** * Write RoomSyncEphemeral to a file */ @@ -57,8 +60,7 @@ internal class RoomSyncEphemeralTemporaryStoreFile @Inject constructor( .takeIf { it.exists() } ?.inputStream() ?.use { pos -> - MoshiProvider.providesMoshi().adapter(RoomSyncEphemeral::class.java) - .fromJson(JsonReader.of(pos.source().buffer())) + roomSyncEphemeralAdapter.fromJson(JsonReader.of(pos.source().buffer())) } } From 5b94540f7656b698f54ef89877d18e695aef6f3d Mon Sep 17 00:00:00 2001 From: Valere Date: Tue, 16 Mar 2021 12:16:11 +0100 Subject: [PATCH 27/34] Quick Work around to remove stuck messages --- CHANGES.md | 2 +- .../home/room/detail/RoomDetailAction.kt | 2 +- .../home/room/detail/RoomDetailFragment.kt | 20 +++++++------ .../home/room/detail/RoomDetailViewModel.kt | 4 +++ .../timeline/action/EventSharedAction.kt | 2 +- .../action/MessageActionsViewModel.kt | 28 ++++++++++++++----- 6 files changed, 40 insertions(+), 18 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index e4ff049550..d123e3c732 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -8,7 +8,7 @@ Improvements 🙌: - Bugfix 🐛: - - + - Add option to cancel stuck messages at bottom of timeline see #516 Translations 🗣: - diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailAction.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailAction.kt index ecbd0b0d30..72e614c18c 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailAction.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailAction.kt @@ -58,7 +58,7 @@ sealed class RoomDetailAction : VectorViewModelAction { data class ResendMessage(val eventId: String) : RoomDetailAction() data class RemoveFailedEcho(val eventId: String) : RoomDetailAction() - data class CancelSend(val eventId: String) : RoomDetailAction() + data class CancelSend(val eventId: String, val force: Boolean) : RoomDetailAction() data class ReplyToOptions(val eventId: String, val optionIndex: Int, val optionValue: String) : RoomDetailAction() diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailFragment.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailFragment.kt index 1a487eb065..a80aeb65b0 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailFragment.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailFragment.kt @@ -1570,14 +1570,18 @@ class RoomDetailFragment @Inject constructor( } private fun handleCancelSend(action: EventSharedAction.Cancel) { - AlertDialog.Builder(requireContext()) - .setTitle(R.string.dialog_title_confirmation) - .setMessage(getString(R.string.event_status_cancel_sending_dialog_message)) - .setNegativeButton(R.string.no, null) - .setPositiveButton(R.string.yes) { _, _ -> - roomDetailViewModel.handle(RoomDetailAction.CancelSend(action.eventId)) - } - .show() + if (action.force) { + roomDetailViewModel.handle(RoomDetailAction.CancelSend(action.eventId, true)) + } else { + AlertDialog.Builder(requireContext()) + .setTitle(R.string.dialog_title_confirmation) + .setMessage(getString(R.string.event_status_cancel_sending_dialog_message)) + .setNegativeButton(R.string.no, null) + .setPositiveButton(R.string.yes) { _, _ -> + roomDetailViewModel.handle(RoomDetailAction.CancelSend(action.eventId, false)) + } + .show() + } } override fun onAvatarClicked(informationData: MessageInformationData) { 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 115b0b29dd..af3d5461ef 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 @@ -1208,6 +1208,10 @@ class RoomDetailViewModel @AssistedInject constructor( } private fun handleCancel(action: RoomDetailAction.CancelSend) { + if (action.force) { + room.cancelSend(action.eventId) + return + } val targetEventId = action.eventId room.getTimeLineEvent(targetEventId)?.let { // State must be in one of the sending states diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/action/EventSharedAction.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/action/EventSharedAction.kt index c21d552409..d9ee7f3ccf 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/action/EventSharedAction.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/action/EventSharedAction.kt @@ -63,7 +63,7 @@ sealed class EventSharedAction(@StringRes val titleRes: Int, data class Redact(val eventId: String, val askForReason: Boolean) : EventSharedAction(R.string.message_action_item_redact, R.drawable.ic_delete, true) - data class Cancel(val eventId: String) : + data class Cancel(val eventId: String, val force: Boolean) : EventSharedAction(R.string.cancel, R.drawable.ic_close_round) data class ViewSource(val content: String) : 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 adf315a955..f748369061 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 @@ -250,6 +250,9 @@ class MessageActionsViewModel @AssistedInject constructor(@Assisted timelineEvent.root.sendState == SendState.SYNCED -> { addActionsForSyncedState(timelineEvent, actionPermissions, messageContent, msgType) } + timelineEvent.root.sendState == SendState.SENT -> { + addActionsForSentNotSyncedState(timelineEvent, actionPermissions, messageContent, msgType) + } } } } @@ -287,7 +290,24 @@ class MessageActionsViewModel @AssistedInject constructor(@Assisted private fun ArrayList.addActionsForSendingState(timelineEvent: TimelineEvent) { // TODO is uploading attachment? if (canCancel(timelineEvent)) { - add(EventSharedAction.Cancel(timelineEvent.eventId)) + add(EventSharedAction.Cancel(timelineEvent.eventId, false)) + } + } + + private fun ArrayList.addActionsForSentNotSyncedState(timelineEvent: TimelineEvent, + actionPermissions: ActionPermissions, + messageContent: MessageContent?, + msgType: String?) { + if (timelineEvent.root.sendState == SendState.SENT) { + // If sent but not synced (synapse stuck at bottom bug) + // Still offer action to cancel (will only remove local echo) + timelineEvent.root.eventId?.let { + add(EventSharedAction.Cancel(it, true)) + } + + // TODO Can be redacted + + // TODO sent by me or sufficient power level } } @@ -337,12 +357,6 @@ class MessageActionsViewModel @AssistedInject constructor(@Assisted if (canSave(msgType) && messageContent is MessageWithAttachmentContent) { add(EventSharedAction.Save(timelineEvent.eventId, messageContent)) } - - if (timelineEvent.root.sendState == SendState.SENT) { - // TODO Can be redacted - - // TODO sent by me or sufficient power level - } } if (vectorPreferences.developerMode()) { From cb17fa60dcf916c460b2292af446e5c3c64d3426 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Tue, 16 Mar 2021 13:28:36 +0100 Subject: [PATCH 28/34] Ganfra's review: delete files after the Realm transaction --- .../session/room/read/SetReadMarkersTask.kt | 2 +- .../session/room/timeline/DefaultTimeline.kt | 3 +- .../session/sync/ReadReceiptHandler.kt | 44 +++++++++++++++---- .../internal/session/sync/RoomSyncHandler.kt | 33 +++++++++----- .../session/sync/RoomTypingUsersHandler.kt | 1 + .../session/sync/SyncResponseHandler.kt | 30 ++++++++----- .../SyncResponsePostTreatmentAggregator.kt | 22 ++++++++++ ...cResponsePostTreatmentAggregatorHandler.kt | 33 ++++++++++++++ 8 files changed, 133 insertions(+), 35 deletions(-) create mode 100644 matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/SyncResponsePostTreatmentAggregator.kt create mode 100644 matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/SyncResponsePostTreatmentAggregatorHandler.kt diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/read/SetReadMarkersTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/read/SetReadMarkersTask.kt index c7f962a699..54d2307dd4 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/read/SetReadMarkersTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/read/SetReadMarkersTask.kt @@ -117,7 +117,7 @@ internal class DefaultSetReadMarkersTask @Inject constructor( } if (readReceiptId != null) { val readReceiptContent = ReadReceiptHandler.createContent(userId, readReceiptId) - readReceiptHandler.handle(realm, roomId, readReceiptContent, false) + readReceiptHandler.handle(realm, roomId, readReceiptContent, false, null) } if (shouldUpdateRoomSummary) { val roomSummary = RoomSummaryEntity.where(realm, roomId).findFirst() diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/DefaultTimeline.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/DefaultTimeline.kt index 70d5f31042..61f770b956 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/DefaultTimeline.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/DefaultTimeline.kt @@ -199,7 +199,8 @@ internal class DefaultTimeline( } ?.let { readReceiptContent -> realm.executeTransactionAsync { - readReceiptHandler.handle(it, roomId, readReceiptContent, false) + readReceiptHandler.handle(it, roomId, readReceiptContent, false, null) + readReceiptHandler.onContentFromInitSyncHandled(roomId) } } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/ReadReceiptHandler.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/ReadReceiptHandler.kt index a4a48752bc..e5d9217db7 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/ReadReceiptHandler.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/ReadReceiptHandler.kt @@ -55,21 +55,29 @@ internal class ReadReceiptHandler @Inject constructor( } } - fun handle(realm: Realm, roomId: String, content: ReadReceiptContent?, isInitialSync: Boolean) { + fun handle(realm: Realm, + roomId: String, + content: ReadReceiptContent?, + isInitialSync: Boolean, + aggregator: SyncResponsePostTreatmentAggregator?) { content ?: return try { - handleReadReceiptContent(realm, roomId, content, isInitialSync) + handleReadReceiptContent(realm, roomId, content, isInitialSync, aggregator) } catch (exception: Exception) { Timber.e("Fail to handle read receipt for room $roomId") } } - private fun handleReadReceiptContent(realm: Realm, roomId: String, content: ReadReceiptContent, isInitialSync: Boolean) { + private fun handleReadReceiptContent(realm: Realm, + roomId: String, + content: ReadReceiptContent, + isInitialSync: Boolean, + aggregator: SyncResponsePostTreatmentAggregator?) { if (isInitialSync) { initialSyncStrategy(realm, roomId, content) } else { - incrementalSyncStrategy(realm, roomId, content) + incrementalSyncStrategy(realm, roomId, content, aggregator) } } @@ -89,11 +97,15 @@ internal class ReadReceiptHandler @Inject constructor( realm.insertOrUpdate(readReceiptSummaries) } - private fun incrementalSyncStrategy(realm: Realm, roomId: String, content: ReadReceiptContent) { + private fun incrementalSyncStrategy(realm: Realm, + roomId: String, + content: ReadReceiptContent, + aggregator: SyncResponsePostTreatmentAggregator?) { // First check if we have data from init sync to handle getContentFromInitSync(roomId)?.let { Timber.w("INIT_SYNC Insert during incremental sync RR for room $roomId") doIncrementalSyncStrategy(realm, roomId, it) + aggregator?.ephemeralFilesToDelete?.add(roomId) } doIncrementalSyncStrategy(realm, roomId, content) @@ -124,11 +136,25 @@ internal class ReadReceiptHandler @Inject constructor( } fun getContentFromInitSync(roomId: String): ReadReceiptContent? { + val dataFromFile = roomSyncEphemeralTemporaryStore.read(roomId) + + dataFromFile ?: return null + @Suppress("UNCHECKED_CAST") - return roomSyncEphemeralTemporaryStore.read(roomId) - ?.also { roomSyncEphemeralTemporaryStore.delete(roomId) } - ?.events - ?.firstOrNull { it.type == EventType.RECEIPT } + val content = dataFromFile + .events + .firstOrNull { it.type == EventType.RECEIPT } ?.content as? ReadReceiptContent + + if (content == null) { + // We can delete the file now + roomSyncEphemeralTemporaryStore.delete(roomId) + } + + return content + } + + fun onContentFromInitSyncHandled(roomId: String) { + roomSyncEphemeralTemporaryStore.delete(roomId) } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/RoomSyncHandler.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/RoomSyncHandler.kt index 99ffa80760..6c07d4d241 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/RoomSyncHandler.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/RoomSyncHandler.kt @@ -88,16 +88,21 @@ internal class RoomSyncHandler @Inject constructor(private val readReceiptHandle fun handle(realm: Realm, roomsSyncResponse: RoomsSyncResponse, isInitialSync: Boolean, + aggregator: SyncResponsePostTreatmentAggregator, reporter: ProgressReporter? = null) { Timber.v("Execute transaction from $this") - handleRoomSync(realm, HandlingStrategy.JOINED(roomsSyncResponse.join), isInitialSync, reporter) - handleRoomSync(realm, HandlingStrategy.INVITED(roomsSyncResponse.invite), isInitialSync, reporter) - handleRoomSync(realm, HandlingStrategy.LEFT(roomsSyncResponse.leave), isInitialSync, reporter) + handleRoomSync(realm, HandlingStrategy.JOINED(roomsSyncResponse.join), isInitialSync, aggregator, reporter) + handleRoomSync(realm, HandlingStrategy.INVITED(roomsSyncResponse.invite), isInitialSync, aggregator, reporter) + handleRoomSync(realm, HandlingStrategy.LEFT(roomsSyncResponse.leave), isInitialSync, aggregator,reporter) } // PRIVATE METHODS ***************************************************************************** - private fun handleRoomSync(realm: Realm, handlingStrategy: HandlingStrategy, isInitialSync: Boolean, reporter: ProgressReporter?) { + private fun handleRoomSync(realm: Realm, + handlingStrategy: HandlingStrategy, + isInitialSync: Boolean, + aggregator: SyncResponsePostTreatmentAggregator, + reporter: ProgressReporter?) { val insertType = if (isInitialSync) { EventInsertType.INITIAL_SYNC } else { @@ -107,12 +112,12 @@ internal class RoomSyncHandler @Inject constructor(private val readReceiptHandle val rooms = when (handlingStrategy) { is HandlingStrategy.JOINED -> { if (isInitialSync && initialSyncStrategy is InitialSyncStrategy.Optimized) { - insertJoinRoomsFromInitSync(realm, handlingStrategy, syncLocalTimeStampMillis, reporter) + insertJoinRoomsFromInitSync(realm, handlingStrategy, syncLocalTimeStampMillis, aggregator, reporter) // Rooms are already inserted, return an empty list emptyList() } else { handlingStrategy.data.mapWithProgress(reporter, InitSyncStep.ImportingAccountJoinedRooms, 0.6f) { - handleJoinedRoom(realm, it.key, it.value, insertType, syncLocalTimeStampMillis) + handleJoinedRoom(realm, it.key, it.value, insertType, syncLocalTimeStampMillis, aggregator) } } } @@ -133,6 +138,7 @@ internal class RoomSyncHandler @Inject constructor(private val readReceiptHandle private fun insertJoinRoomsFromInitSync(realm: Realm, handlingStrategy: HandlingStrategy.JOINED, syncLocalTimeStampMillis: Long, + aggregator: SyncResponsePostTreatmentAggregator, reporter: ProgressReporter?) { val maxSize = (initialSyncStrategy as? InitialSyncStrategy.Optimized)?.maxRoomsToInsert ?: Int.MAX_VALUE val listSize = handlingStrategy.data.keys.size @@ -154,7 +160,8 @@ internal class RoomSyncHandler @Inject constructor(private val readReceiptHandle roomId = it, roomSync = handlingStrategy.data[it] ?: error("Should not happen"), insertType = EventInsertType.INITIAL_SYNC, - syncLocalTimestampMillis = syncLocalTimeStampMillis + syncLocalTimestampMillis = syncLocalTimeStampMillis, + aggregator ) } realm.insertOrUpdate(roomEntities) @@ -164,7 +171,7 @@ internal class RoomSyncHandler @Inject constructor(private val readReceiptHandle } else { // No need to split val rooms = handlingStrategy.data.mapWithProgress(reporter, InitSyncStep.ImportingAccountJoinedRooms, 0.6f) { - handleJoinedRoom(realm, it.key, it.value, EventInsertType.INITIAL_SYNC, syncLocalTimeStampMillis) + handleJoinedRoom(realm, it.key, it.value, EventInsertType.INITIAL_SYNC, syncLocalTimeStampMillis, aggregator) } realm.insertOrUpdate(rooms) } @@ -174,14 +181,15 @@ internal class RoomSyncHandler @Inject constructor(private val readReceiptHandle roomId: String, roomSync: RoomSync, insertType: EventInsertType, - syncLocalTimestampMillis: Long): RoomEntity { + syncLocalTimestampMillis: Long, + aggregator: SyncResponsePostTreatmentAggregator): RoomEntity { Timber.v("Handle join sync for room $roomId") val ephemeralResult = (roomSync.ephemeral as? LazyRoomSyncEphemeral.Parsed) ?._roomSyncEphemeral ?.events ?.takeIf { it.isNotEmpty() } - ?.let { handleEphemeral(realm, roomId, it, insertType == EventInsertType.INITIAL_SYNC) } + ?.let { handleEphemeral(realm, roomId, it, insertType == EventInsertType.INITIAL_SYNC, aggregator) } if (roomSync.accountData?.events?.isNotEmpty() == true) { handleRoomAccountDataEvents(realm, roomId, roomSync.accountData) @@ -421,14 +429,15 @@ internal class RoomSyncHandler @Inject constructor(private val readReceiptHandle private fun handleEphemeral(realm: Realm, roomId: String, ephemeralEvents: List, - isInitialSync: Boolean): EphemeralResult { + isInitialSync: Boolean, + aggregator: SyncResponsePostTreatmentAggregator): EphemeralResult { var result = EphemeralResult() for (event in ephemeralEvents) { when (event.type) { EventType.RECEIPT -> { @Suppress("UNCHECKED_CAST") (event.content as? ReadReceiptContent)?.let { readReceiptContent -> - readReceiptHandler.handle(realm, roomId, readReceiptContent, isInitialSync) + readReceiptHandler.handle(realm, roomId, readReceiptContent, isInitialSync, aggregator) } } EventType.TYPING -> { diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/RoomTypingUsersHandler.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/RoomTypingUsersHandler.kt index f4f3e6ce43..b7851031ad 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/RoomTypingUsersHandler.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/RoomTypingUsersHandler.kt @@ -26,6 +26,7 @@ import javax.inject.Inject internal class RoomTypingUsersHandler @Inject constructor(@UserId private val userId: String, private val typingUsersTracker: DefaultTypingUsersTracker) { + // TODO This could be handled outside of the Realm transaction. Use the new aggregator? fun handle(realm: Realm, roomId: String, ephemeralResult: RoomSyncHandler.EphemeralResult?) { val roomMemberHelper = RoomMemberHelper(realm, roomId) val typingIds = ephemeralResult?.typingUserIds?.filter { it != userId }.orEmpty() diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/SyncResponseHandler.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/SyncResponseHandler.kt index fab1369aff..98ef5290a6 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/SyncResponseHandler.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/SyncResponseHandler.kt @@ -41,17 +41,19 @@ import kotlin.system.measureTimeMillis private const val GET_GROUP_DATA_WORKER = "GET_GROUP_DATA_WORKER" -internal class SyncResponseHandler @Inject constructor(@SessionDatabase private val monarchy: Monarchy, - @SessionId private val sessionId: String, - private val workManagerProvider: WorkManagerProvider, - private val roomSyncHandler: RoomSyncHandler, - private val userAccountDataSyncHandler: UserAccountDataSyncHandler, - private val groupSyncHandler: GroupSyncHandler, - private val cryptoSyncHandler: CryptoSyncHandler, - private val cryptoService: DefaultCryptoService, - private val tokenStore: SyncTokenStore, - private val processEventForPushTask: ProcessEventForPushTask, - private val pushRuleService: PushRuleService) { +internal class SyncResponseHandler @Inject constructor( + @SessionDatabase private val monarchy: Monarchy, + @SessionId private val sessionId: String, + private val workManagerProvider: WorkManagerProvider, + private val roomSyncHandler: RoomSyncHandler, + private val userAccountDataSyncHandler: UserAccountDataSyncHandler, + private val groupSyncHandler: GroupSyncHandler, + private val cryptoSyncHandler: CryptoSyncHandler, + private val aggregatorHandler: SyncResponsePostTreatmentAggregatorHandler, + private val cryptoService: DefaultCryptoService, + private val tokenStore: SyncTokenStore, + private val processEventForPushTask: ProcessEventForPushTask, + private val pushRuleService: PushRuleService) { suspend fun handleResponse(syncResponse: SyncResponse, fromToken: String?, @@ -81,13 +83,14 @@ internal class SyncResponseHandler @Inject constructor(@SessionDatabase private }.also { Timber.v("Finish handling toDevice in $it ms") } + val aggregator = SyncResponsePostTreatmentAggregator() // Start one big transaction monarchy.awaitTransaction { realm -> measureTimeMillis { Timber.v("Handle rooms") reportSubtask(reporter, InitSyncStep.ImportingAccountRoom, 1, 0.7f) { if (syncResponse.rooms != null) { - roomSyncHandler.handle(realm, syncResponse.rooms, isInitialSync, reporter) + roomSyncHandler.handle(realm, syncResponse.rooms, isInitialSync, aggregator,reporter) } } }.also { @@ -115,7 +118,10 @@ internal class SyncResponseHandler @Inject constructor(@SessionDatabase private } tokenStore.saveToken(realm, syncResponse.nextBatch) } + // Everything else we need to do outside the transaction + aggregatorHandler.handle(aggregator) + syncResponse.rooms?.let { checkPushRules(it, isInitialSync) userAccountDataSyncHandler.synchronizeWithServerIfNeeded(it.invite) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/SyncResponsePostTreatmentAggregator.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/SyncResponsePostTreatmentAggregator.kt new file mode 100644 index 0000000000..ea10a32f3e --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/SyncResponsePostTreatmentAggregator.kt @@ -0,0 +1,22 @@ +/* + * 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.session.sync + +internal class SyncResponsePostTreatmentAggregator { + // List of RoomId + val ephemeralFilesToDelete = mutableListOf() +} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/SyncResponsePostTreatmentAggregatorHandler.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/SyncResponsePostTreatmentAggregatorHandler.kt new file mode 100644 index 0000000000..8a3f115af5 --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/SyncResponsePostTreatmentAggregatorHandler.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 org.matrix.android.sdk.internal.session.sync + +import javax.inject.Inject + +internal class SyncResponsePostTreatmentAggregatorHandler @Inject constructor( + private val ephemeralTemporaryStore: RoomSyncEphemeralTemporaryStore +) { + fun handle(synResHaResponsePostTreatmentAggregator: SyncResponsePostTreatmentAggregator) { + cleanupEphemeralFiles(synResHaResponsePostTreatmentAggregator.ephemeralFilesToDelete) + } + + private fun cleanupEphemeralFiles(ephemeralFilesToDelete: List) { + ephemeralFilesToDelete.forEach { + ephemeralTemporaryStore.delete(it) + } + } +} From b93200f371990ee5f89b74a13643e11b26d60a70 Mon Sep 17 00:00:00 2001 From: Valere Date: Tue, 16 Mar 2021 14:31:37 +0100 Subject: [PATCH 29/34] unused params --- .../action/MessageActionsViewModel.kt | 25 ++++++++----------- 1 file changed, 10 insertions(+), 15 deletions(-) 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 f748369061..ac1c2258aa 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 @@ -251,7 +251,7 @@ class MessageActionsViewModel @AssistedInject constructor(@Assisted addActionsForSyncedState(timelineEvent, actionPermissions, messageContent, msgType) } timelineEvent.root.sendState == SendState.SENT -> { - addActionsForSentNotSyncedState(timelineEvent, actionPermissions, messageContent, msgType) + addActionsForSentNotSyncedState(timelineEvent) } } } @@ -294,21 +294,16 @@ class MessageActionsViewModel @AssistedInject constructor(@Assisted } } - private fun ArrayList.addActionsForSentNotSyncedState(timelineEvent: TimelineEvent, - actionPermissions: ActionPermissions, - messageContent: MessageContent?, - msgType: String?) { - if (timelineEvent.root.sendState == SendState.SENT) { - // If sent but not synced (synapse stuck at bottom bug) - // Still offer action to cancel (will only remove local echo) - timelineEvent.root.eventId?.let { - add(EventSharedAction.Cancel(it, true)) - } - - // TODO Can be redacted - - // TODO sent by me or sufficient power level + private fun ArrayList.addActionsForSentNotSyncedState(timelineEvent: TimelineEvent) { + // If sent but not synced (synapse stuck at bottom bug) + // Still offer action to cancel (will only remove local echo) + timelineEvent.root.eventId?.let { + add(EventSharedAction.Cancel(it, true)) } + + // TODO Can be redacted + + // TODO sent by me or sufficient power level } private fun ArrayList.addActionsForSyncedState(timelineEvent: TimelineEvent, From a30660ed4389623bd2eb72f2342da21d2afad7ea Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Tue, 16 Mar 2021 14:44:40 +0100 Subject: [PATCH 30/34] Cleanup --- .../matrix/android/sdk/internal/session/sync/RoomSyncHandler.kt | 2 +- .../android/sdk/internal/session/sync/SyncResponseHandler.kt | 2 +- .../session/sync/SyncResponsePostTreatmentAggregatorHandler.kt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/RoomSyncHandler.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/RoomSyncHandler.kt index 6c07d4d241..a96d55d028 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/RoomSyncHandler.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/RoomSyncHandler.kt @@ -93,7 +93,7 @@ internal class RoomSyncHandler @Inject constructor(private val readReceiptHandle Timber.v("Execute transaction from $this") handleRoomSync(realm, HandlingStrategy.JOINED(roomsSyncResponse.join), isInitialSync, aggregator, reporter) handleRoomSync(realm, HandlingStrategy.INVITED(roomsSyncResponse.invite), isInitialSync, aggregator, reporter) - handleRoomSync(realm, HandlingStrategy.LEFT(roomsSyncResponse.leave), isInitialSync, aggregator,reporter) + handleRoomSync(realm, HandlingStrategy.LEFT(roomsSyncResponse.leave), isInitialSync, aggregator, reporter) } // PRIVATE METHODS ***************************************************************************** diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/SyncResponseHandler.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/SyncResponseHandler.kt index 98ef5290a6..8e243c3443 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/SyncResponseHandler.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/SyncResponseHandler.kt @@ -90,7 +90,7 @@ internal class SyncResponseHandler @Inject constructor( Timber.v("Handle rooms") reportSubtask(reporter, InitSyncStep.ImportingAccountRoom, 1, 0.7f) { if (syncResponse.rooms != null) { - roomSyncHandler.handle(realm, syncResponse.rooms, isInitialSync, aggregator,reporter) + roomSyncHandler.handle(realm, syncResponse.rooms, isInitialSync, aggregator, reporter) } } }.also { diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/SyncResponsePostTreatmentAggregatorHandler.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/SyncResponsePostTreatmentAggregatorHandler.kt index 8a3f115af5..12b77c706b 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/SyncResponsePostTreatmentAggregatorHandler.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/SyncResponsePostTreatmentAggregatorHandler.kt @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021 New Vector Ltd + * 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. From 641ce206c2b9e7cc324a1af37b300fbf3ac62a64 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Tue, 16 Mar 2021 14:48:27 +0100 Subject: [PATCH 31/34] Move change to correct release version (#3001) --- CHANGES.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGES.md b/CHANGES.md index 0dc1a5d3c2..6799af2383 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -10,6 +10,7 @@ Improvements 🙌: Bugfix 🐛: - Ensure message are decrypted in the room list after a clear cache + - Cross signing now works with servers with an explicit port in the servername Translations 🗣: - @@ -48,7 +49,6 @@ Bugfix 🐛: - Be robust if Event.type is missing (#2946) - Snappier message send status - Fix MainActivity display (#2927) - - Cross signing now works with servers with an explicit port in the servername Translations 🗣: - All string resources and translations have been moved to the application module. Weblate project for the SDK will be removed. From 9492887345ec1462cd8bcbdb09f655b532266e47 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Tue, 16 Mar 2021 14:53:15 +0100 Subject: [PATCH 32/34] Restore call to clearUnavailableServersList() removed by mistake See https://github.com/vector-im/element-android/pull/3001#pullrequestreview-613195611 --- .../android/sdk/internal/crypto/DeviceListManager.kt | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/DeviceListManager.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/DeviceListManager.kt index b6e48322b7..e5f1c011f8 100755 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/DeviceListManager.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/DeviceListManager.kt @@ -168,13 +168,17 @@ internal class DeviceListManager @Inject constructor(private val cryptoStore: IM * @param left the user ids list which left a room */ fun handleDeviceListsChanges(changed: Collection, left: Collection) { - Timber.v("## CRYPTO: handleDeviceListsChanges changed:$changed / left:$left") + Timber.v("## CRYPTO: handleDeviceListsChanges changed: ${changed.logLimit()} / left: ${left.logLimit()}") var isUpdated = false val deviceTrackingStatuses = cryptoStore.getDeviceTrackingStatuses().toMutableMap() + if (changed.isNotEmpty() || left.isNotEmpty()) { + clearUnavailableServersList() + } + for (userId in changed) { if (deviceTrackingStatuses.containsKey(userId)) { - Timber.v("## CRYPTO | invalidateUserDeviceList() : Marking device list outdated for $userId") + Timber.v("## CRYPTO | handleDeviceListsChanges() : Marking device list outdated for $userId") deviceTrackingStatuses[userId] = TRACKING_STATUS_PENDING_DOWNLOAD isUpdated = true } @@ -182,7 +186,7 @@ internal class DeviceListManager @Inject constructor(private val cryptoStore: IM for (userId in left) { if (deviceTrackingStatuses.containsKey(userId)) { - Timber.v("## CRYPTO | invalidateUserDeviceList() : No longer tracking device list for $userId") + Timber.v("## CRYPTO | handleDeviceListsChanges() : No longer tracking device list for $userId") deviceTrackingStatuses[userId] = TRACKING_STATUS_NOT_TRACKED isUpdated = true } From a52d20eeff831170514e2311d3c00d9bfb0c4be8 Mon Sep 17 00:00:00 2001 From: Valere Date: Tue, 16 Mar 2021 15:26:06 +0100 Subject: [PATCH 33/34] Regression: Video will not play upon tap --- CHANGES.md | 1 + .../app/features/media/VectorAttachmentViewerActivity.kt | 3 +-- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index aad7773511..8e70e8f78f 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -11,6 +11,7 @@ Improvements 🙌: Bugfix 🐛: - Add option to cancel stuck messages at bottom of timeline see #516 - Ensure message are decrypted in the room list after a clear cache + - Regression: Video will not play upon tap, but only after swipe #2928 Translations 🗣: - diff --git a/vector/src/main/java/im/vector/app/features/media/VectorAttachmentViewerActivity.kt b/vector/src/main/java/im/vector/app/features/media/VectorAttachmentViewerActivity.kt index 158dbfdaae..c632a008ce 100644 --- a/vector/src/main/java/im/vector/app/features/media/VectorAttachmentViewerActivity.kt +++ b/vector/src/main/java/im/vector/app/features/media/VectorAttachmentViewerActivity.kt @@ -29,7 +29,6 @@ import androidx.core.view.ViewCompat import androidx.core.view.isInvisible import androidx.core.view.isVisible import androidx.lifecycle.Lifecycle -import androidx.lifecycle.lifecycleScope import androidx.transition.Transition import im.vector.app.R import im.vector.app.core.di.ActiveSessionHolder @@ -132,7 +131,7 @@ class VectorAttachmentViewerActivity : AttachmentViewerActivity(), BaseAttachmen if (savedInstanceState == null) { pager2.setCurrentItem(initialIndex, false) // The page change listener is not notified of the change... - lifecycleScope.launchWhenResumed { + pager2.post { onSelectedPositionChanged(initialIndex) } } From 2d64fe96c198cbab56df84c75387679336f31045 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Tue, 16 Mar 2021 22:08:29 +0100 Subject: [PATCH 34/34] Prepare release 1.1.2 --- CHANGES.md | 28 ++----------------- .../android/en-US/changelogs/40101020.txt | 2 ++ 2 files changed, 4 insertions(+), 26 deletions(-) create mode 100644 fastlane/metadata/android/en-US/changelogs/40101020.txt diff --git a/CHANGES.md b/CHANGES.md index 98c7a0351a..491b779582 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,9 +1,6 @@ -Changes in Element 1.1.2 (2021-XX-XX) +Changes in Element 1.1.2 (2021-03-16) =================================================== -Features ✨: - - - Improvements 🙌: - Lazy storage of ReadReceipts - Do not load room members in e2e after init sync @@ -14,27 +11,12 @@ Bugfix 🐛: - Regression: Video will not play upon tap, but only after swipe #2928 - Cross signing now works with servers with an explicit port in the servername -Translations 🗣: - - - -SDK API changes ⚠️: - - - -Build 🧱: - - - -Test: - - - Other changes: - Change formatting on issue templates to proper headings. -Changes in Element 1.1.1 (2021-XX-XX) +Changes in Element 1.1.1 (2021-03-10) =================================================== -Features ✨: - - - Improvements 🙌: - Allow non-HTTPS connections to homeservers on Tor (#2941) - Fetch homeserver type and version and display in a new setting screen and add info in rageshakes (#2831) @@ -55,16 +37,10 @@ Bugfix 🐛: Translations 🗣: - All string resources and translations have been moved to the application module. Weblate project for the SDK will be removed. -SDK API changes ⚠️: - - - Build 🧱: - Update a lot of dependencies, with the help of dependabot. - Add a script to download and install APK from the CI -Test: - - - Other changes: - Rework edition of event management diff --git a/fastlane/metadata/android/en-US/changelogs/40101020.txt b/fastlane/metadata/android/en-US/changelogs/40101020.txt new file mode 100644 index 0000000000..8146712593 --- /dev/null +++ b/fastlane/metadata/android/en-US/changelogs/40101020.txt @@ -0,0 +1,2 @@ +Main changes in this version: performance improvement and bug fixes! +Full changelog: https://github.com/vector-im/element-android/releases/tag/v1.1.2 \ No newline at end of file