From 12ad6496c200aec33e3e8cc55b4aafefaf7d35f7 Mon Sep 17 00:00:00 2001 From: Maxime NATUREL Date: Tue, 20 Dec 2022 09:38:32 +0100 Subject: [PATCH 01/40] Adding changelog entry --- changelog.d/7824.feature | 1 + 1 file changed, 1 insertion(+) create mode 100644 changelog.d/7824.feature diff --git a/changelog.d/7824.feature b/changelog.d/7824.feature new file mode 100644 index 0000000000..3c8b416571 --- /dev/null +++ b/changelog.d/7824.feature @@ -0,0 +1 @@ +[Poll] Warning message on decryption failure of some events From bd7b1f94960336f1fd176b8bb3481bd80d99f3d4 Mon Sep 17 00:00:00 2001 From: Maxime NATUREL Date: Tue, 20 Dec 2022 17:17:42 +0100 Subject: [PATCH 02/40] (WIP) Introducing new UnableToDecryptEventEntity --- .../database/EventInsertLiveObserver.kt | 3 + .../UnableToDecryptEventLiveObserver.kt | 83 ++++++++++++++++++ .../PollResponseAggregatedSummaryEntity.kt | 1 + .../model/UnableToDecryptEventEntity.kt | 27 ++++++ .../database/query/EventEntityQueries.kt | 9 +- .../sdk/internal/session/SessionModule.kt | 10 +++ .../UnableToDecryptEventLiveProcessor.kt | 33 +++++++ ...yptedEventRelationsAggregationProcessor.kt | 87 +++++++++++++++++++ .../EventRelationsAggregationProcessor.kt | 6 +- .../poll/DefaultPollAggregationProcessor.kt | 4 + .../EncryptedReferenceAggregationProcessor.kt | 48 ++++++++++ .../factory/PollItemViewStateFactory.kt | 1 + 12 files changed, 310 insertions(+), 2 deletions(-) create mode 100644 matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/UnableToDecryptEventLiveObserver.kt create mode 100644 matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/UnableToDecryptEventEntity.kt create mode 100644 matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/UnableToDecryptEventLiveProcessor.kt create mode 100644 matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/EncryptedEventRelationsAggregationProcessor.kt create mode 100644 matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/aggregation/utd/EncryptedReferenceAggregationProcessor.kt diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/EventInsertLiveObserver.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/EventInsertLiveObserver.kt index d1ca4f48a6..c276e571fe 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/EventInsertLiveObserver.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/EventInsertLiveObserver.kt @@ -40,6 +40,9 @@ internal class EventInsertLiveObserver @Inject constructor( private val lock = Mutex() + // TODO should we create a dedicated UnableToDecryptEntity or EncryptedEventEntity? + // and process them into a dedicated observer? + // Create also a new LiveProcessor interface for the new entity? override val query = Monarchy.Query { it.where(EventInsertEntity::class.java).equalTo(EventInsertEntityFields.CAN_BE_PROCESSED, true) } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/UnableToDecryptEventLiveObserver.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/UnableToDecryptEventLiveObserver.kt new file mode 100644 index 0000000000..7835a16ad1 --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/UnableToDecryptEventLiveObserver.kt @@ -0,0 +1,83 @@ +/* + * Copyright 2022 The Matrix.org Foundation C.I.C. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.matrix.android.sdk.internal.database + +import com.zhuinden.monarchy.Monarchy +import io.realm.RealmConfiguration +import io.realm.RealmResults +import kotlinx.coroutines.launch +import kotlinx.coroutines.sync.Mutex +import kotlinx.coroutines.sync.withLock +import org.matrix.android.sdk.internal.database.mapper.asDomain +import org.matrix.android.sdk.internal.database.model.EventEntity +import org.matrix.android.sdk.internal.database.model.UnableToDecryptEventEntity +import org.matrix.android.sdk.internal.database.model.UnableToDecryptEventEntityFields +import org.matrix.android.sdk.internal.database.query.where +import org.matrix.android.sdk.internal.di.SessionDatabase +import org.matrix.android.sdk.internal.session.UnableToDecryptEventLiveProcessor +import timber.log.Timber +import javax.inject.Inject + +internal class UnableToDecryptEventLiveObserver @Inject constructor( + @SessionDatabase realmConfiguration: RealmConfiguration, + private val processors: Set<@JvmSuppressWildcards UnableToDecryptEventLiveProcessor> +) : + RealmLiveEntityObserver(realmConfiguration) { + + private val lock = Mutex() + + override val query = Monarchy.Query { + it.where(UnableToDecryptEventEntity::class.java) + } + + override fun onChange(results: RealmResults) { + observerScope.launch { + lock.withLock { + if (!results.isLoaded || results.isEmpty()) { + return@withLock + } + val copiedEvents = ArrayList(results.size) + Timber.v("UnableToDecryptEventEntity updated with ${results.size} results in db") + results.forEach { + // don't use copy from realm over there + val copiedEvent = UnableToDecryptEventEntity(eventId = it.eventId) + copiedEvents.add(copiedEvent) + } + awaitTransaction(realmConfiguration) { realm -> + Timber.v("##Transaction: There are ${copiedEvents.size} events to process ") + copiedEvents.forEach { utdEvent -> + val eventId = utdEvent.eventId + val event = EventEntity.where(realm, eventId).findFirst() + if (event == null) { + Timber.v("Event $eventId not found") + return@forEach + } + val domainEvent = event.asDomain() + processors.forEach { + it.process(realm, domainEvent) + } + } + realm.where(UnableToDecryptEventEntity::class.java) + .`in`(UnableToDecryptEventEntityFields.EVENT_ID, copiedEvents.map { it.eventId }.toTypedArray()) + .findAll() + .deleteAllFromRealm() + } + processors.forEach { it.onPostProcess() } + } + } + } +} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/PollResponseAggregatedSummaryEntity.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/PollResponseAggregatedSummaryEntity.kt index d759bd3cd9..11d2ed64ba 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/PollResponseAggregatedSummaryEntity.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/PollResponseAggregatedSummaryEntity.kt @@ -36,5 +36,6 @@ internal open class PollResponseAggregatedSummaryEntity( var sourceLocalEchoEvents: RealmList = RealmList() ) : RealmObject() { + // TODO add a list of related eventIds which could not be decrypted companion object } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/UnableToDecryptEventEntity.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/UnableToDecryptEventEntity.kt new file mode 100644 index 0000000000..6393877d38 --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/UnableToDecryptEventEntity.kt @@ -0,0 +1,27 @@ +/* + * Copyright (c) 2022 The Matrix.org Foundation C.I.C. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.matrix.android.sdk.internal.database.model + +import io.realm.RealmObject + +/** + * This class is used to get notification on new UTD events. Since these events cannot be processed + * in EventInsertEntity, we should introduce a dedicated entity for that. + */ +internal open class UnableToDecryptEventEntity( + var eventId: String = "", +) : RealmObject() diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/query/EventEntityQueries.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/query/EventEntityQueries.kt index 0f1c226044..e18495c924 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/query/EventEntityQueries.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/query/EventEntityQueries.kt @@ -25,6 +25,7 @@ import org.matrix.android.sdk.internal.database.model.EventEntity import org.matrix.android.sdk.internal.database.model.EventEntityFields import org.matrix.android.sdk.internal.database.model.EventInsertEntity import org.matrix.android.sdk.internal.database.model.EventInsertType +import org.matrix.android.sdk.internal.database.model.UnableToDecryptEventEntity internal fun EventEntity.copyToRealmOrIgnore(realm: Realm, insertType: EventInsertType): EventEntity { val eventEntity = realm.where() @@ -32,11 +33,17 @@ internal fun EventEntity.copyToRealmOrIgnore(realm: Realm, insertType: EventInse .equalTo(EventEntityFields.ROOM_ID, roomId) .findFirst() return if (eventEntity == null) { - val canBeProcessed = type != EventType.ENCRYPTED || decryptionResultJson != null + val isEncrypted = type == EventType.ENCRYPTED && decryptionResultJson == null + val canBeProcessed = isEncrypted.not() val insertEntity = EventInsertEntity(eventId = eventId, eventType = type, canBeProcessed = canBeProcessed).apply { this.insertType = insertType } realm.insert(insertEntity) + // TODO check with others if it is the right spot to detect UTD events + if (isEncrypted) { + val utdEventEntity = UnableToDecryptEventEntity(eventId = eventId) + realm.insert(utdEventEntity) + } // copy this event entity and return it realm.copyToRealm(this) } else { diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/SessionModule.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/SessionModule.kt index b9f56cbc9f..d33112a922 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/SessionModule.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/SessionModule.kt @@ -50,6 +50,7 @@ import org.matrix.android.sdk.internal.crypto.tasks.RedactEventTask import org.matrix.android.sdk.internal.database.EventInsertLiveObserver import org.matrix.android.sdk.internal.database.RealmSessionProvider import org.matrix.android.sdk.internal.database.SessionRealmConfigurationFactory +import org.matrix.android.sdk.internal.database.UnableToDecryptEventLiveObserver import org.matrix.android.sdk.internal.di.Authenticated import org.matrix.android.sdk.internal.di.CacheDirectory import org.matrix.android.sdk.internal.di.DeviceId @@ -84,6 +85,7 @@ import org.matrix.android.sdk.internal.session.identity.DefaultIdentityService import org.matrix.android.sdk.internal.session.integrationmanager.IntegrationManager import org.matrix.android.sdk.internal.session.openid.DefaultOpenIdService import org.matrix.android.sdk.internal.session.permalinks.DefaultPermalinkService +import org.matrix.android.sdk.internal.session.room.EncryptedEventRelationsAggregationProcessor import org.matrix.android.sdk.internal.session.room.EventRelationsAggregationProcessor import org.matrix.android.sdk.internal.session.room.aggregation.poll.DefaultPollAggregationProcessor import org.matrix.android.sdk.internal.session.room.aggregation.poll.PollAggregationProcessor @@ -346,6 +348,10 @@ internal abstract class SessionModule { @IntoSet abstract fun bindEventInsertObserver(observer: EventInsertLiveObserver): SessionLifecycleObserver + @Binds + @IntoSet + abstract fun bindUnableToDecryptEventObserver(observer: UnableToDecryptEventLiveObserver): SessionLifecycleObserver + @Binds @IntoSet abstract fun bindIntegrationManager(manager: IntegrationManager): SessionLifecycleObserver @@ -405,4 +411,8 @@ internal abstract class SessionModule { @Binds abstract fun bindPollAggregationProcessor(processor: DefaultPollAggregationProcessor): PollAggregationProcessor + + @Binds + @IntoSet + abstract fun bindEncryptedEventRelationsAggregationProcessor(processor: EncryptedEventRelationsAggregationProcessor): UnableToDecryptEventLiveProcessor } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/UnableToDecryptEventLiveProcessor.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/UnableToDecryptEventLiveProcessor.kt new file mode 100644 index 0000000000..c5fa6dc88e --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/UnableToDecryptEventLiveProcessor.kt @@ -0,0 +1,33 @@ +/* + * Copyright 2022 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 + +import io.realm.Realm +import org.matrix.android.sdk.api.session.events.model.Event + +internal interface UnableToDecryptEventLiveProcessor { + + fun process(realm: Realm, event: Event) + + /** + * Called after transaction. + * Maybe you prefer to process the events outside of the realm transaction. + */ + suspend fun onPostProcess() { + // Noop by default + } +} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/EncryptedEventRelationsAggregationProcessor.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/EncryptedEventRelationsAggregationProcessor.kt new file mode 100644 index 0000000000..2cd05a8e6f --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/EncryptedEventRelationsAggregationProcessor.kt @@ -0,0 +1,87 @@ +/* + * Copyright (c) 2022 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.room + +import io.realm.Realm +import org.matrix.android.sdk.api.session.events.model.Event +import org.matrix.android.sdk.api.session.events.model.EventType +import org.matrix.android.sdk.api.session.events.model.LocalEcho +import org.matrix.android.sdk.api.session.events.model.RelationType +import org.matrix.android.sdk.api.session.events.model.content.EncryptedEventContent +import org.matrix.android.sdk.api.session.events.model.toModel +import org.matrix.android.sdk.internal.session.UnableToDecryptEventLiveProcessor +import org.matrix.android.sdk.internal.session.room.aggregation.utd.EncryptedReferenceAggregationProcessor +import timber.log.Timber +import javax.inject.Inject + +internal class EncryptedEventRelationsAggregationProcessor @Inject constructor( + private val encryptedReferenceAggregationProcessor: EncryptedReferenceAggregationProcessor, +) : UnableToDecryptEventLiveProcessor { + + // TODO add unit tests + override fun process(realm: Realm, event: Event) { + val roomId = event.roomId + if (roomId == null) { + Timber.w("Event has no room id ${event.eventId}") + return + } + + val isLocalEcho = LocalEcho.isLocalEchoId(event.eventId ?: "") + + when (event.getClearType()) { + EventType.ENCRYPTED -> { + val encryptedEventContent = event.content.toModel() + processEncryptedContent( + encryptedEventContent = encryptedEventContent, + realm = realm, + event = event, + roomId = roomId, + isLocalEcho = isLocalEcho, + ) + } + else -> Unit + } + } + + private fun processEncryptedContent( + encryptedEventContent: EncryptedEventContent?, + realm: Realm, + event: Event, + roomId: String, + isLocalEcho: Boolean, + ) { + when (encryptedEventContent?.relatesTo?.type) { + RelationType.REPLACE -> { + Timber.w("## UTD replace in room $roomId for event ${event.eventId}") + } + RelationType.RESPONSE -> { + // can we / should we do we something for UTD response?? + Timber.w("## UTD response in room $roomId related to ${encryptedEventContent.relatesTo.eventId}") + } + RelationType.REFERENCE -> { + // can we / should we do we something for UTD reference?? + Timber.w("## UTD reference in room $roomId related to ${encryptedEventContent.relatesTo.eventId}") + encryptedReferenceAggregationProcessor.handle(realm, event, roomId, isLocalEcho, encryptedEventContent.relatesTo.eventId) + } + RelationType.ANNOTATION -> { + // can we / should we do we something for UTD annotation?? + Timber.w("## UTD annotation in room $roomId related to ${encryptedEventContent.relatesTo.eventId}") + } + else -> Unit + } + } +} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/EventRelationsAggregationProcessor.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/EventRelationsAggregationProcessor.kt index be73309837..d03f98158b 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/EventRelationsAggregationProcessor.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/EventRelationsAggregationProcessor.kt @@ -61,6 +61,7 @@ import org.matrix.android.sdk.internal.di.UserId import org.matrix.android.sdk.internal.session.EventInsertLiveProcessor import org.matrix.android.sdk.internal.session.room.aggregation.livelocation.LiveLocationAggregationProcessor import org.matrix.android.sdk.internal.session.room.aggregation.poll.PollAggregationProcessor +import org.matrix.android.sdk.internal.session.room.aggregation.utd.EncryptedReferenceAggregationProcessor import org.matrix.android.sdk.internal.session.room.state.StateEventDataSource import org.matrix.android.sdk.internal.util.time.Clock import timber.log.Timber @@ -73,6 +74,7 @@ internal class EventRelationsAggregationProcessor @Inject constructor( private val sessionManager: SessionManager, private val liveLocationAggregationProcessor: LiveLocationAggregationProcessor, private val pollAggregationProcessor: PollAggregationProcessor, + private val encryptedReferenceAggregationProcessor: EncryptedReferenceAggregationProcessor, private val editValidator: EventEditValidator, private val clock: Clock, ) : EventInsertLiveProcessor { @@ -170,11 +172,12 @@ internal class EventRelationsAggregationProcessor @Inject constructor( } } } + // TODO remove this once other implementation is validated // As for now Live event processors are not receiving UTD events. // They will get an update if the event is decrypted later EventType.ENCRYPTED -> { // Relation type is in clear, it might be possible to do some things? - // Notice that if the event is decrypted later, process be called again + // Notice that if the event is decrypted later, process will be called again val encryptedEventContent = event.content.toModel() when (encryptedEventContent?.relatesTo?.type) { RelationType.REPLACE -> { @@ -189,6 +192,7 @@ internal class EventRelationsAggregationProcessor @Inject constructor( RelationType.REFERENCE -> { // can we / should we do we something for UTD reference?? Timber.w("## UTD reference in room $roomId related to ${encryptedEventContent.relatesTo.eventId}") + encryptedReferenceAggregationProcessor.handle(realm, event, roomId, isLocalEcho, encryptedEventContent.relatesTo.eventId) } RelationType.ANNOTATION -> { // can we / should we do we something for UTD annotation?? diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/aggregation/poll/DefaultPollAggregationProcessor.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/aggregation/poll/DefaultPollAggregationProcessor.kt index a424becbd6..301996ebab 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/aggregation/poll/DefaultPollAggregationProcessor.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/aggregation/poll/DefaultPollAggregationProcessor.kt @@ -155,6 +155,8 @@ internal class DefaultPollAggregationProcessor @Inject constructor( ) aggregatedPollSummaryEntity.aggregatedContent = ContentMapper.map(newSumModel.toContent()) + // TODO check also if eventId is part of UTD list of eventIds, if so remove it + return true } @@ -184,6 +186,8 @@ internal class DefaultPollAggregationProcessor @Inject constructor( ensurePollIsFullyAggregated(roomId, pollEventId) } + // TODO check also if eventId is part of UTD list of eventIds, if so remove it + return true } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/aggregation/utd/EncryptedReferenceAggregationProcessor.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/aggregation/utd/EncryptedReferenceAggregationProcessor.kt new file mode 100644 index 0000000000..442ce24dbb --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/aggregation/utd/EncryptedReferenceAggregationProcessor.kt @@ -0,0 +1,48 @@ +/* + * Copyright (c) 2022 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.matrix.android.sdk.internal.session.room.aggregation.utd + +import io.realm.Realm +import org.matrix.android.sdk.api.session.events.model.Event +import javax.inject.Inject + +class EncryptedReferenceAggregationProcessor @Inject constructor() { + + // TODO add unit tests + fun handle( + realm: Realm, + event: Event, + roomId: String, + isLocalEcho: Boolean, + relatedEventId: String? + ) { + if(isLocalEcho || relatedEventId.isNullOrEmpty()) return + + handlePollReference(realm = realm, event = event, roomId = roomId, relatedEventId = relatedEventId) + } + + // TODO how to check this is working? + private fun handlePollReference( + realm: Realm, + event: Event, + roomId: String, + relatedEventId: String + ) { + // TODO check if relatedEventId is referencing any existing poll event in DB + // TODO if related to a poll, then add the event id into the list of encryptedRelatedEvents in the summary + } +} diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/PollItemViewStateFactory.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/PollItemViewStateFactory.kt index 13f63e86c4..c885f6f3ff 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/PollItemViewStateFactory.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/PollItemViewStateFactory.kt @@ -35,6 +35,7 @@ class PollItemViewStateFactory @Inject constructor( pollContent: MessagePollContent, informationData: MessageInformationData, ): PollViewState { + // TODO check for decryption failure error in informationData val pollCreationInfo = pollContent.getBestPollCreationInfo() val question = pollCreationInfo?.question?.getBestQuestion().orEmpty() From ae2639aeb069d8e802c019b2d406f9178956c7c0 Mon Sep 17 00:00:00 2001 From: Maxime NATUREL Date: Wed, 21 Dec 2022 14:10:13 +0100 Subject: [PATCH 03/40] Keep track of related events to a poll which had failed to be decrypted --- .../model/PollResponseAggregatedSummary.kt | 4 +++- .../database/EventInsertLiveObserver.kt | 3 --- ...llResponseAggregatedSummaryEntityMapper.kt | 6 ++++-- .../PollResponseAggregatedSummaryEntity.kt | 5 +++-- .../database/model/SessionRealmModule.kt | 3 ++- ...yptedEventRelationsAggregationProcessor.kt | 2 +- .../EncryptedReferenceAggregationProcessor.kt | 21 ++++++++++++------- .../factory/PollItemViewStateFactory.kt | 2 +- 8 files changed, 28 insertions(+), 18 deletions(-) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/PollResponseAggregatedSummary.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/PollResponseAggregatedSummary.kt index b16852e47d..e8b4ef6ed6 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/PollResponseAggregatedSummary.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/PollResponseAggregatedSummary.kt @@ -23,5 +23,7 @@ data class PollResponseAggregatedSummary( val nbOptions: Int = 0, // The list of the eventIDs used to build the summary (might be out of sync if chunked received from message chunk) val sourceEvents: List, - val localEchos: List + val localEchos: List, + // list of related event ids which are encrypted due to decryption failure + val encryptedRelatedEventIds: List, ) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/EventInsertLiveObserver.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/EventInsertLiveObserver.kt index c276e571fe..d1ca4f48a6 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/EventInsertLiveObserver.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/EventInsertLiveObserver.kt @@ -40,9 +40,6 @@ internal class EventInsertLiveObserver @Inject constructor( private val lock = Mutex() - // TODO should we create a dedicated UnableToDecryptEntity or EncryptedEventEntity? - // and process them into a dedicated observer? - // Create also a new LiveProcessor interface for the new entity? override val query = Monarchy.Query { it.where(EventInsertEntity::class.java).equalTo(EventInsertEntityFields.CAN_BE_PROCESSED, true) } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/mapper/PollResponseAggregatedSummaryEntityMapper.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/mapper/PollResponseAggregatedSummaryEntityMapper.kt index 00998af9bb..808a49b958 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/mapper/PollResponseAggregatedSummaryEntityMapper.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/mapper/PollResponseAggregatedSummaryEntityMapper.kt @@ -30,7 +30,8 @@ internal object PollResponseAggregatedSummaryEntityMapper { closedTime = entity.closedTime, localEchos = entity.sourceLocalEchoEvents.toList(), sourceEvents = entity.sourceEvents.toList(), - nbOptions = entity.nbOptions + nbOptions = entity.nbOptions, + encryptedRelatedEventIds = entity.encryptedRelatedEventIds.toList(), ) } @@ -40,7 +41,8 @@ internal object PollResponseAggregatedSummaryEntityMapper { nbOptions = model.nbOptions, closedTime = model.closedTime, sourceEvents = RealmList().apply { addAll(model.sourceEvents) }, - sourceLocalEchoEvents = RealmList().apply { addAll(model.localEchos) } + sourceLocalEchoEvents = RealmList().apply { addAll(model.localEchos) }, + encryptedRelatedEventIds = RealmList().apply { addAll(model.encryptedRelatedEventIds) }, ) } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/PollResponseAggregatedSummaryEntity.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/PollResponseAggregatedSummaryEntity.kt index 11d2ed64ba..906e329f6f 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/PollResponseAggregatedSummaryEntity.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/PollResponseAggregatedSummaryEntity.kt @@ -33,9 +33,10 @@ internal open class PollResponseAggregatedSummaryEntity( // The list of the eventIDs used to build the summary (might be out of sync if chunked received from message chunk) var sourceEvents: RealmList = RealmList(), - var sourceLocalEchoEvents: RealmList = RealmList() + var sourceLocalEchoEvents: RealmList = RealmList(), + // list of related event ids which are encrypted due to decryption failure + var encryptedRelatedEventIds: RealmList = RealmList(), ) : RealmObject() { - // TODO add a list of related eventIds which could not be decrypted companion object } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/SessionRealmModule.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/SessionRealmModule.kt index 0d998e8fe1..79b0dd699c 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/SessionRealmModule.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/SessionRealmModule.kt @@ -72,7 +72,8 @@ import org.matrix.android.sdk.internal.database.model.threads.ThreadSummaryEntit SpaceParentSummaryEntity::class, UserPresenceEntity::class, ThreadSummaryEntity::class, - ThreadListPageEntity::class + ThreadListPageEntity::class, + UnableToDecryptEventEntity::class, ] ) internal class SessionRealmModule diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/EncryptedEventRelationsAggregationProcessor.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/EncryptedEventRelationsAggregationProcessor.kt index 2cd05a8e6f..7e9a0a9499 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/EncryptedEventRelationsAggregationProcessor.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/EncryptedEventRelationsAggregationProcessor.kt @@ -75,7 +75,7 @@ internal class EncryptedEventRelationsAggregationProcessor @Inject constructor( RelationType.REFERENCE -> { // can we / should we do we something for UTD reference?? Timber.w("## UTD reference in room $roomId related to ${encryptedEventContent.relatesTo.eventId}") - encryptedReferenceAggregationProcessor.handle(realm, event, roomId, isLocalEcho, encryptedEventContent.relatesTo.eventId) + encryptedReferenceAggregationProcessor.handle(realm, event, isLocalEcho, encryptedEventContent.relatesTo.eventId) } RelationType.ANNOTATION -> { // can we / should we do we something for UTD annotation?? diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/aggregation/utd/EncryptedReferenceAggregationProcessor.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/aggregation/utd/EncryptedReferenceAggregationProcessor.kt index 442ce24dbb..fdd3bc80e3 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/aggregation/utd/EncryptedReferenceAggregationProcessor.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/aggregation/utd/EncryptedReferenceAggregationProcessor.kt @@ -18,6 +18,8 @@ package org.matrix.android.sdk.internal.session.room.aggregation.utd import io.realm.Realm import org.matrix.android.sdk.api.session.events.model.Event +import org.matrix.android.sdk.internal.database.model.PollResponseAggregatedSummaryEntity +import org.matrix.android.sdk.internal.database.model.PollResponseAggregatedSummaryEntityFields import javax.inject.Inject class EncryptedReferenceAggregationProcessor @Inject constructor() { @@ -26,23 +28,28 @@ class EncryptedReferenceAggregationProcessor @Inject constructor() { fun handle( realm: Realm, event: Event, - roomId: String, isLocalEcho: Boolean, relatedEventId: String? ) { - if(isLocalEcho || relatedEventId.isNullOrEmpty()) return + if (isLocalEcho || relatedEventId.isNullOrEmpty()) return - handlePollReference(realm = realm, event = event, roomId = roomId, relatedEventId = relatedEventId) + handlePollReference(realm = realm, event = event, relatedEventId = relatedEventId) } - // TODO how to check this is working? private fun handlePollReference( realm: Realm, event: Event, - roomId: String, relatedEventId: String ) { - // TODO check if relatedEventId is referencing any existing poll event in DB - // TODO if related to a poll, then add the event id into the list of encryptedRelatedEvents in the summary + event.eventId?.let { eventId -> + val existingRelatedPoll = getPollSummaryWithEventId(realm, relatedEventId) + existingRelatedPoll?.encryptedRelatedEventIds?.add(eventId) + } + } + + private fun getPollSummaryWithEventId(realm: Realm, eventId: String): PollResponseAggregatedSummaryEntity? { + return realm.where(PollResponseAggregatedSummaryEntity::class.java) + .containsValue(PollResponseAggregatedSummaryEntityFields.SOURCE_EVENTS.`$`, eventId) + .findFirst() } } diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/PollItemViewStateFactory.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/PollItemViewStateFactory.kt index c885f6f3ff..db11dff007 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/PollItemViewStateFactory.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/PollItemViewStateFactory.kt @@ -35,7 +35,7 @@ class PollItemViewStateFactory @Inject constructor( pollContent: MessagePollContent, informationData: MessageInformationData, ): PollViewState { - // TODO check for decryption failure error in informationData + // TODO add new field in ViewState to reflect decryption error of related events val pollCreationInfo = pollContent.getBestPollCreationInfo() val question = pollCreationInfo?.question?.getBestQuestion().orEmpty() From c0c5e208bde46610e615862ca23331d248b86096 Mon Sep 17 00:00:00 2001 From: Maxime NATUREL Date: Wed, 21 Dec 2022 15:16:47 +0100 Subject: [PATCH 04/40] Remove processing of encrypted events from EventRelationsAggregationProcessor --- .../EventRelationsAggregationProcessor.kt | 31 ------------------- 1 file changed, 31 deletions(-) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/EventRelationsAggregationProcessor.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/EventRelationsAggregationProcessor.kt index d03f98158b..0734b286b3 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/EventRelationsAggregationProcessor.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/EventRelationsAggregationProcessor.kt @@ -23,7 +23,6 @@ import org.matrix.android.sdk.api.session.events.model.Event import org.matrix.android.sdk.api.session.events.model.EventType import org.matrix.android.sdk.api.session.events.model.LocalEcho import org.matrix.android.sdk.api.session.events.model.RelationType -import org.matrix.android.sdk.api.session.events.model.content.EncryptedEventContent import org.matrix.android.sdk.api.session.events.model.getRelationContent import org.matrix.android.sdk.api.session.events.model.toContent import org.matrix.android.sdk.api.session.events.model.toModel @@ -61,7 +60,6 @@ import org.matrix.android.sdk.internal.di.UserId import org.matrix.android.sdk.internal.session.EventInsertLiveProcessor import org.matrix.android.sdk.internal.session.room.aggregation.livelocation.LiveLocationAggregationProcessor import org.matrix.android.sdk.internal.session.room.aggregation.poll.PollAggregationProcessor -import org.matrix.android.sdk.internal.session.room.aggregation.utd.EncryptedReferenceAggregationProcessor import org.matrix.android.sdk.internal.session.room.state.StateEventDataSource import org.matrix.android.sdk.internal.util.time.Clock import timber.log.Timber @@ -74,7 +72,6 @@ internal class EventRelationsAggregationProcessor @Inject constructor( private val sessionManager: SessionManager, private val liveLocationAggregationProcessor: LiveLocationAggregationProcessor, private val pollAggregationProcessor: PollAggregationProcessor, - private val encryptedReferenceAggregationProcessor: EncryptedReferenceAggregationProcessor, private val editValidator: EventEditValidator, private val clock: Clock, ) : EventInsertLiveProcessor { @@ -172,34 +169,6 @@ internal class EventRelationsAggregationProcessor @Inject constructor( } } } - // TODO remove this once other implementation is validated - // As for now Live event processors are not receiving UTD events. - // They will get an update if the event is decrypted later - EventType.ENCRYPTED -> { - // Relation type is in clear, it might be possible to do some things? - // Notice that if the event is decrypted later, process will be called again - val encryptedEventContent = event.content.toModel() - when (encryptedEventContent?.relatesTo?.type) { - RelationType.REPLACE -> { - Timber.v("###REPLACE in room $roomId for event ${event.eventId}") - // A replace! - handleReplace(realm, event, roomId, isLocalEcho, encryptedEventContent.relatesTo.eventId) - } - RelationType.RESPONSE -> { - // can we / should we do we something for UTD response?? - Timber.w("## UTD response in room $roomId related to ${encryptedEventContent.relatesTo.eventId}") - } - RelationType.REFERENCE -> { - // can we / should we do we something for UTD reference?? - Timber.w("## UTD reference in room $roomId related to ${encryptedEventContent.relatesTo.eventId}") - encryptedReferenceAggregationProcessor.handle(realm, event, roomId, isLocalEcho, encryptedEventContent.relatesTo.eventId) - } - RelationType.ANNOTATION -> { - // can we / should we do we something for UTD annotation?? - Timber.w("## UTD annotation in room $roomId related to ${encryptedEventContent.relatesTo.eventId}") - } - } - } EventType.REDACTION -> { val eventToPrune = event.redacts?.let { EventEntity.where(realm, eventId = it).findFirst() } ?: return From 3b9faa5f31f983a8204c4e0ba53e3f7aee63c22a Mon Sep 17 00:00:00 2001 From: Maxime NATUREL Date: Wed, 21 Dec 2022 15:17:35 +0100 Subject: [PATCH 05/40] Render specific message on decryption error --- .../src/main/res/values/strings.xml | 1 + ...yptedEventRelationsAggregationProcessor.kt | 7 +++++- .../factory/PollItemViewStateFactory.kt | 22 +++++++++++++++---- .../timeline/item/MessageInformationData.kt | 3 ++- 4 files changed, 27 insertions(+), 6 deletions(-) diff --git a/library/ui-strings/src/main/res/values/strings.xml b/library/ui-strings/src/main/res/values/strings.xml index f1c2eb2c6d..d884938170 100644 --- a/library/ui-strings/src/main/res/values/strings.xml +++ b/library/ui-strings/src/main/res/values/strings.xml @@ -3194,6 +3194,7 @@ Closed poll Results are only revealed when you end the poll Ended the poll. + Due to decryption errors, some votes may not be counted Active polls There are no active polls in this room Past polls diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/EncryptedEventRelationsAggregationProcessor.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/EncryptedEventRelationsAggregationProcessor.kt index 7e9a0a9499..bae95f1c15 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/EncryptedEventRelationsAggregationProcessor.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/EncryptedEventRelationsAggregationProcessor.kt @@ -75,7 +75,12 @@ internal class EncryptedEventRelationsAggregationProcessor @Inject constructor( RelationType.REFERENCE -> { // can we / should we do we something for UTD reference?? Timber.w("## UTD reference in room $roomId related to ${encryptedEventContent.relatesTo.eventId}") - encryptedReferenceAggregationProcessor.handle(realm, event, isLocalEcho, encryptedEventContent.relatesTo.eventId) + encryptedReferenceAggregationProcessor.handle( + realm = realm, + event = event, + isLocalEcho = isLocalEcho, + relatedEventId = encryptedEventContent.relatesTo.eventId + ) } RelationType.ANNOTATION -> { // can we / should we do we something for UTD annotation?? diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/PollItemViewStateFactory.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/PollItemViewStateFactory.kt index db11dff007..64f529fb59 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/PollItemViewStateFactory.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/PollItemViewStateFactory.kt @@ -31,11 +31,11 @@ class PollItemViewStateFactory @Inject constructor( private val stringProvider: StringProvider, ) { + // TODO update unit tests fun create( pollContent: MessagePollContent, informationData: MessageInformationData, ): PollViewState { - // TODO add new field in ViewState to reflect decryption error of related events val pollCreationInfo = pollContent.getBestPollCreationInfo() val question = pollCreationInfo?.question?.getBestQuestion().orEmpty() @@ -84,9 +84,14 @@ class PollItemViewStateFactory @Inject constructor( totalVotes: Int, winnerVoteCount: Int?, ): PollViewState { + val totalVotesText = if (pollResponseSummary?.hasDecryptionError.orFalse()) { + stringProvider.getString(R.string.unable_to_decrypt_some_events_in_poll) + } else { + stringProvider.getQuantityString(R.plurals.poll_total_vote_count_after_ended, totalVotes, totalVotes) + } return PollViewState( question = question, - votesStatus = stringProvider.getQuantityString(R.plurals.poll_total_vote_count_after_ended, totalVotes, totalVotes), + votesStatus = totalVotesText, canVote = false, optionViewStates = pollCreationInfo?.answers?.map { answer -> val voteSummary = pollResponseSummary?.getVoteSummaryOfAnOption(answer.id ?: "") @@ -127,9 +132,14 @@ class PollItemViewStateFactory @Inject constructor( pollResponseSummary: PollResponseData?, totalVotes: Int ): PollViewState { + val totalVotesText = if (pollResponseSummary?.hasDecryptionError.orFalse()) { + stringProvider.getString(R.string.unable_to_decrypt_some_events_in_poll) + } else { + stringProvider.getQuantityString(R.plurals.poll_total_vote_count_before_ended_and_voted, totalVotes, totalVotes) + } return PollViewState( question = question, - votesStatus = stringProvider.getQuantityString(R.plurals.poll_total_vote_count_before_ended_and_voted, totalVotes, totalVotes), + votesStatus = totalVotesText, canVote = true, optionViewStates = pollCreationInfo?.answers?.map { answer -> val isMyVote = pollResponseSummary?.myVote == answer.id @@ -145,7 +155,11 @@ class PollItemViewStateFactory @Inject constructor( ) } - private fun createReadyPollViewState(question: String, pollCreationInfo: PollCreationInfo?, totalVotes: Int): PollViewState { + private fun createReadyPollViewState( + question: String, + pollCreationInfo: PollCreationInfo?, + totalVotes: Int + ): PollViewState { val totalVotesText = if (totalVotes == 0) { stringProvider.getString(R.string.poll_no_votes_cast) } else { diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageInformationData.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageInformationData.kt index 757246d4e4..4bdfb948ce 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageInformationData.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageInformationData.kt @@ -90,7 +90,8 @@ data class PollResponseData( val votes: Map?, val totalVotes: Int = 0, val winnerVoteCount: Int = 0, - val isClosed: Boolean = false + val isClosed: Boolean = false, + val hasDecryptionError: Boolean = false, ) : Parcelable { fun getVoteSummaryOfAnOption(optionId: String) = votes?.get(optionId) From a29d4399a5b0945cff50c7a53b8ad1210b39a3bd Mon Sep 17 00:00:00 2001 From: Maxime NATUREL Date: Wed, 21 Dec 2022 16:34:54 +0100 Subject: [PATCH 06/40] Removing encrypted related id when receiving decrypted event --- .../poll/DefaultPollAggregationProcessor.kt | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/aggregation/poll/DefaultPollAggregationProcessor.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/aggregation/poll/DefaultPollAggregationProcessor.kt index 301996ebab..5b25238677 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/aggregation/poll/DefaultPollAggregationProcessor.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/aggregation/poll/DefaultPollAggregationProcessor.kt @@ -45,6 +45,7 @@ import org.matrix.android.sdk.internal.session.room.relation.poll.FetchPollRespo import org.matrix.android.sdk.internal.task.TaskExecutor import javax.inject.Inject +// TODO update unit tests internal class DefaultPollAggregationProcessor @Inject constructor( private val taskExecutor: TaskExecutor, private val fetchPollResponseEventsTask: FetchPollResponseEventsTask, @@ -155,7 +156,7 @@ internal class DefaultPollAggregationProcessor @Inject constructor( ) aggregatedPollSummaryEntity.aggregatedContent = ContentMapper.map(newSumModel.toContent()) - // TODO check also if eventId is part of UTD list of eventIds, if so remove it + event.eventId?.let { removeEncryptedRelatedEventIdIfNeeded(aggregatedPollSummaryEntity, it) } return true } @@ -182,12 +183,12 @@ internal class DefaultPollAggregationProcessor @Inject constructor( aggregatedPollSummaryEntity.sourceEvents.add(event.eventId) } + event.eventId?.let { removeEncryptedRelatedEventIdIfNeeded(aggregatedPollSummaryEntity, it) } + if (!isLocalEcho) { ensurePollIsFullyAggregated(roomId, pollEventId) } - // TODO check also if eventId is part of UTD list of eventIds, if so remove it - return true } @@ -230,4 +231,10 @@ internal class DefaultPollAggregationProcessor @Inject constructor( fetchPollResponseEventsTask.execute(params) } } + + private fun removeEncryptedRelatedEventIdIfNeeded(aggregatedPollSummaryEntity: PollResponseAggregatedSummaryEntity, eventId: String) { + if(aggregatedPollSummaryEntity.encryptedRelatedEventIds.contains(eventId)) { + aggregatedPollSummaryEntity.encryptedRelatedEventIds.remove(eventId) + } + } } From 7e1016da7e42f5dff6c4dca955ecc945a66d3d6d Mon Sep 17 00:00:00 2001 From: Maxime NATUREL Date: Wed, 21 Dec 2022 16:35:27 +0100 Subject: [PATCH 07/40] Changing where we insert UnableToDecryptEventEntity in DB --- .../sdk/internal/crypto/CryptoModule.kt | 5 ++ .../sdk/internal/crypto/EventDecryptor.kt | 14 +++++- .../CreateUnableToDecryptEventEntityTask.kt | 46 +++++++++++++++++++ .../database/query/EventEntityQueries.kt | 9 +--- 4 files changed, 65 insertions(+), 9 deletions(-) create mode 100644 matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/tasks/CreateUnableToDecryptEventEntityTask.kt diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/CryptoModule.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/CryptoModule.kt index c69a859016..d78f4a3107 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/CryptoModule.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/CryptoModule.kt @@ -62,7 +62,9 @@ import org.matrix.android.sdk.internal.crypto.store.db.RealmCryptoStore import org.matrix.android.sdk.internal.crypto.store.db.RealmCryptoStoreMigration import org.matrix.android.sdk.internal.crypto.store.db.RealmCryptoStoreModule import org.matrix.android.sdk.internal.crypto.tasks.ClaimOneTimeKeysForUsersDeviceTask +import org.matrix.android.sdk.internal.crypto.tasks.CreateUnableToDecryptEventEntityTask import org.matrix.android.sdk.internal.crypto.tasks.DefaultClaimOneTimeKeysForUsersDevice +import org.matrix.android.sdk.internal.crypto.tasks.DefaultCreateUnableToDecryptEventEntityTask import org.matrix.android.sdk.internal.crypto.tasks.DefaultDeleteDeviceTask import org.matrix.android.sdk.internal.crypto.tasks.DefaultDownloadKeysForUsers import org.matrix.android.sdk.internal.crypto.tasks.DefaultEncryptEventTask @@ -253,4 +255,7 @@ internal abstract class CryptoModule { @Binds abstract fun bindSendEventTask(task: DefaultSendEventTask): SendEventTask + + @Binds + abstract fun bindCreateUnableToDecryptEventEntityTask(task: DefaultCreateUnableToDecryptEventEntityTask): CreateUnableToDecryptEventEntityTask } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/EventDecryptor.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/EventDecryptor.kt index c9eabeab48..524db32670 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/EventDecryptor.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/EventDecryptor.kt @@ -38,6 +38,7 @@ import org.matrix.android.sdk.api.session.events.model.toModel import org.matrix.android.sdk.internal.crypto.actions.EnsureOlmSessionsForDevicesAction import org.matrix.android.sdk.internal.crypto.actions.MessageEncrypter import org.matrix.android.sdk.internal.crypto.store.IMXCryptoStore +import org.matrix.android.sdk.internal.crypto.tasks.CreateUnableToDecryptEventEntityTask import org.matrix.android.sdk.internal.crypto.tasks.SendToDeviceTask import org.matrix.android.sdk.internal.extensions.foldToCallback import org.matrix.android.sdk.internal.session.SessionScope @@ -59,7 +60,8 @@ internal class EventDecryptor @Inject constructor( private val sendToDeviceTask: SendToDeviceTask, private val deviceListManager: DeviceListManager, private val ensureOlmSessionsForDevicesAction: EnsureOlmSessionsForDevicesAction, - private val cryptoStore: IMXCryptoStore + private val cryptoStore: IMXCryptoStore, + private val createUnableToDecryptEventEntityTask: CreateUnableToDecryptEventEntityTask, ) { /** @@ -136,6 +138,7 @@ internal class EventDecryptor @Inject constructor( val eventContent = event.content if (eventContent == null) { Timber.tag(loggerTag.value).e("decryptEvent : empty event content") + createUnableToDecryptEventEntity(event.eventId) throw MXCryptoError.Base(MXCryptoError.ErrorType.BAD_ENCRYPTED_MESSAGE, MXCryptoError.BAD_ENCRYPTED_MESSAGE_REASON) } else if (event.isRedacted()) { // we shouldn't attempt to decrypt a redacted event because the content is cleared and decryption will fail because of null algorithm @@ -153,6 +156,7 @@ internal class EventDecryptor @Inject constructor( if (alg == null) { val reason = String.format(MXCryptoError.UNABLE_TO_DECRYPT_REASON, event.eventId, algorithm) Timber.tag(loggerTag.value).e("decryptEvent() : $reason") + createUnableToDecryptEventEntity(event.eventId) throw MXCryptoError.Base(MXCryptoError.ErrorType.UNABLE_TO_DECRYPT, reason) } else { try { @@ -171,12 +175,20 @@ internal class EventDecryptor @Inject constructor( } } } + createUnableToDecryptEventEntity(event.eventId) throw mxCryptoError } } } } + private suspend fun createUnableToDecryptEventEntity(eventId: String?) { + eventId?.let { + val params = CreateUnableToDecryptEventEntityTask.Params(eventId = it) + createUnableToDecryptEventEntityTask.execute(params) + } + } + private suspend fun markOlmSessionForUnwedging(senderId: String, senderKey: String) { wedgedMutex.withLock { val info = WedgedDeviceInfo(senderId, senderKey) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/tasks/CreateUnableToDecryptEventEntityTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/tasks/CreateUnableToDecryptEventEntityTask.kt new file mode 100644 index 0000000000..17c7ae5ccd --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/tasks/CreateUnableToDecryptEventEntityTask.kt @@ -0,0 +1,46 @@ +/* + * Copyright (c) 2022 The Matrix.org Foundation C.I.C. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.matrix.android.sdk.internal.crypto.tasks + +import io.realm.RealmConfiguration +import org.matrix.android.sdk.internal.crypto.store.db.doRealmTransactionAsync +import org.matrix.android.sdk.internal.database.model.UnableToDecryptEventEntity +import org.matrix.android.sdk.internal.di.SessionDatabase +import org.matrix.android.sdk.internal.task.Task +import javax.inject.Inject + +/** + * This task create a dedicated entity for UTD events so that it can be processed later. + */ +internal interface CreateUnableToDecryptEventEntityTask : Task { + data class Params( + val eventId: String, + ) +} + +// TODO add unit tests +internal class DefaultCreateUnableToDecryptEventEntityTask @Inject constructor( + @SessionDatabase val realmConfiguration: RealmConfiguration, +) : CreateUnableToDecryptEventEntityTask { + + override suspend fun execute(params: CreateUnableToDecryptEventEntityTask.Params) { + val utdEventEntity = UnableToDecryptEventEntity(eventId = params.eventId) + doRealmTransactionAsync(realmConfiguration) { realm -> + realm.insert(utdEventEntity) + } + } +} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/query/EventEntityQueries.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/query/EventEntityQueries.kt index e18495c924..0f1c226044 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/query/EventEntityQueries.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/query/EventEntityQueries.kt @@ -25,7 +25,6 @@ import org.matrix.android.sdk.internal.database.model.EventEntity import org.matrix.android.sdk.internal.database.model.EventEntityFields import org.matrix.android.sdk.internal.database.model.EventInsertEntity import org.matrix.android.sdk.internal.database.model.EventInsertType -import org.matrix.android.sdk.internal.database.model.UnableToDecryptEventEntity internal fun EventEntity.copyToRealmOrIgnore(realm: Realm, insertType: EventInsertType): EventEntity { val eventEntity = realm.where() @@ -33,17 +32,11 @@ internal fun EventEntity.copyToRealmOrIgnore(realm: Realm, insertType: EventInse .equalTo(EventEntityFields.ROOM_ID, roomId) .findFirst() return if (eventEntity == null) { - val isEncrypted = type == EventType.ENCRYPTED && decryptionResultJson == null - val canBeProcessed = isEncrypted.not() + val canBeProcessed = type != EventType.ENCRYPTED || decryptionResultJson != null val insertEntity = EventInsertEntity(eventId = eventId, eventType = type, canBeProcessed = canBeProcessed).apply { this.insertType = insertType } realm.insert(insertEntity) - // TODO check with others if it is the right spot to detect UTD events - if (isEncrypted) { - val utdEventEntity = UnableToDecryptEventEntity(eventId = eventId) - realm.insert(utdEventEntity) - } // copy this event entity and return it realm.copyToRealm(this) } else { From da6b41c34d94052d8975f6debfb987b4bc27ec46 Mon Sep 17 00:00:00 2001 From: Maxime NATUREL Date: Thu, 22 Dec 2022 13:31:43 +0100 Subject: [PATCH 08/40] Updating unit tests for PollItemViewStateFactory --- .../factory/PollItemViewStateFactory.kt | 1 - .../factory/PollItemViewStateFactoryTest.kt | 46 +++++++++++++++++++ 2 files changed, 46 insertions(+), 1 deletion(-) diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/PollItemViewStateFactory.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/PollItemViewStateFactory.kt index 64f529fb59..28c560e161 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/PollItemViewStateFactory.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/PollItemViewStateFactory.kt @@ -31,7 +31,6 @@ class PollItemViewStateFactory @Inject constructor( private val stringProvider: StringProvider, ) { - // TODO update unit tests fun create( pollContent: MessagePollContent, informationData: MessageInformationData, diff --git a/vector/src/test/java/im/vector/app/features/home/room/detail/timeline/factory/PollItemViewStateFactoryTest.kt b/vector/src/test/java/im/vector/app/features/home/room/detail/timeline/factory/PollItemViewStateFactoryTest.kt index 78e544f79d..4d14458e7f 100644 --- a/vector/src/test/java/im/vector/app/features/home/room/detail/timeline/factory/PollItemViewStateFactoryTest.kt +++ b/vector/src/test/java/im/vector/app/features/home/room/detail/timeline/factory/PollItemViewStateFactoryTest.kt @@ -131,6 +131,24 @@ class PollItemViewStateFactoryTest { ) } + @Test + fun `given a sent poll state with some decryption error when poll is closed then warning message is displayed`() { + // Given + val stringProvider = FakeStringProvider() + val pollItemViewStateFactory = PollItemViewStateFactory(stringProvider.instance) + val closedPollSummary = A_POLL_RESPONSE_DATA.copy(isClosed = true, hasDecryptionError = true) + val closedPollInformationData = A_MESSAGE_INFORMATION_DATA.copy(pollResponseAggregatedSummary = closedPollSummary) + + // When + val pollViewState = pollItemViewStateFactory.create( + pollContent = A_POLL_CONTENT, + informationData = closedPollInformationData, + ) + + // Then + pollViewState.votesStatus shouldBeEqualTo stringProvider.instance.getString(R.string.unable_to_decrypt_some_events_in_poll) + } + @Test fun `given a sent poll when undisclosed poll type is selected then poll is votable and option states are PollUndisclosed`() { val stringProvider = FakeStringProvider() @@ -193,6 +211,34 @@ class PollItemViewStateFactoryTest { ) } + @Test + fun `given a sent poll with decryption failure when my vote exists then a warning message is displayed`() { + // Given + val stringProvider = FakeStringProvider() + val pollItemViewStateFactory = PollItemViewStateFactory(stringProvider.instance) + val votedPollData = A_POLL_RESPONSE_DATA.copy( + totalVotes = 1, + myVote = A_POLL_OPTION_IDS[0], + votes = mapOf(A_POLL_OPTION_IDS[0] to PollVoteSummaryData(total = 1, percentage = 1.0)), + hasDecryptionError = true, + ) + val disclosedPollContent = A_POLL_CONTENT.copy( + unstablePollCreationInfo = A_POLL_CONTENT.getBestPollCreationInfo()?.copy( + kind = PollType.DISCLOSED_UNSTABLE + ), + ) + val votedInformationData = A_MESSAGE_INFORMATION_DATA.copy(pollResponseAggregatedSummary = votedPollData) + + // When + val pollViewState = pollItemViewStateFactory.create( + pollContent = disclosedPollContent, + informationData = votedInformationData, + ) + + // Then + pollViewState.votesStatus shouldBeEqualTo stringProvider.instance.getString(R.string.unable_to_decrypt_some_events_in_poll) + } + @Test fun `given a sent poll when poll type is disclosed then poll is votable and option view states are PollReady`() { val stringProvider = FakeStringProvider() From fdc28c0383fd61c91770e6fb2623d662ad16e439 Mon Sep 17 00:00:00 2001 From: Maxime NATUREL Date: Tue, 27 Dec 2022 09:40:58 +0100 Subject: [PATCH 09/40] Add encrypted event id only if not already in the list --- .../aggregation/utd/EncryptedReferenceAggregationProcessor.kt | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/aggregation/utd/EncryptedReferenceAggregationProcessor.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/aggregation/utd/EncryptedReferenceAggregationProcessor.kt index fdd3bc80e3..8987e837bc 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/aggregation/utd/EncryptedReferenceAggregationProcessor.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/aggregation/utd/EncryptedReferenceAggregationProcessor.kt @@ -43,7 +43,9 @@ class EncryptedReferenceAggregationProcessor @Inject constructor() { ) { event.eventId?.let { eventId -> val existingRelatedPoll = getPollSummaryWithEventId(realm, relatedEventId) - existingRelatedPoll?.encryptedRelatedEventIds?.add(eventId) + if (eventId !in existingRelatedPoll?.encryptedRelatedEventIds.orEmpty()) { + existingRelatedPoll?.encryptedRelatedEventIds?.add(eventId) + } } } From eb4de37603e345b6a9d403163a216cfde7455c62 Mon Sep 17 00:00:00 2001 From: Maxime NATUREL Date: Tue, 27 Dec 2022 09:55:32 +0100 Subject: [PATCH 10/40] Updating unit tests for poll aggregation processor --- .../poll/DefaultPollAggregationProcessor.kt | 1 - .../DefaultPollAggregationProcessorTest.kt | 54 ++++++++++++++++--- 2 files changed, 48 insertions(+), 7 deletions(-) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/aggregation/poll/DefaultPollAggregationProcessor.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/aggregation/poll/DefaultPollAggregationProcessor.kt index 5b25238677..e6815eb0ca 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/aggregation/poll/DefaultPollAggregationProcessor.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/aggregation/poll/DefaultPollAggregationProcessor.kt @@ -45,7 +45,6 @@ import org.matrix.android.sdk.internal.session.room.relation.poll.FetchPollRespo import org.matrix.android.sdk.internal.task.TaskExecutor import javax.inject.Inject -// TODO update unit tests internal class DefaultPollAggregationProcessor @Inject constructor( private val taskExecutor: TaskExecutor, private val fetchPollResponseEventsTask: FetchPollResponseEventsTask, diff --git a/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/session/room/aggregation/poll/DefaultPollAggregationProcessorTest.kt b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/session/room/aggregation/poll/DefaultPollAggregationProcessorTest.kt index 0888d82907..766e51a8e5 100644 --- a/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/session/room/aggregation/poll/DefaultPollAggregationProcessorTest.kt +++ b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/session/room/aggregation/poll/DefaultPollAggregationProcessorTest.kt @@ -25,6 +25,8 @@ import kotlinx.coroutines.test.advanceUntilIdle import kotlinx.coroutines.test.runTest import org.amshove.kluent.shouldBeFalse import org.amshove.kluent.shouldBeTrue +import org.amshove.kluent.shouldContain +import org.amshove.kluent.shouldNotContain import org.junit.Before import org.junit.Test import org.matrix.android.sdk.api.session.Session @@ -105,6 +107,24 @@ class DefaultPollAggregationProcessorTest { pollAggregationProcessor.handlePollResponseEvent(session, realm.instance, A_POLL_RESPONSE_EVENT).shouldBeTrue() } + @Test + fun `given a poll response event with a reference, when processing, then event id is removed from encrypted events list`() { + // Given + val anotherEventId = "other-event-id" + val pollResponseAggregatedSummaryEntity = PollResponseAggregatedSummaryEntity( + encryptedRelatedEventIds = RealmList(AN_EVENT_ID, anotherEventId) + ) + every { realm.instance.createObject(PollResponseAggregatedSummaryEntity::class.java) } returns pollResponseAggregatedSummaryEntity + + // When + val result = pollAggregationProcessor.handlePollResponseEvent(session, realm.instance, A_POLL_RESPONSE_EVENT) + + // Then + result.shouldBeTrue() + pollResponseAggregatedSummaryEntity.encryptedRelatedEventIds.shouldNotContain(AN_EVENT_ID) + pollResponseAggregatedSummaryEntity.encryptedRelatedEventIds.shouldContain(anotherEventId) + } + @Test fun `given a poll response event after poll is closed, when processing, then is ignored and returns false`() { every { realm.instance.createObject(PollResponseAggregatedSummaryEntity::class.java) } returns PollResponseAggregatedSummaryEntity().apply { @@ -132,12 +152,33 @@ class DefaultPollAggregationProcessorTest { // Given every { realm.instance.createObject(PollResponseAggregatedSummaryEntity::class.java) } returns PollResponseAggregatedSummaryEntity() every { fakeTaskExecutor.instance.executorScope } returns this - - // When val powerLevelsHelper = mockRedactionPowerLevels(A_USER_ID_1, true) + // When + val result = pollAggregationProcessor.handlePollEndEvent(session, powerLevelsHelper, realm.instance, A_POLL_END_EVENT) + // Then - pollAggregationProcessor.handlePollEndEvent(session, powerLevelsHelper, realm.instance, A_POLL_END_EVENT).shouldBeTrue() + result.shouldBeTrue() + } + + @Test + fun `given a poll end event, when processing, then event id is removed from encrypted events list`() = runTest { + // Given + val anotherEventId = "other-event-id" + val pollResponseAggregatedSummaryEntity = PollResponseAggregatedSummaryEntity( + encryptedRelatedEventIds = RealmList(AN_EVENT_ID, anotherEventId) + ) + every { realm.instance.createObject(PollResponseAggregatedSummaryEntity::class.java) } returns pollResponseAggregatedSummaryEntity + every { fakeTaskExecutor.instance.executorScope } returns this + val powerLevelsHelper = mockRedactionPowerLevels(A_USER_ID_1, true) + + // When + val result = pollAggregationProcessor.handlePollEndEvent(session, powerLevelsHelper, realm.instance, A_POLL_END_EVENT) + + // Then + result.shouldBeTrue() + pollResponseAggregatedSummaryEntity.encryptedRelatedEventIds.shouldNotContain(AN_EVENT_ID) + pollResponseAggregatedSummaryEntity.encryptedRelatedEventIds.shouldContain(anotherEventId) } @Test @@ -145,12 +186,13 @@ class DefaultPollAggregationProcessorTest { // Given every { realm.instance.createObject(PollResponseAggregatedSummaryEntity::class.java) } returns PollResponseAggregatedSummaryEntity() every { fakeTaskExecutor.instance.executorScope } returns this - - // When val powerLevelsHelper = mockRedactionPowerLevels(A_USER_ID_1, false) + // When + val result = pollAggregationProcessor.handlePollEndEvent(session, powerLevelsHelper, realm.instance, A_POLL_END_EVENT) + // Then - pollAggregationProcessor.handlePollEndEvent(session, powerLevelsHelper, realm.instance, A_POLL_END_EVENT).shouldBeTrue() + result.shouldBeTrue() } @Test From 1bd11775e92f454bf6d8e8cc2ac45fb4b9954202 Mon Sep 17 00:00:00 2001 From: Maxime NATUREL Date: Tue, 27 Dec 2022 10:33:25 +0100 Subject: [PATCH 11/40] Adding unit tests for EncryptedReferenceAggregationProcessor --- .../poll/PollAggregationProcessor.kt | 2 +- .../EncryptedReferenceAggregationProcessor.kt | 16 +- ...ryptedReferenceAggregationProcessorTest.kt | 138 ++++++++++++++++++ .../android/sdk/test/fakes/FakeRealm.kt | 8 + 4 files changed, 156 insertions(+), 8 deletions(-) create mode 100644 matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/session/room/aggregation/utd/EncryptedReferenceAggregationProcessorTest.kt diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/aggregation/poll/PollAggregationProcessor.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/aggregation/poll/PollAggregationProcessor.kt index 848643b435..33a69b720a 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/aggregation/poll/PollAggregationProcessor.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/aggregation/poll/PollAggregationProcessor.kt @@ -21,7 +21,7 @@ import org.matrix.android.sdk.api.session.Session import org.matrix.android.sdk.api.session.events.model.Event import org.matrix.android.sdk.api.session.room.powerlevels.PowerLevelsHelper -interface PollAggregationProcessor { +internal interface PollAggregationProcessor { /** * Poll start events don't need to be processed by the aggregator. * This function will only handle if the poll is edited and will update the poll summary entity. diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/aggregation/utd/EncryptedReferenceAggregationProcessor.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/aggregation/utd/EncryptedReferenceAggregationProcessor.kt index 8987e837bc..43631fcc3e 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/aggregation/utd/EncryptedReferenceAggregationProcessor.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/aggregation/utd/EncryptedReferenceAggregationProcessor.kt @@ -1,5 +1,5 @@ /* - * Copyright (c) 2022 New Vector Ltd + * Copyright (c) 2022 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. @@ -22,18 +22,20 @@ import org.matrix.android.sdk.internal.database.model.PollResponseAggregatedSumm import org.matrix.android.sdk.internal.database.model.PollResponseAggregatedSummaryEntityFields import javax.inject.Inject -class EncryptedReferenceAggregationProcessor @Inject constructor() { +internal class EncryptedReferenceAggregationProcessor @Inject constructor() { - // TODO add unit tests fun handle( realm: Realm, event: Event, isLocalEcho: Boolean, relatedEventId: String? - ) { - if (isLocalEcho || relatedEventId.isNullOrEmpty()) return - - handlePollReference(realm = realm, event = event, relatedEventId = relatedEventId) + ): Boolean { + return if (isLocalEcho || relatedEventId.isNullOrEmpty()) { + false + } else { + handlePollReference(realm = realm, event = event, relatedEventId = relatedEventId) + true + } } private fun handlePollReference( diff --git a/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/session/room/aggregation/utd/EncryptedReferenceAggregationProcessorTest.kt b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/session/room/aggregation/utd/EncryptedReferenceAggregationProcessorTest.kt new file mode 100644 index 0000000000..2998b9bff0 --- /dev/null +++ b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/session/room/aggregation/utd/EncryptedReferenceAggregationProcessorTest.kt @@ -0,0 +1,138 @@ +/* + * Copyright (c) 2022 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.room.aggregation.utd + +import io.mockk.every +import io.mockk.mockk +import io.realm.RealmList +import org.amshove.kluent.shouldBeFalse +import org.amshove.kluent.shouldBeTrue +import org.amshove.kluent.shouldContain +import org.junit.Test +import org.matrix.android.sdk.api.session.events.model.Event +import org.matrix.android.sdk.internal.database.model.PollResponseAggregatedSummaryEntity +import org.matrix.android.sdk.internal.database.model.PollResponseAggregatedSummaryEntityFields +import org.matrix.android.sdk.test.fakes.FakeRealm +import org.matrix.android.sdk.test.fakes.givenContainsValue +import org.matrix.android.sdk.test.fakes.givenFindFirst + +internal class EncryptedReferenceAggregationProcessorTest { + + private val fakeRealm = FakeRealm() + + private val encryptedReferenceAggregationProcessor = EncryptedReferenceAggregationProcessor() + + @Test + fun `given local echo when process then result is false`() { + // Given + val anEvent = mockk() + val isLocalEcho = true + val relatedEventId = "event-id" + + // When + val result = encryptedReferenceAggregationProcessor.handle( + realm = fakeRealm.instance, + event = anEvent, + isLocalEcho = isLocalEcho, + relatedEventId = relatedEventId, + ) + + // Then + result.shouldBeFalse() + } + + @Test + fun `given invalid event id when process then result is false`() { + // Given + val anEvent = mockk() + val isLocalEcho = false + + // When + val result1 = encryptedReferenceAggregationProcessor.handle( + realm = fakeRealm.instance, + event = anEvent, + isLocalEcho = isLocalEcho, + relatedEventId = null, + ) + val result2 = encryptedReferenceAggregationProcessor.handle( + realm = fakeRealm.instance, + event = anEvent, + isLocalEcho = isLocalEcho, + relatedEventId = "", + ) + + // Then + result1.shouldBeFalse() + result2.shouldBeFalse() + } + + @Test + fun `given related event id of an existing poll when process then result is true and event id is stored in poll summary`() { + // Given + val anEventId = "event-id" + val anEvent = givenAnEvent(anEventId) + val isLocalEcho = false + val relatedEventId = "related-event-id" + val pollResponseAggregatedSummaryEntity = PollResponseAggregatedSummaryEntity( + encryptedRelatedEventIds = RealmList(), + ) + fakeRealm.givenWhere() + .givenContainsValue(PollResponseAggregatedSummaryEntityFields.SOURCE_EVENTS.`$`, relatedEventId) + .givenFindFirst(pollResponseAggregatedSummaryEntity) + + // When + val result = encryptedReferenceAggregationProcessor.handle( + realm = fakeRealm.instance, + event = anEvent, + isLocalEcho = isLocalEcho, + relatedEventId = relatedEventId, + ) + + // Then + result.shouldBeTrue() + pollResponseAggregatedSummaryEntity.encryptedRelatedEventIds.shouldContain(anEventId) + } + + @Test + fun `given related event id but no existing related poll when process then result is true and event id is not stored`() { + // Given + val anEventId = "event-id" + val anEvent = givenAnEvent(anEventId) + val isLocalEcho = false + val relatedEventId = "related-event-id" + fakeRealm.givenWhere() + .givenContainsValue(PollResponseAggregatedSummaryEntityFields.SOURCE_EVENTS.`$`, relatedEventId) + .givenFindFirst(null) + + // When + val result = encryptedReferenceAggregationProcessor.handle( + realm = fakeRealm.instance, + event = anEvent, + isLocalEcho = isLocalEcho, + relatedEventId = relatedEventId, + ) + + // Then + result.shouldBeTrue() + } + + private fun givenAnEvent(eventId: String): Event { + return mockk().also { + every { it.eventId } returns eventId + } + } +} diff --git a/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fakes/FakeRealm.kt b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fakes/FakeRealm.kt index ba124a86aa..49d64c1835 100644 --- a/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fakes/FakeRealm.kt +++ b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fakes/FakeRealm.kt @@ -117,6 +117,14 @@ inline fun RealmQuery.givenIn( return this } +inline fun RealmQuery.givenContainsValue( + fieldName: String, + value: String, +): RealmQuery { + every { containsValue(fieldName, value) } returns this + return this +} + /** * Should be called on a mocked RealmObject and not on a real RealmObject so that the underlying final method is mocked. */ From a04c60a85bee8c52e6dc1c94ce5be13283863db0 Mon Sep 17 00:00:00 2001 From: Maxime NATUREL Date: Tue, 27 Dec 2022 11:57:50 +0100 Subject: [PATCH 12/40] Adding unit tests for EncryptedEventRelationsAggregationProcessor --- .../UnableToDecryptEventLiveProcessor.kt | 8 +- ...yptedEventRelationsAggregationProcessor.kt | 49 ++-- ...dEventRelationsAggregationProcessorTest.kt | 209 ++++++++++++++++++ ...eEncryptedReferenceAggregationProcessor.kt | 42 ++++ 4 files changed, 284 insertions(+), 24 deletions(-) create mode 100644 matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/session/room/EncryptedEventRelationsAggregationProcessorTest.kt create mode 100644 matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fakes/internal/session/room/aggregation/utd/FakeEncryptedReferenceAggregationProcessor.kt diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/UnableToDecryptEventLiveProcessor.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/UnableToDecryptEventLiveProcessor.kt index c5fa6dc88e..8ff8cec6ce 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/UnableToDecryptEventLiveProcessor.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/UnableToDecryptEventLiveProcessor.kt @@ -21,7 +21,13 @@ import org.matrix.android.sdk.api.session.events.model.Event internal interface UnableToDecryptEventLiveProcessor { - fun process(realm: Realm, event: Event) + /** + * Process the given event. + * @param realm a realm instance + * @param event the event to be processed + * @return true if it has been processed, false if it was ignored. + */ + fun process(realm: Realm, event: Event): Boolean /** * Called after transaction. diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/EncryptedEventRelationsAggregationProcessor.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/EncryptedEventRelationsAggregationProcessor.kt index bae95f1c15..1d0270d8e4 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/EncryptedEventRelationsAggregationProcessor.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/EncryptedEventRelationsAggregationProcessor.kt @@ -32,28 +32,27 @@ internal class EncryptedEventRelationsAggregationProcessor @Inject constructor( private val encryptedReferenceAggregationProcessor: EncryptedReferenceAggregationProcessor, ) : UnableToDecryptEventLiveProcessor { - // TODO add unit tests - override fun process(realm: Realm, event: Event) { + override fun process(realm: Realm, event: Event): Boolean { val roomId = event.roomId - if (roomId == null) { + return if (roomId == null) { Timber.w("Event has no room id ${event.eventId}") - return - } + false + } else { + val isLocalEcho = LocalEcho.isLocalEchoId(event.eventId ?: "") - val isLocalEcho = LocalEcho.isLocalEchoId(event.eventId ?: "") - - when (event.getClearType()) { - EventType.ENCRYPTED -> { - val encryptedEventContent = event.content.toModel() - processEncryptedContent( - encryptedEventContent = encryptedEventContent, - realm = realm, - event = event, - roomId = roomId, - isLocalEcho = isLocalEcho, - ) + return when (event.getClearType()) { + EventType.ENCRYPTED -> { + val encryptedEventContent = event.content.toModel() + processEncryptedContent( + encryptedEventContent = encryptedEventContent, + realm = realm, + event = event, + roomId = roomId, + isLocalEcho = isLocalEcho, + ) + } + else -> false } - else -> Unit } } @@ -63,30 +62,34 @@ internal class EncryptedEventRelationsAggregationProcessor @Inject constructor( event: Event, roomId: String, isLocalEcho: Boolean, - ) { - when (encryptedEventContent?.relatesTo?.type) { + ): Boolean { + return when (encryptedEventContent?.relatesTo?.type) { RelationType.REPLACE -> { Timber.w("## UTD replace in room $roomId for event ${event.eventId}") + false } RelationType.RESPONSE -> { // can we / should we do we something for UTD response?? Timber.w("## UTD response in room $roomId related to ${encryptedEventContent.relatesTo.eventId}") + false } RelationType.REFERENCE -> { // can we / should we do we something for UTD reference?? Timber.w("## UTD reference in room $roomId related to ${encryptedEventContent.relatesTo.eventId}") - encryptedReferenceAggregationProcessor.handle( + val result = encryptedReferenceAggregationProcessor.handle( realm = realm, event = event, isLocalEcho = isLocalEcho, - relatedEventId = encryptedEventContent.relatesTo.eventId + relatedEventId = encryptedEventContent.relatesTo.eventId, ) + result } RelationType.ANNOTATION -> { // can we / should we do we something for UTD annotation?? Timber.w("## UTD annotation in room $roomId related to ${encryptedEventContent.relatesTo.eventId}") + false } - else -> Unit + else -> false } } } diff --git a/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/session/room/EncryptedEventRelationsAggregationProcessorTest.kt b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/session/room/EncryptedEventRelationsAggregationProcessorTest.kt new file mode 100644 index 0000000000..9d68ed1a77 --- /dev/null +++ b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/session/room/EncryptedEventRelationsAggregationProcessorTest.kt @@ -0,0 +1,209 @@ +/* + * Copyright (c) 2022 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.room + +import io.mockk.every +import io.mockk.mockk +import org.amshove.kluent.shouldBeEqualTo +import org.amshove.kluent.shouldBeFalse +import org.junit.Test +import org.matrix.android.sdk.api.session.events.model.Event +import org.matrix.android.sdk.api.session.events.model.EventType +import org.matrix.android.sdk.api.session.events.model.RelationType +import org.matrix.android.sdk.api.session.events.model.content.EncryptedEventContent +import org.matrix.android.sdk.api.session.events.model.toContent +import org.matrix.android.sdk.api.session.room.model.relation.RelationDefaultContent +import org.matrix.android.sdk.test.fakes.FakeRealm +import org.matrix.android.sdk.test.fakes.internal.session.room.aggregation.utd.FakeEncryptedReferenceAggregationProcessor + +class EncryptedEventRelationsAggregationProcessorTest { + + private val fakeEncryptedReferenceAggregationProcessor = FakeEncryptedReferenceAggregationProcessor() + private val fakeRealm = FakeRealm() + + private val encryptedEventRelationsAggregationProcessor = EncryptedEventRelationsAggregationProcessor( + encryptedReferenceAggregationProcessor = fakeEncryptedReferenceAggregationProcessor.instance, + ) + + @Test + fun `given no room Id when process then result is false`() { + // Given + val anEvent = givenAnEvent( + eventId = "event-id", + roomId = null, + eventType = EventType.ENCRYPTED, + ) + + // When + val result = encryptedEventRelationsAggregationProcessor.process( + realm = fakeRealm.instance, + event = anEvent, + ) + + // Then + result.shouldBeFalse() + } + + @Test + fun `given an encrypted reference event when process then reference is processed`() { + // Given + val anEvent = givenAnEvent( + eventId = "event-id", + roomId = "room-id", + eventType = EventType.ENCRYPTED, + ) + val relatedEventId = "related-event-id" + val encryptedEventContent = givenEncryptedEventContent( + relationType = RelationType.REFERENCE, + relatedEventId = relatedEventId, + ) + every { anEvent.content } returns encryptedEventContent.toContent() + val resultOfReferenceProcess = false + fakeEncryptedReferenceAggregationProcessor.givenHandleReturns(resultOfReferenceProcess) + + // When + val result = encryptedEventRelationsAggregationProcessor.process( + realm = fakeRealm.instance, + event = anEvent, + ) + + // Then + result shouldBeEqualTo resultOfReferenceProcess + fakeEncryptedReferenceAggregationProcessor.verifyHandle( + realm = fakeRealm.instance, + event = anEvent, + isLocalEcho = false, + relatedEventId = relatedEventId, + ) + } + + @Test + fun `given an encrypted replace event when process then result is false`() { + // Given + val anEvent = givenAnEvent( + eventId = "event-id", + roomId = "room-id", + eventType = EventType.ENCRYPTED, + ) + val relatedEventId = "related-event-id" + val encryptedEventContent = givenEncryptedEventContent( + relationType = RelationType.REPLACE, + relatedEventId = relatedEventId, + ) + every { anEvent.content } returns encryptedEventContent.toContent() + + // When + val result = encryptedEventRelationsAggregationProcessor.process( + realm = fakeRealm.instance, + event = anEvent, + ) + + // Then + result.shouldBeFalse() + } + + @Test + fun `given an encrypted response event when process then result is false`() { + // Given + val anEvent = givenAnEvent( + eventId = "event-id", + roomId = "room-id", + eventType = EventType.ENCRYPTED, + ) + val relatedEventId = "related-event-id" + val encryptedEventContent = givenEncryptedEventContent( + relationType = RelationType.RESPONSE, + relatedEventId = relatedEventId, + ) + every { anEvent.content } returns encryptedEventContent.toContent() + + // When + val result = encryptedEventRelationsAggregationProcessor.process( + realm = fakeRealm.instance, + event = anEvent, + ) + + // Then + result.shouldBeFalse() + } + + @Test + fun `given an encrypted annotation event when process then result is false`() { + // Given + val anEvent = givenAnEvent( + eventId = "event-id", + roomId = "room-id", + eventType = EventType.ENCRYPTED, + ) + val relatedEventId = "related-event-id" + val encryptedEventContent = givenEncryptedEventContent( + relationType = RelationType.ANNOTATION, + relatedEventId = relatedEventId, + ) + every { anEvent.content } returns encryptedEventContent.toContent() + + // When + val result = encryptedEventRelationsAggregationProcessor.process( + realm = fakeRealm.instance, + event = anEvent, + ) + + // Then + result.shouldBeFalse() + } + + @Test + fun `given a non encrypted event when process then result is false`() { + // Given + val anEvent = givenAnEvent( + eventId = "event-id", + roomId = "room-id", + eventType = EventType.MESSAGE, + ) + + // When + val result = encryptedEventRelationsAggregationProcessor.process( + realm = fakeRealm.instance, + event = anEvent, + ) + + // Then + result.shouldBeFalse() + } + + private fun givenAnEvent( + eventId: String, + roomId: String?, + eventType: String, + ): Event { + return mockk().also { + every { it.eventId } returns eventId + every { it.roomId } returns roomId + every { it.getClearType() } returns eventType + } + } + + private fun givenEncryptedEventContent(relationType: String, relatedEventId: String): EncryptedEventContent { + val relationContent = RelationDefaultContent( + eventId = relatedEventId, + type = relationType, + ) + return EncryptedEventContent( + relatesTo = relationContent, + ) + } +} diff --git a/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fakes/internal/session/room/aggregation/utd/FakeEncryptedReferenceAggregationProcessor.kt b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fakes/internal/session/room/aggregation/utd/FakeEncryptedReferenceAggregationProcessor.kt new file mode 100644 index 0000000000..7661095fe3 --- /dev/null +++ b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fakes/internal/session/room/aggregation/utd/FakeEncryptedReferenceAggregationProcessor.kt @@ -0,0 +1,42 @@ +/* + * Copyright (c) 2022 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.test.fakes.internal.session.room.aggregation.utd + +import io.mockk.every +import io.mockk.mockk +import io.mockk.verify +import io.realm.Realm +import org.matrix.android.sdk.api.session.events.model.Event +import org.matrix.android.sdk.internal.session.room.aggregation.utd.EncryptedReferenceAggregationProcessor + +internal class FakeEncryptedReferenceAggregationProcessor { + + val instance: EncryptedReferenceAggregationProcessor = mockk() + + fun givenHandleReturns(result: Boolean) { + every { instance.handle(any(), any(), any(), any()) } returns result + } + + fun verifyHandle( + realm: Realm, + event: Event, + isLocalEcho: Boolean, + relatedEventId: String?, + ) { + verify { instance.handle(realm, event, isLocalEcho, relatedEventId) } + } +} From e9f59d85b46a2fc8b84362859b16bab18c0ca756 Mon Sep 17 00:00:00 2001 From: Maxime NATUREL Date: Tue, 27 Dec 2022 14:38:21 +0100 Subject: [PATCH 13/40] Adding unit tests for DefaultCreateUnableToDecryptEventEntityTask --- .../CreateUnableToDecryptEventEntityTask.kt | 1 - ...reateUnableToDecryptEventEntityTaskTest.kt | 65 +++++++++++++++++++ .../android/sdk/test/fakes/FakeRealm.kt | 8 +++ .../sdk/test/fakes/FakeRealmConfiguration.kt | 6 ++ 4 files changed, 79 insertions(+), 1 deletion(-) create mode 100644 matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/crypto/tasks/DefaultCreateUnableToDecryptEventEntityTaskTest.kt diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/tasks/CreateUnableToDecryptEventEntityTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/tasks/CreateUnableToDecryptEventEntityTask.kt index 17c7ae5ccd..c502e19e35 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/tasks/CreateUnableToDecryptEventEntityTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/tasks/CreateUnableToDecryptEventEntityTask.kt @@ -32,7 +32,6 @@ internal interface CreateUnableToDecryptEventEntityTask : Task { + it is UnableToDecryptEventEntity && it.eventId == anEventId + }) + } + } +} diff --git a/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fakes/FakeRealm.kt b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fakes/FakeRealm.kt index 49d64c1835..1f9bc2a976 100644 --- a/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fakes/FakeRealm.kt +++ b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fakes/FakeRealm.kt @@ -23,6 +23,7 @@ import io.mockk.mockk import io.mockk.runs import io.mockk.verify import io.realm.Realm +import io.realm.Realm.Transaction import io.realm.RealmModel import io.realm.RealmObject import io.realm.RealmQuery @@ -42,6 +43,13 @@ internal class FakeRealm { inline fun verifyInsertOrUpdate(crossinline verification: MockKVerificationScope.() -> T) { verify { instance.insertOrUpdate(verification()) } } + + fun givenExecuteTransactionAsync() { + every { instance.executeTransactionAsync(any()) } answers { + firstArg().execute(instance) + mockk() + } + } } inline fun RealmQuery.givenFindFirst( diff --git a/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fakes/FakeRealmConfiguration.kt b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fakes/FakeRealmConfiguration.kt index 9ad7032262..3a69515140 100644 --- a/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fakes/FakeRealmConfiguration.kt +++ b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fakes/FakeRealmConfiguration.kt @@ -17,6 +17,7 @@ package org.matrix.android.sdk.test.fakes import io.mockk.coEvery +import io.mockk.every import io.mockk.mockk import io.mockk.mockkStatic import io.realm.Realm @@ -36,4 +37,9 @@ internal class FakeRealmConfiguration { secondArg<(Realm) -> T>().invoke(realm) } } + + fun givenGetRealmInstance(realm: Realm) { + mockkStatic(Realm::class) + every { Realm.getInstance(instance) } returns realm + } } From b001bc382f5d8dd96aadeec191170c6e14eea3e6 Mon Sep 17 00:00:00 2001 From: Maxime NATUREL Date: Tue, 27 Dec 2022 15:29:42 +0100 Subject: [PATCH 14/40] Fixing code style issue --- .../room/aggregation/poll/DefaultPollAggregationProcessor.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/aggregation/poll/DefaultPollAggregationProcessor.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/aggregation/poll/DefaultPollAggregationProcessor.kt index e6815eb0ca..2ff43d6812 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/aggregation/poll/DefaultPollAggregationProcessor.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/aggregation/poll/DefaultPollAggregationProcessor.kt @@ -232,7 +232,7 @@ internal class DefaultPollAggregationProcessor @Inject constructor( } private fun removeEncryptedRelatedEventIdIfNeeded(aggregatedPollSummaryEntity: PollResponseAggregatedSummaryEntity, eventId: String) { - if(aggregatedPollSummaryEntity.encryptedRelatedEventIds.contains(eventId)) { + if (aggregatedPollSummaryEntity.encryptedRelatedEventIds.contains(eventId)) { aggregatedPollSummaryEntity.encryptedRelatedEventIds.remove(eventId) } } From fd58875c31c0c42f4d5c4136c243456ed58b46f1 Mon Sep 17 00:00:00 2001 From: Maxime NATUREL <46314705+mnaturel@users.noreply.github.com> Date: Tue, 3 Jan 2023 10:43:34 +0100 Subject: [PATCH 15/40] Database migration --- .../database/RealmSessionStoreMigration.kt | 4 +- .../database/migration/MigrateSessionTo048.kt | 38 +++++++++++++++++++ 2 files changed, 41 insertions(+), 1 deletion(-) create mode 100644 matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/migration/MigrateSessionTo048.kt diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/RealmSessionStoreMigration.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/RealmSessionStoreMigration.kt index ba102a7a48..2b7e9a04a1 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/RealmSessionStoreMigration.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/RealmSessionStoreMigration.kt @@ -64,6 +64,7 @@ import org.matrix.android.sdk.internal.database.migration.MigrateSessionTo044 import org.matrix.android.sdk.internal.database.migration.MigrateSessionTo045 import org.matrix.android.sdk.internal.database.migration.MigrateSessionTo046 import org.matrix.android.sdk.internal.database.migration.MigrateSessionTo047 +import org.matrix.android.sdk.internal.database.migration.MigrateSessionTo048 import org.matrix.android.sdk.internal.util.Normalizer import org.matrix.android.sdk.internal.util.database.MatrixRealmMigration import javax.inject.Inject @@ -72,7 +73,7 @@ internal class RealmSessionStoreMigration @Inject constructor( private val normalizer: Normalizer ) : MatrixRealmMigration( dbName = "Session", - schemaVersion = 47L, + schemaVersion = 48L, ) { /** * Forces all RealmSessionStoreMigration instances to be equal. @@ -129,5 +130,6 @@ internal class RealmSessionStoreMigration @Inject constructor( if (oldVersion < 45) MigrateSessionTo045(realm).perform() if (oldVersion < 46) MigrateSessionTo046(realm).perform() if (oldVersion < 47) MigrateSessionTo047(realm).perform() + if (oldVersion < 48) MigrateSessionTo048(realm).perform() } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/migration/MigrateSessionTo048.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/migration/MigrateSessionTo048.kt new file mode 100644 index 0000000000..f9bd8ae7aa --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/migration/MigrateSessionTo048.kt @@ -0,0 +1,38 @@ +/* + * Copyright (c) 2023 The Matrix.org Foundation C.I.C. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.matrix.android.sdk.internal.database.migration + +import io.realm.DynamicRealm +import org.matrix.android.sdk.internal.database.model.PollResponseAggregatedSummaryEntityFields +import org.matrix.android.sdk.internal.database.model.UnableToDecryptEventEntityFields +import org.matrix.android.sdk.internal.util.database.RealmMigrator + +/** + * Adding a new field in poll summary to keep track of non decrypted related events. + * Adding a new entity UnableToDecryptEventEntity. + */ +internal class MigrateSessionTo048(realm: DynamicRealm) : RealmMigrator(realm, 47) { + + override fun doMigrate(realm: DynamicRealm) { + realm.schema.get("PollResponseAggregatedSummaryEntity") + ?.addRealmListField(PollResponseAggregatedSummaryEntityFields.ENCRYPTED_RELATED_EVENT_IDS.`$`, String::class.java) + + realm.schema.create("UnableToDecryptEventEntity") + ?.addField(UnableToDecryptEventEntityFields.EVENT_ID, String::class.java) + ?.setRequired(UnableToDecryptEventEntityFields.EVENT_ID, true) + } +} From 8b051c5b86cf6dfd12b41dd95b8665af8b2e5eb9 Mon Sep 17 00:00:00 2001 From: Maxime NATUREL <46314705+mnaturel@users.noreply.github.com> Date: Fri, 6 Jan 2023 15:19:33 +0100 Subject: [PATCH 16/40] Replace usage of new UnableToDecryptEventEntity by usage of existing EventInsertEntity --- .../sdk/internal/crypto/CryptoModule.kt | 5 - .../sdk/internal/crypto/EventDecryptor.kt | 12 - .../CreateUnableToDecryptEventEntityTask.kt | 45 ---- .../database/EventInsertLiveObserver.kt | 16 +- .../UnableToDecryptEventLiveObserver.kt | 83 ------- .../database/migration/MigrateSessionTo048.kt | 8 +- .../database/model/EventInsertEntity.kt | 2 +- .../database/model/SessionRealmModule.kt | 1 - .../database/query/EventEntityQueries.kt | 8 +- .../sdk/internal/session/SessionModule.kt | 10 - .../UnableToDecryptEventLiveProcessor.kt | 39 ---- ...yptedEventRelationsAggregationProcessor.kt | 95 -------- .../EventRelationsAggregationProcessor.kt | 43 ++++ ...reateUnableToDecryptEventEntityTaskTest.kt | 65 ------ ...dEventRelationsAggregationProcessorTest.kt | 209 ------------------ .../EventRelationsAggregationProcessorTest.kt | 132 +++++++++++ .../android/sdk/test/fakes/FakeRealm.kt | 8 - .../sdk/test/fakes/FakeRealmConfiguration.kt | 6 - .../fakes/internal/FakeEventEditValidator.kt} | 18 +- .../FakeLiveLocationAggregationProcessor.kt | 25 +++ .../internal/FakePollAggregationProcessor.kt | 25 +++ 21 files changed, 252 insertions(+), 603 deletions(-) delete mode 100644 matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/tasks/CreateUnableToDecryptEventEntityTask.kt delete mode 100644 matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/UnableToDecryptEventLiveObserver.kt delete mode 100644 matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/UnableToDecryptEventLiveProcessor.kt delete mode 100644 matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/EncryptedEventRelationsAggregationProcessor.kt delete mode 100644 matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/crypto/tasks/DefaultCreateUnableToDecryptEventEntityTaskTest.kt delete mode 100644 matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/session/room/EncryptedEventRelationsAggregationProcessorTest.kt create mode 100644 matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/session/room/EventRelationsAggregationProcessorTest.kt rename matrix-sdk-android/src/{main/java/org/matrix/android/sdk/internal/database/model/UnableToDecryptEventEntity.kt => test/java/org/matrix/android/sdk/test/fakes/internal/FakeEventEditValidator.kt} (57%) create mode 100644 matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fakes/internal/FakeLiveLocationAggregationProcessor.kt create mode 100644 matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fakes/internal/FakePollAggregationProcessor.kt diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/CryptoModule.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/CryptoModule.kt index d78f4a3107..c69a859016 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/CryptoModule.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/CryptoModule.kt @@ -62,9 +62,7 @@ import org.matrix.android.sdk.internal.crypto.store.db.RealmCryptoStore import org.matrix.android.sdk.internal.crypto.store.db.RealmCryptoStoreMigration import org.matrix.android.sdk.internal.crypto.store.db.RealmCryptoStoreModule import org.matrix.android.sdk.internal.crypto.tasks.ClaimOneTimeKeysForUsersDeviceTask -import org.matrix.android.sdk.internal.crypto.tasks.CreateUnableToDecryptEventEntityTask import org.matrix.android.sdk.internal.crypto.tasks.DefaultClaimOneTimeKeysForUsersDevice -import org.matrix.android.sdk.internal.crypto.tasks.DefaultCreateUnableToDecryptEventEntityTask import org.matrix.android.sdk.internal.crypto.tasks.DefaultDeleteDeviceTask import org.matrix.android.sdk.internal.crypto.tasks.DefaultDownloadKeysForUsers import org.matrix.android.sdk.internal.crypto.tasks.DefaultEncryptEventTask @@ -255,7 +253,4 @@ internal abstract class CryptoModule { @Binds abstract fun bindSendEventTask(task: DefaultSendEventTask): SendEventTask - - @Binds - abstract fun bindCreateUnableToDecryptEventEntityTask(task: DefaultCreateUnableToDecryptEventEntityTask): CreateUnableToDecryptEventEntityTask } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/EventDecryptor.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/EventDecryptor.kt index 524db32670..03672ae81c 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/EventDecryptor.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/EventDecryptor.kt @@ -38,7 +38,6 @@ import org.matrix.android.sdk.api.session.events.model.toModel import org.matrix.android.sdk.internal.crypto.actions.EnsureOlmSessionsForDevicesAction import org.matrix.android.sdk.internal.crypto.actions.MessageEncrypter import org.matrix.android.sdk.internal.crypto.store.IMXCryptoStore -import org.matrix.android.sdk.internal.crypto.tasks.CreateUnableToDecryptEventEntityTask import org.matrix.android.sdk.internal.crypto.tasks.SendToDeviceTask import org.matrix.android.sdk.internal.extensions.foldToCallback import org.matrix.android.sdk.internal.session.SessionScope @@ -61,7 +60,6 @@ internal class EventDecryptor @Inject constructor( private val deviceListManager: DeviceListManager, private val ensureOlmSessionsForDevicesAction: EnsureOlmSessionsForDevicesAction, private val cryptoStore: IMXCryptoStore, - private val createUnableToDecryptEventEntityTask: CreateUnableToDecryptEventEntityTask, ) { /** @@ -138,7 +136,6 @@ internal class EventDecryptor @Inject constructor( val eventContent = event.content if (eventContent == null) { Timber.tag(loggerTag.value).e("decryptEvent : empty event content") - createUnableToDecryptEventEntity(event.eventId) throw MXCryptoError.Base(MXCryptoError.ErrorType.BAD_ENCRYPTED_MESSAGE, MXCryptoError.BAD_ENCRYPTED_MESSAGE_REASON) } else if (event.isRedacted()) { // we shouldn't attempt to decrypt a redacted event because the content is cleared and decryption will fail because of null algorithm @@ -156,7 +153,6 @@ internal class EventDecryptor @Inject constructor( if (alg == null) { val reason = String.format(MXCryptoError.UNABLE_TO_DECRYPT_REASON, event.eventId, algorithm) Timber.tag(loggerTag.value).e("decryptEvent() : $reason") - createUnableToDecryptEventEntity(event.eventId) throw MXCryptoError.Base(MXCryptoError.ErrorType.UNABLE_TO_DECRYPT, reason) } else { try { @@ -175,20 +171,12 @@ internal class EventDecryptor @Inject constructor( } } } - createUnableToDecryptEventEntity(event.eventId) throw mxCryptoError } } } } - private suspend fun createUnableToDecryptEventEntity(eventId: String?) { - eventId?.let { - val params = CreateUnableToDecryptEventEntityTask.Params(eventId = it) - createUnableToDecryptEventEntityTask.execute(params) - } - } - private suspend fun markOlmSessionForUnwedging(senderId: String, senderKey: String) { wedgedMutex.withLock { val info = WedgedDeviceInfo(senderId, senderKey) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/tasks/CreateUnableToDecryptEventEntityTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/tasks/CreateUnableToDecryptEventEntityTask.kt deleted file mode 100644 index c502e19e35..0000000000 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/tasks/CreateUnableToDecryptEventEntityTask.kt +++ /dev/null @@ -1,45 +0,0 @@ -/* - * Copyright (c) 2022 The Matrix.org Foundation C.I.C. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.matrix.android.sdk.internal.crypto.tasks - -import io.realm.RealmConfiguration -import org.matrix.android.sdk.internal.crypto.store.db.doRealmTransactionAsync -import org.matrix.android.sdk.internal.database.model.UnableToDecryptEventEntity -import org.matrix.android.sdk.internal.di.SessionDatabase -import org.matrix.android.sdk.internal.task.Task -import javax.inject.Inject - -/** - * This task create a dedicated entity for UTD events so that it can be processed later. - */ -internal interface CreateUnableToDecryptEventEntityTask : Task { - data class Params( - val eventId: String, - ) -} - -internal class DefaultCreateUnableToDecryptEventEntityTask @Inject constructor( - @SessionDatabase val realmConfiguration: RealmConfiguration, -) : CreateUnableToDecryptEventEntityTask { - - override suspend fun execute(params: CreateUnableToDecryptEventEntityTask.Params) { - val utdEventEntity = UnableToDecryptEventEntity(eventId = params.eventId) - doRealmTransactionAsync(realmConfiguration) { realm -> - realm.insert(utdEventEntity) - } - } -} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/EventInsertLiveObserver.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/EventInsertLiveObserver.kt index d1ca4f48a6..84fb14c914 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/EventInsertLiveObserver.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/EventInsertLiveObserver.kt @@ -22,6 +22,7 @@ import io.realm.RealmResults import kotlinx.coroutines.launch import kotlinx.coroutines.sync.Mutex import kotlinx.coroutines.sync.withLock +import org.matrix.android.sdk.api.session.events.model.EventType import org.matrix.android.sdk.internal.database.mapper.asDomain import org.matrix.android.sdk.internal.database.model.EventEntity import org.matrix.android.sdk.internal.database.model.EventInsertEntity @@ -34,7 +35,7 @@ import javax.inject.Inject internal class EventInsertLiveObserver @Inject constructor( @SessionDatabase realmConfiguration: RealmConfiguration, - private val processors: Set<@JvmSuppressWildcards EventInsertLiveProcessor> + private val processors: Set<@JvmSuppressWildcards EventInsertLiveProcessor>, ) : RealmLiveEntityObserver(realmConfiguration) { @@ -51,6 +52,7 @@ internal class EventInsertLiveObserver @Inject constructor( return@withLock } val idsToDeleteAfterProcess = ArrayList() + val idsOfEncryptedEvents = ArrayList() val filteredEvents = ArrayList(results.size) Timber.v("EventInsertEntity updated with ${results.size} results in db") results.forEach { @@ -64,7 +66,11 @@ internal class EventInsertLiveObserver @Inject constructor( } filteredEvents.add(copiedEvent) } - idsToDeleteAfterProcess.add(it.eventId) + if (it.eventType == EventType.ENCRYPTED) { + idsOfEncryptedEvents.add(it.eventId) + } else { + idsToDeleteAfterProcess.add(it.eventId) + } } awaitTransaction(realmConfiguration) { realm -> Timber.v("##Transaction: There are ${filteredEvents.size} events to process ") @@ -86,6 +92,12 @@ internal class EventInsertLiveObserver @Inject constructor( .`in`(EventInsertEntityFields.EVENT_ID, idsToDeleteAfterProcess.toTypedArray()) .findAll() .deleteAllFromRealm() + + // make the encrypted events not processable: they will be processed again after decryption + realm.where(EventInsertEntity::class.java) + .`in`(EventInsertEntityFields.EVENT_ID, idsOfEncryptedEvents.toTypedArray()) + .findAll() + .forEach { it.canBeProcessed = false } } processors.forEach { it.onPostProcess() } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/UnableToDecryptEventLiveObserver.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/UnableToDecryptEventLiveObserver.kt deleted file mode 100644 index 7835a16ad1..0000000000 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/UnableToDecryptEventLiveObserver.kt +++ /dev/null @@ -1,83 +0,0 @@ -/* - * Copyright 2022 The Matrix.org Foundation C.I.C. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.matrix.android.sdk.internal.database - -import com.zhuinden.monarchy.Monarchy -import io.realm.RealmConfiguration -import io.realm.RealmResults -import kotlinx.coroutines.launch -import kotlinx.coroutines.sync.Mutex -import kotlinx.coroutines.sync.withLock -import org.matrix.android.sdk.internal.database.mapper.asDomain -import org.matrix.android.sdk.internal.database.model.EventEntity -import org.matrix.android.sdk.internal.database.model.UnableToDecryptEventEntity -import org.matrix.android.sdk.internal.database.model.UnableToDecryptEventEntityFields -import org.matrix.android.sdk.internal.database.query.where -import org.matrix.android.sdk.internal.di.SessionDatabase -import org.matrix.android.sdk.internal.session.UnableToDecryptEventLiveProcessor -import timber.log.Timber -import javax.inject.Inject - -internal class UnableToDecryptEventLiveObserver @Inject constructor( - @SessionDatabase realmConfiguration: RealmConfiguration, - private val processors: Set<@JvmSuppressWildcards UnableToDecryptEventLiveProcessor> -) : - RealmLiveEntityObserver(realmConfiguration) { - - private val lock = Mutex() - - override val query = Monarchy.Query { - it.where(UnableToDecryptEventEntity::class.java) - } - - override fun onChange(results: RealmResults) { - observerScope.launch { - lock.withLock { - if (!results.isLoaded || results.isEmpty()) { - return@withLock - } - val copiedEvents = ArrayList(results.size) - Timber.v("UnableToDecryptEventEntity updated with ${results.size} results in db") - results.forEach { - // don't use copy from realm over there - val copiedEvent = UnableToDecryptEventEntity(eventId = it.eventId) - copiedEvents.add(copiedEvent) - } - awaitTransaction(realmConfiguration) { realm -> - Timber.v("##Transaction: There are ${copiedEvents.size} events to process ") - copiedEvents.forEach { utdEvent -> - val eventId = utdEvent.eventId - val event = EventEntity.where(realm, eventId).findFirst() - if (event == null) { - Timber.v("Event $eventId not found") - return@forEach - } - val domainEvent = event.asDomain() - processors.forEach { - it.process(realm, domainEvent) - } - } - realm.where(UnableToDecryptEventEntity::class.java) - .`in`(UnableToDecryptEventEntityFields.EVENT_ID, copiedEvents.map { it.eventId }.toTypedArray()) - .findAll() - .deleteAllFromRealm() - } - processors.forEach { it.onPostProcess() } - } - } - } -} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/migration/MigrateSessionTo048.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/migration/MigrateSessionTo048.kt index f9bd8ae7aa..4299054c56 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/migration/MigrateSessionTo048.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/migration/MigrateSessionTo048.kt @@ -18,21 +18,15 @@ package org.matrix.android.sdk.internal.database.migration import io.realm.DynamicRealm import org.matrix.android.sdk.internal.database.model.PollResponseAggregatedSummaryEntityFields -import org.matrix.android.sdk.internal.database.model.UnableToDecryptEventEntityFields import org.matrix.android.sdk.internal.util.database.RealmMigrator /** * Adding a new field in poll summary to keep track of non decrypted related events. - * Adding a new entity UnableToDecryptEventEntity. */ -internal class MigrateSessionTo048(realm: DynamicRealm) : RealmMigrator(realm, 47) { +internal class MigrateSessionTo048(realm: DynamicRealm) : RealmMigrator(realm, 48) { override fun doMigrate(realm: DynamicRealm) { realm.schema.get("PollResponseAggregatedSummaryEntity") ?.addRealmListField(PollResponseAggregatedSummaryEntityFields.ENCRYPTED_RELATED_EVENT_IDS.`$`, String::class.java) - - realm.schema.create("UnableToDecryptEventEntity") - ?.addField(UnableToDecryptEventEntityFields.EVENT_ID, String::class.java) - ?.setRequired(UnableToDecryptEventEntityFields.EVENT_ID, true) } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/EventInsertEntity.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/EventInsertEntity.kt index eff332dc3a..054094c398 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/EventInsertEntity.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/EventInsertEntity.kt @@ -27,7 +27,7 @@ internal open class EventInsertEntity( var eventType: String = "", /** * This flag will be used to filter EventInsertEntity in EventInsertLiveObserver. - * Currently it's set to false when the event content is encrypted. + * Currently it's set to false after an event with encrypted content has been processed. */ var canBeProcessed: Boolean = true ) : RealmObject() { diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/SessionRealmModule.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/SessionRealmModule.kt index 79b0dd699c..93fe1bd1d2 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/SessionRealmModule.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/SessionRealmModule.kt @@ -73,7 +73,6 @@ import org.matrix.android.sdk.internal.database.model.threads.ThreadSummaryEntit UserPresenceEntity::class, ThreadSummaryEntity::class, ThreadListPageEntity::class, - UnableToDecryptEventEntity::class, ] ) internal class SessionRealmModule diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/query/EventEntityQueries.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/query/EventEntityQueries.kt index 0f1c226044..4805c36f8c 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/query/EventEntityQueries.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/query/EventEntityQueries.kt @@ -20,7 +20,6 @@ import io.realm.Realm import io.realm.RealmList import io.realm.RealmQuery import io.realm.kotlin.where -import org.matrix.android.sdk.api.session.events.model.EventType import org.matrix.android.sdk.internal.database.model.EventEntity import org.matrix.android.sdk.internal.database.model.EventEntityFields import org.matrix.android.sdk.internal.database.model.EventInsertEntity @@ -32,10 +31,9 @@ internal fun EventEntity.copyToRealmOrIgnore(realm: Realm, insertType: EventInse .equalTo(EventEntityFields.ROOM_ID, roomId) .findFirst() return if (eventEntity == null) { - val canBeProcessed = type != EventType.ENCRYPTED || decryptionResultJson != null - val insertEntity = EventInsertEntity(eventId = eventId, eventType = type, canBeProcessed = canBeProcessed).apply { - this.insertType = insertType - } + val insertEntity = EventInsertEntity(eventId = eventId, eventType = type, canBeProcessed = true) + insertEntity.insertType = insertType + realm.insert(insertEntity) // copy this event entity and return it realm.copyToRealm(this) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/SessionModule.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/SessionModule.kt index d33112a922..b9f56cbc9f 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/SessionModule.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/SessionModule.kt @@ -50,7 +50,6 @@ import org.matrix.android.sdk.internal.crypto.tasks.RedactEventTask import org.matrix.android.sdk.internal.database.EventInsertLiveObserver import org.matrix.android.sdk.internal.database.RealmSessionProvider import org.matrix.android.sdk.internal.database.SessionRealmConfigurationFactory -import org.matrix.android.sdk.internal.database.UnableToDecryptEventLiveObserver import org.matrix.android.sdk.internal.di.Authenticated import org.matrix.android.sdk.internal.di.CacheDirectory import org.matrix.android.sdk.internal.di.DeviceId @@ -85,7 +84,6 @@ import org.matrix.android.sdk.internal.session.identity.DefaultIdentityService import org.matrix.android.sdk.internal.session.integrationmanager.IntegrationManager import org.matrix.android.sdk.internal.session.openid.DefaultOpenIdService import org.matrix.android.sdk.internal.session.permalinks.DefaultPermalinkService -import org.matrix.android.sdk.internal.session.room.EncryptedEventRelationsAggregationProcessor import org.matrix.android.sdk.internal.session.room.EventRelationsAggregationProcessor import org.matrix.android.sdk.internal.session.room.aggregation.poll.DefaultPollAggregationProcessor import org.matrix.android.sdk.internal.session.room.aggregation.poll.PollAggregationProcessor @@ -348,10 +346,6 @@ internal abstract class SessionModule { @IntoSet abstract fun bindEventInsertObserver(observer: EventInsertLiveObserver): SessionLifecycleObserver - @Binds - @IntoSet - abstract fun bindUnableToDecryptEventObserver(observer: UnableToDecryptEventLiveObserver): SessionLifecycleObserver - @Binds @IntoSet abstract fun bindIntegrationManager(manager: IntegrationManager): SessionLifecycleObserver @@ -411,8 +405,4 @@ internal abstract class SessionModule { @Binds abstract fun bindPollAggregationProcessor(processor: DefaultPollAggregationProcessor): PollAggregationProcessor - - @Binds - @IntoSet - abstract fun bindEncryptedEventRelationsAggregationProcessor(processor: EncryptedEventRelationsAggregationProcessor): UnableToDecryptEventLiveProcessor } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/UnableToDecryptEventLiveProcessor.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/UnableToDecryptEventLiveProcessor.kt deleted file mode 100644 index 8ff8cec6ce..0000000000 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/UnableToDecryptEventLiveProcessor.kt +++ /dev/null @@ -1,39 +0,0 @@ -/* - * Copyright 2022 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 - -import io.realm.Realm -import org.matrix.android.sdk.api.session.events.model.Event - -internal interface UnableToDecryptEventLiveProcessor { - - /** - * Process the given event. - * @param realm a realm instance - * @param event the event to be processed - * @return true if it has been processed, false if it was ignored. - */ - fun process(realm: Realm, event: Event): Boolean - - /** - * Called after transaction. - * Maybe you prefer to process the events outside of the realm transaction. - */ - suspend fun onPostProcess() { - // Noop by default - } -} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/EncryptedEventRelationsAggregationProcessor.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/EncryptedEventRelationsAggregationProcessor.kt deleted file mode 100644 index 1d0270d8e4..0000000000 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/EncryptedEventRelationsAggregationProcessor.kt +++ /dev/null @@ -1,95 +0,0 @@ -/* - * Copyright (c) 2022 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.room - -import io.realm.Realm -import org.matrix.android.sdk.api.session.events.model.Event -import org.matrix.android.sdk.api.session.events.model.EventType -import org.matrix.android.sdk.api.session.events.model.LocalEcho -import org.matrix.android.sdk.api.session.events.model.RelationType -import org.matrix.android.sdk.api.session.events.model.content.EncryptedEventContent -import org.matrix.android.sdk.api.session.events.model.toModel -import org.matrix.android.sdk.internal.session.UnableToDecryptEventLiveProcessor -import org.matrix.android.sdk.internal.session.room.aggregation.utd.EncryptedReferenceAggregationProcessor -import timber.log.Timber -import javax.inject.Inject - -internal class EncryptedEventRelationsAggregationProcessor @Inject constructor( - private val encryptedReferenceAggregationProcessor: EncryptedReferenceAggregationProcessor, -) : UnableToDecryptEventLiveProcessor { - - override fun process(realm: Realm, event: Event): Boolean { - val roomId = event.roomId - return if (roomId == null) { - Timber.w("Event has no room id ${event.eventId}") - false - } else { - val isLocalEcho = LocalEcho.isLocalEchoId(event.eventId ?: "") - - return when (event.getClearType()) { - EventType.ENCRYPTED -> { - val encryptedEventContent = event.content.toModel() - processEncryptedContent( - encryptedEventContent = encryptedEventContent, - realm = realm, - event = event, - roomId = roomId, - isLocalEcho = isLocalEcho, - ) - } - else -> false - } - } - } - - private fun processEncryptedContent( - encryptedEventContent: EncryptedEventContent?, - realm: Realm, - event: Event, - roomId: String, - isLocalEcho: Boolean, - ): Boolean { - return when (encryptedEventContent?.relatesTo?.type) { - RelationType.REPLACE -> { - Timber.w("## UTD replace in room $roomId for event ${event.eventId}") - false - } - RelationType.RESPONSE -> { - // can we / should we do we something for UTD response?? - Timber.w("## UTD response in room $roomId related to ${encryptedEventContent.relatesTo.eventId}") - false - } - RelationType.REFERENCE -> { - // can we / should we do we something for UTD reference?? - Timber.w("## UTD reference in room $roomId related to ${encryptedEventContent.relatesTo.eventId}") - val result = encryptedReferenceAggregationProcessor.handle( - realm = realm, - event = event, - isLocalEcho = isLocalEcho, - relatedEventId = encryptedEventContent.relatesTo.eventId, - ) - result - } - RelationType.ANNOTATION -> { - // can we / should we do we something for UTD annotation?? - Timber.w("## UTD annotation in room $roomId related to ${encryptedEventContent.relatesTo.eventId}") - false - } - else -> false - } - } -} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/EventRelationsAggregationProcessor.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/EventRelationsAggregationProcessor.kt index 0734b286b3..edc10bd187 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/EventRelationsAggregationProcessor.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/EventRelationsAggregationProcessor.kt @@ -23,6 +23,7 @@ import org.matrix.android.sdk.api.session.events.model.Event import org.matrix.android.sdk.api.session.events.model.EventType import org.matrix.android.sdk.api.session.events.model.LocalEcho import org.matrix.android.sdk.api.session.events.model.RelationType +import org.matrix.android.sdk.api.session.events.model.content.EncryptedEventContent import org.matrix.android.sdk.api.session.events.model.getRelationContent import org.matrix.android.sdk.api.session.events.model.toContent import org.matrix.android.sdk.api.session.events.model.toModel @@ -60,6 +61,7 @@ import org.matrix.android.sdk.internal.di.UserId import org.matrix.android.sdk.internal.session.EventInsertLiveProcessor import org.matrix.android.sdk.internal.session.room.aggregation.livelocation.LiveLocationAggregationProcessor import org.matrix.android.sdk.internal.session.room.aggregation.poll.PollAggregationProcessor +import org.matrix.android.sdk.internal.session.room.aggregation.utd.EncryptedReferenceAggregationProcessor import org.matrix.android.sdk.internal.session.room.state.StateEventDataSource import org.matrix.android.sdk.internal.util.time.Clock import timber.log.Timber @@ -72,6 +74,7 @@ internal class EventRelationsAggregationProcessor @Inject constructor( private val sessionManager: SessionManager, private val liveLocationAggregationProcessor: LiveLocationAggregationProcessor, private val pollAggregationProcessor: PollAggregationProcessor, + private val encryptedReferenceAggregationProcessor: EncryptedReferenceAggregationProcessor, private val editValidator: EventEditValidator, private val clock: Clock, ) : EventInsertLiveProcessor { @@ -139,6 +142,16 @@ internal class EventRelationsAggregationProcessor @Inject constructor( Timber.v("###REACTION in room $roomId , reaction eventID ${event.eventId}") handleReaction(realm, event, roomId, isLocalEcho) } + EventType.ENCRYPTED -> { + val encryptedEventContent = event.content.toModel() + processEncryptedContent( + encryptedEventContent = encryptedEventContent, + realm = realm, + event = event, + roomId = roomId, + isLocalEcho = isLocalEcho, + ) + } EventType.MESSAGE -> { if (event.unsignedData?.relations?.annotations != null) { Timber.v("###REACTION Aggregation in room $roomId for event ${event.eventId}") @@ -223,6 +236,36 @@ internal class EventRelationsAggregationProcessor @Inject constructor( } } + private fun processEncryptedContent( + encryptedEventContent: EncryptedEventContent?, + realm: Realm, + event: Event, + roomId: String, + isLocalEcho: Boolean, + ) { + when (encryptedEventContent?.relatesTo?.type) { + RelationType.REPLACE -> { + Timber.w("## UTD replace in room $roomId for event ${event.eventId}") + } + RelationType.RESPONSE -> { + Timber.w("## UTD response in room $roomId related to ${encryptedEventContent.relatesTo.eventId}") + } + RelationType.REFERENCE -> { + Timber.w("## UTD reference in room $roomId related to ${encryptedEventContent.relatesTo.eventId}") + encryptedReferenceAggregationProcessor.handle( + realm = realm, + event = event, + isLocalEcho = isLocalEcho, + relatedEventId = encryptedEventContent.relatesTo.eventId, + ) + } + RelationType.ANNOTATION -> { + Timber.w("## UTD annotation in room $roomId related to ${encryptedEventContent.relatesTo.eventId}") + } + else -> Unit + } + } + // OPT OUT serer aggregation until API mature enough private val SHOULD_HANDLE_SERVER_AGREGGATION = false // should be true to work with e2e diff --git a/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/crypto/tasks/DefaultCreateUnableToDecryptEventEntityTaskTest.kt b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/crypto/tasks/DefaultCreateUnableToDecryptEventEntityTaskTest.kt deleted file mode 100644 index 85032d809c..0000000000 --- a/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/crypto/tasks/DefaultCreateUnableToDecryptEventEntityTaskTest.kt +++ /dev/null @@ -1,65 +0,0 @@ -/* - * Copyright (c) 2022 The Matrix.org Foundation C.I.C. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.matrix.android.sdk.internal.crypto.tasks - -import io.mockk.unmockkAll -import io.mockk.verify -import io.realm.RealmModel -import kotlinx.coroutines.ExperimentalCoroutinesApi -import kotlinx.coroutines.test.runTest -import org.junit.After -import org.junit.Test -import org.matrix.android.sdk.internal.database.model.UnableToDecryptEventEntity -import org.matrix.android.sdk.test.fakes.FakeRealm -import org.matrix.android.sdk.test.fakes.FakeRealmConfiguration - -@OptIn(ExperimentalCoroutinesApi::class) -internal class DefaultCreateUnableToDecryptEventEntityTaskTest { - - private val fakeRealmConfiguration = FakeRealmConfiguration() - - private val defaultCreateUnableToDecryptEventEntityTask = DefaultCreateUnableToDecryptEventEntityTask( - realmConfiguration = fakeRealmConfiguration.instance, - ) - - @After - fun tearDown() { - unmockkAll() - } - - @Test - fun `given an event id when execute then insert entity into database`() = runTest { - // Given - val anEventId = "event-id" - val params = CreateUnableToDecryptEventEntityTask.Params( - eventId = anEventId, - ) - val fakeRealm = FakeRealm() - fakeRealm.givenExecuteTransactionAsync() - fakeRealmConfiguration.givenGetRealmInstance(fakeRealm.instance) - - // When - defaultCreateUnableToDecryptEventEntityTask.execute(params) - - // Then - verify { - fakeRealm.instance.insert(match { - it is UnableToDecryptEventEntity && it.eventId == anEventId - }) - } - } -} diff --git a/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/session/room/EncryptedEventRelationsAggregationProcessorTest.kt b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/session/room/EncryptedEventRelationsAggregationProcessorTest.kt deleted file mode 100644 index 9d68ed1a77..0000000000 --- a/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/session/room/EncryptedEventRelationsAggregationProcessorTest.kt +++ /dev/null @@ -1,209 +0,0 @@ -/* - * Copyright (c) 2022 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.room - -import io.mockk.every -import io.mockk.mockk -import org.amshove.kluent.shouldBeEqualTo -import org.amshove.kluent.shouldBeFalse -import org.junit.Test -import org.matrix.android.sdk.api.session.events.model.Event -import org.matrix.android.sdk.api.session.events.model.EventType -import org.matrix.android.sdk.api.session.events.model.RelationType -import org.matrix.android.sdk.api.session.events.model.content.EncryptedEventContent -import org.matrix.android.sdk.api.session.events.model.toContent -import org.matrix.android.sdk.api.session.room.model.relation.RelationDefaultContent -import org.matrix.android.sdk.test.fakes.FakeRealm -import org.matrix.android.sdk.test.fakes.internal.session.room.aggregation.utd.FakeEncryptedReferenceAggregationProcessor - -class EncryptedEventRelationsAggregationProcessorTest { - - private val fakeEncryptedReferenceAggregationProcessor = FakeEncryptedReferenceAggregationProcessor() - private val fakeRealm = FakeRealm() - - private val encryptedEventRelationsAggregationProcessor = EncryptedEventRelationsAggregationProcessor( - encryptedReferenceAggregationProcessor = fakeEncryptedReferenceAggregationProcessor.instance, - ) - - @Test - fun `given no room Id when process then result is false`() { - // Given - val anEvent = givenAnEvent( - eventId = "event-id", - roomId = null, - eventType = EventType.ENCRYPTED, - ) - - // When - val result = encryptedEventRelationsAggregationProcessor.process( - realm = fakeRealm.instance, - event = anEvent, - ) - - // Then - result.shouldBeFalse() - } - - @Test - fun `given an encrypted reference event when process then reference is processed`() { - // Given - val anEvent = givenAnEvent( - eventId = "event-id", - roomId = "room-id", - eventType = EventType.ENCRYPTED, - ) - val relatedEventId = "related-event-id" - val encryptedEventContent = givenEncryptedEventContent( - relationType = RelationType.REFERENCE, - relatedEventId = relatedEventId, - ) - every { anEvent.content } returns encryptedEventContent.toContent() - val resultOfReferenceProcess = false - fakeEncryptedReferenceAggregationProcessor.givenHandleReturns(resultOfReferenceProcess) - - // When - val result = encryptedEventRelationsAggregationProcessor.process( - realm = fakeRealm.instance, - event = anEvent, - ) - - // Then - result shouldBeEqualTo resultOfReferenceProcess - fakeEncryptedReferenceAggregationProcessor.verifyHandle( - realm = fakeRealm.instance, - event = anEvent, - isLocalEcho = false, - relatedEventId = relatedEventId, - ) - } - - @Test - fun `given an encrypted replace event when process then result is false`() { - // Given - val anEvent = givenAnEvent( - eventId = "event-id", - roomId = "room-id", - eventType = EventType.ENCRYPTED, - ) - val relatedEventId = "related-event-id" - val encryptedEventContent = givenEncryptedEventContent( - relationType = RelationType.REPLACE, - relatedEventId = relatedEventId, - ) - every { anEvent.content } returns encryptedEventContent.toContent() - - // When - val result = encryptedEventRelationsAggregationProcessor.process( - realm = fakeRealm.instance, - event = anEvent, - ) - - // Then - result.shouldBeFalse() - } - - @Test - fun `given an encrypted response event when process then result is false`() { - // Given - val anEvent = givenAnEvent( - eventId = "event-id", - roomId = "room-id", - eventType = EventType.ENCRYPTED, - ) - val relatedEventId = "related-event-id" - val encryptedEventContent = givenEncryptedEventContent( - relationType = RelationType.RESPONSE, - relatedEventId = relatedEventId, - ) - every { anEvent.content } returns encryptedEventContent.toContent() - - // When - val result = encryptedEventRelationsAggregationProcessor.process( - realm = fakeRealm.instance, - event = anEvent, - ) - - // Then - result.shouldBeFalse() - } - - @Test - fun `given an encrypted annotation event when process then result is false`() { - // Given - val anEvent = givenAnEvent( - eventId = "event-id", - roomId = "room-id", - eventType = EventType.ENCRYPTED, - ) - val relatedEventId = "related-event-id" - val encryptedEventContent = givenEncryptedEventContent( - relationType = RelationType.ANNOTATION, - relatedEventId = relatedEventId, - ) - every { anEvent.content } returns encryptedEventContent.toContent() - - // When - val result = encryptedEventRelationsAggregationProcessor.process( - realm = fakeRealm.instance, - event = anEvent, - ) - - // Then - result.shouldBeFalse() - } - - @Test - fun `given a non encrypted event when process then result is false`() { - // Given - val anEvent = givenAnEvent( - eventId = "event-id", - roomId = "room-id", - eventType = EventType.MESSAGE, - ) - - // When - val result = encryptedEventRelationsAggregationProcessor.process( - realm = fakeRealm.instance, - event = anEvent, - ) - - // Then - result.shouldBeFalse() - } - - private fun givenAnEvent( - eventId: String, - roomId: String?, - eventType: String, - ): Event { - return mockk().also { - every { it.eventId } returns eventId - every { it.roomId } returns roomId - every { it.getClearType() } returns eventType - } - } - - private fun givenEncryptedEventContent(relationType: String, relatedEventId: String): EncryptedEventContent { - val relationContent = RelationDefaultContent( - eventId = relatedEventId, - type = relationType, - ) - return EncryptedEventContent( - relatesTo = relationContent, - ) - } -} diff --git a/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/session/room/EventRelationsAggregationProcessorTest.kt b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/session/room/EventRelationsAggregationProcessorTest.kt new file mode 100644 index 0000000000..ff803c4f1a --- /dev/null +++ b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/session/room/EventRelationsAggregationProcessorTest.kt @@ -0,0 +1,132 @@ +/* + * Copyright (c) 2022 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.room + +import io.mockk.every +import io.mockk.mockk +import org.junit.Test +import org.matrix.android.sdk.api.session.events.model.Event +import org.matrix.android.sdk.api.session.events.model.EventType +import org.matrix.android.sdk.api.session.events.model.RelationType +import org.matrix.android.sdk.api.session.events.model.content.EncryptedEventContent +import org.matrix.android.sdk.api.session.events.model.toContent +import org.matrix.android.sdk.api.session.room.model.relation.RelationDefaultContent +import org.matrix.android.sdk.internal.database.model.EventAnnotationsSummaryEntity +import org.matrix.android.sdk.internal.database.model.EventAnnotationsSummaryEntityFields +import org.matrix.android.sdk.test.fakes.FakeClock +import org.matrix.android.sdk.test.fakes.FakeRealm +import org.matrix.android.sdk.test.fakes.FakeStateEventDataSource +import org.matrix.android.sdk.test.fakes.givenEqualTo +import org.matrix.android.sdk.test.fakes.givenFindFirst +import org.matrix.android.sdk.test.fakes.internal.FakeEventEditValidator +import org.matrix.android.sdk.test.fakes.internal.FakeLiveLocationAggregationProcessor +import org.matrix.android.sdk.test.fakes.internal.FakePollAggregationProcessor +import org.matrix.android.sdk.test.fakes.internal.FakeSessionManager +import org.matrix.android.sdk.test.fakes.internal.session.room.aggregation.utd.FakeEncryptedReferenceAggregationProcessor + +private const val A_ROOM_ID = "room-id" +private const val AN_EVENT_ID = "event-id" + +internal class EventRelationsAggregationProcessorTest { + + private val fakeStateEventDataSource = FakeStateEventDataSource() + private val fakeSessionManager = FakeSessionManager() + private val fakeLiveLocationAggregationProcessor = FakeLiveLocationAggregationProcessor() + private val fakePollAggregationProcessor = FakePollAggregationProcessor() + private val fakeEncryptedReferenceAggregationProcessor = FakeEncryptedReferenceAggregationProcessor() + private val fakeEventEditValidator = FakeEventEditValidator() + private val fakeClock = FakeClock() + private val fakeRealm = FakeRealm() + + private val encryptedEventRelationsAggregationProcessor = EventRelationsAggregationProcessor( + userId = "userId", + stateEventDataSource = fakeStateEventDataSource.instance, + sessionId = "sessionId", + sessionManager = fakeSessionManager.instance, + liveLocationAggregationProcessor = fakeLiveLocationAggregationProcessor.instance, + pollAggregationProcessor = fakePollAggregationProcessor.instance, + encryptedReferenceAggregationProcessor = fakeEncryptedReferenceAggregationProcessor.instance, + editValidator = fakeEventEditValidator.instance, + clock = fakeClock, + ) + + @Test + fun `given an encrypted reference event when process then reference is processed`() { + // Given + val anEvent = givenAnEvent( + eventId = AN_EVENT_ID, + roomId = A_ROOM_ID, + eventType = EventType.ENCRYPTED, + ) + val relatedEventId = "related-event-id" + val encryptedEventContent = givenEncryptedEventContent( + relationType = RelationType.REFERENCE, + relatedEventId = relatedEventId, + ) + every { anEvent.content } returns encryptedEventContent.toContent() + val resultOfReferenceProcess = false + fakeEncryptedReferenceAggregationProcessor.givenHandleReturns(resultOfReferenceProcess) + givenEventAnnotationsSummary(roomId = A_ROOM_ID, eventId = AN_EVENT_ID, annotationsSummary = null) + + // When + encryptedEventRelationsAggregationProcessor.process( + realm = fakeRealm.instance, + event = anEvent, + ) + + // Then + fakeEncryptedReferenceAggregationProcessor.verifyHandle( + realm = fakeRealm.instance, + event = anEvent, + isLocalEcho = false, + relatedEventId = relatedEventId, + ) + } + + private fun givenAnEvent( + eventId: String, + roomId: String?, + eventType: String, + ): Event { + return mockk().also { + every { it.eventId } returns eventId + every { it.roomId } returns roomId + every { it.getClearType() } returns eventType + } + } + + private fun givenEncryptedEventContent(relationType: String, relatedEventId: String): EncryptedEventContent { + val relationContent = RelationDefaultContent( + eventId = relatedEventId, + type = relationType, + ) + return EncryptedEventContent( + relatesTo = relationContent, + ) + } + + private fun givenEventAnnotationsSummary( + roomId: String, + eventId: String, + annotationsSummary: EventAnnotationsSummaryEntity? + ) { + fakeRealm.givenWhere() + .givenEqualTo(EventAnnotationsSummaryEntityFields.ROOM_ID, roomId) + .givenEqualTo(EventAnnotationsSummaryEntityFields.EVENT_ID, eventId) + .givenFindFirst(annotationsSummary) + } +} diff --git a/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fakes/FakeRealm.kt b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fakes/FakeRealm.kt index 1f9bc2a976..49d64c1835 100644 --- a/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fakes/FakeRealm.kt +++ b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fakes/FakeRealm.kt @@ -23,7 +23,6 @@ import io.mockk.mockk import io.mockk.runs import io.mockk.verify import io.realm.Realm -import io.realm.Realm.Transaction import io.realm.RealmModel import io.realm.RealmObject import io.realm.RealmQuery @@ -43,13 +42,6 @@ internal class FakeRealm { inline fun verifyInsertOrUpdate(crossinline verification: MockKVerificationScope.() -> T) { verify { instance.insertOrUpdate(verification()) } } - - fun givenExecuteTransactionAsync() { - every { instance.executeTransactionAsync(any()) } answers { - firstArg().execute(instance) - mockk() - } - } } inline fun RealmQuery.givenFindFirst( diff --git a/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fakes/FakeRealmConfiguration.kt b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fakes/FakeRealmConfiguration.kt index 3a69515140..9ad7032262 100644 --- a/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fakes/FakeRealmConfiguration.kt +++ b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fakes/FakeRealmConfiguration.kt @@ -17,7 +17,6 @@ package org.matrix.android.sdk.test.fakes import io.mockk.coEvery -import io.mockk.every import io.mockk.mockk import io.mockk.mockkStatic import io.realm.Realm @@ -37,9 +36,4 @@ internal class FakeRealmConfiguration { secondArg<(Realm) -> T>().invoke(realm) } } - - fun givenGetRealmInstance(realm: Realm) { - mockkStatic(Realm::class) - every { Realm.getInstance(instance) } returns realm - } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/UnableToDecryptEventEntity.kt b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fakes/internal/FakeEventEditValidator.kt similarity index 57% rename from matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/UnableToDecryptEventEntity.kt rename to matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fakes/internal/FakeEventEditValidator.kt index 6393877d38..2fa36cf60d 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/UnableToDecryptEventEntity.kt +++ b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fakes/internal/FakeEventEditValidator.kt @@ -1,5 +1,5 @@ /* - * Copyright (c) 2022 The Matrix.org Foundation C.I.C. + * Copyright (c) 2023 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. @@ -14,14 +14,12 @@ * limitations under the License. */ -package org.matrix.android.sdk.internal.database.model +package org.matrix.android.sdk.test.fakes.internal -import io.realm.RealmObject +import io.mockk.mockk +import org.matrix.android.sdk.internal.session.room.EventEditValidator -/** - * This class is used to get notification on new UTD events. Since these events cannot be processed - * in EventInsertEntity, we should introduce a dedicated entity for that. - */ -internal open class UnableToDecryptEventEntity( - var eventId: String = "", -) : RealmObject() +internal class FakeEventEditValidator { + + val instance: EventEditValidator = mockk() +} diff --git a/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fakes/internal/FakeLiveLocationAggregationProcessor.kt b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fakes/internal/FakeLiveLocationAggregationProcessor.kt new file mode 100644 index 0000000000..6385110963 --- /dev/null +++ b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fakes/internal/FakeLiveLocationAggregationProcessor.kt @@ -0,0 +1,25 @@ +/* + * Copyright (c) 2023 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.test.fakes.internal + +import io.mockk.mockk +import org.matrix.android.sdk.internal.session.room.aggregation.livelocation.LiveLocationAggregationProcessor + +internal class FakeLiveLocationAggregationProcessor { + + val instance: LiveLocationAggregationProcessor = mockk() +} diff --git a/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fakes/internal/FakePollAggregationProcessor.kt b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fakes/internal/FakePollAggregationProcessor.kt new file mode 100644 index 0000000000..5187c785ca --- /dev/null +++ b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fakes/internal/FakePollAggregationProcessor.kt @@ -0,0 +1,25 @@ +/* + * Copyright (c) 2023 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.test.fakes.internal + +import io.mockk.mockk +import org.matrix.android.sdk.internal.session.room.aggregation.poll.PollAggregationProcessor + +internal class FakePollAggregationProcessor { + + val instance: PollAggregationProcessor = mockk() +} From 94dd599f137906d611d51d6cc02646de454eb833 Mon Sep 17 00:00:00 2001 From: Maxime NATUREL <46314705+mnaturel@users.noreply.github.com> Date: Thu, 12 Jan 2023 10:03:01 +0100 Subject: [PATCH 17/40] Fix after rebase --- .../room/detail/timeline/helper/PollResponseDataFactory.kt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/PollResponseDataFactory.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/PollResponseDataFactory.kt index 533397b4d8..0ef60da6de 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/PollResponseDataFactory.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/PollResponseDataFactory.kt @@ -44,7 +44,8 @@ class PollResponseDataFactory @Inject constructor( ) }, winnerVoteCount = it.aggregatedContent?.winnerVoteCount ?: 0, - totalVotes = it.aggregatedContent?.totalVotes ?: 0 + totalVotes = it.aggregatedContent?.totalVotes ?: 0, + hasDecryptionError = it.encryptedRelatedEventIds.isNotEmpty(), ) } } From d1ce15bf18ea4119ece633dc4b42cf08cc5a3316 Mon Sep 17 00:00:00 2001 From: Maxime NATUREL <46314705+mnaturel@users.noreply.github.com> Date: Thu, 12 Jan 2023 10:14:47 +0100 Subject: [PATCH 18/40] Renaming field in PollResponseData for better clarity --- .../room/detail/timeline/factory/PollItemViewStateFactory.kt | 4 ++-- .../room/detail/timeline/helper/PollResponseDataFactory.kt | 2 +- .../home/room/detail/timeline/item/MessageInformationData.kt | 2 +- .../detail/timeline/factory/PollItemViewStateFactoryTest.kt | 4 ++-- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/PollItemViewStateFactory.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/PollItemViewStateFactory.kt index 28c560e161..7abc51fa51 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/PollItemViewStateFactory.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/PollItemViewStateFactory.kt @@ -83,7 +83,7 @@ class PollItemViewStateFactory @Inject constructor( totalVotes: Int, winnerVoteCount: Int?, ): PollViewState { - val totalVotesText = if (pollResponseSummary?.hasDecryptionError.orFalse()) { + val totalVotesText = if (pollResponseSummary?.hasEncryptedRelatedEvents.orFalse()) { stringProvider.getString(R.string.unable_to_decrypt_some_events_in_poll) } else { stringProvider.getQuantityString(R.plurals.poll_total_vote_count_after_ended, totalVotes, totalVotes) @@ -131,7 +131,7 @@ class PollItemViewStateFactory @Inject constructor( pollResponseSummary: PollResponseData?, totalVotes: Int ): PollViewState { - val totalVotesText = if (pollResponseSummary?.hasDecryptionError.orFalse()) { + val totalVotesText = if (pollResponseSummary?.hasEncryptedRelatedEvents.orFalse()) { stringProvider.getString(R.string.unable_to_decrypt_some_events_in_poll) } else { stringProvider.getQuantityString(R.plurals.poll_total_vote_count_before_ended_and_voted, totalVotes, totalVotes) diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/PollResponseDataFactory.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/PollResponseDataFactory.kt index 0ef60da6de..8f81adcd32 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/PollResponseDataFactory.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/PollResponseDataFactory.kt @@ -45,7 +45,7 @@ class PollResponseDataFactory @Inject constructor( }, winnerVoteCount = it.aggregatedContent?.winnerVoteCount ?: 0, totalVotes = it.aggregatedContent?.totalVotes ?: 0, - hasDecryptionError = it.encryptedRelatedEventIds.isNotEmpty(), + hasEncryptedRelatedEvents = it.encryptedRelatedEventIds.isNotEmpty(), ) } } diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageInformationData.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageInformationData.kt index 4bdfb948ce..a1a214785e 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageInformationData.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageInformationData.kt @@ -91,7 +91,7 @@ data class PollResponseData( val totalVotes: Int = 0, val winnerVoteCount: Int = 0, val isClosed: Boolean = false, - val hasDecryptionError: Boolean = false, + val hasEncryptedRelatedEvents: Boolean = false, ) : Parcelable { fun getVoteSummaryOfAnOption(optionId: String) = votes?.get(optionId) diff --git a/vector/src/test/java/im/vector/app/features/home/room/detail/timeline/factory/PollItemViewStateFactoryTest.kt b/vector/src/test/java/im/vector/app/features/home/room/detail/timeline/factory/PollItemViewStateFactoryTest.kt index 4d14458e7f..8ee55d5b6e 100644 --- a/vector/src/test/java/im/vector/app/features/home/room/detail/timeline/factory/PollItemViewStateFactoryTest.kt +++ b/vector/src/test/java/im/vector/app/features/home/room/detail/timeline/factory/PollItemViewStateFactoryTest.kt @@ -136,7 +136,7 @@ class PollItemViewStateFactoryTest { // Given val stringProvider = FakeStringProvider() val pollItemViewStateFactory = PollItemViewStateFactory(stringProvider.instance) - val closedPollSummary = A_POLL_RESPONSE_DATA.copy(isClosed = true, hasDecryptionError = true) + val closedPollSummary = A_POLL_RESPONSE_DATA.copy(isClosed = true, hasEncryptedRelatedEvents = true) val closedPollInformationData = A_MESSAGE_INFORMATION_DATA.copy(pollResponseAggregatedSummary = closedPollSummary) // When @@ -220,7 +220,7 @@ class PollItemViewStateFactoryTest { totalVotes = 1, myVote = A_POLL_OPTION_IDS[0], votes = mapOf(A_POLL_OPTION_IDS[0] to PollVoteSummaryData(total = 1, percentage = 1.0)), - hasDecryptionError = true, + hasEncryptedRelatedEvents = true, ) val disclosedPollContent = A_POLL_CONTENT.copy( unstablePollCreationInfo = A_POLL_CONTENT.getBestPollCreationInfo()?.copy( From ee3cbd988481d0d071726831c720389fd60c176a Mon Sep 17 00:00:00 2001 From: Maxime NATUREL <46314705+mnaturel@users.noreply.github.com> Date: Thu, 12 Jan 2023 11:15:38 +0100 Subject: [PATCH 19/40] Filter in only encrypted events with relatesTo content --- .../database/EventInsertLiveObserver.kt | 36 ++++++++++++++----- 1 file changed, 27 insertions(+), 9 deletions(-) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/EventInsertLiveObserver.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/EventInsertLiveObserver.kt index 84fb14c914..2cae6c09ed 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/EventInsertLiveObserver.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/EventInsertLiveObserver.kt @@ -17,12 +17,16 @@ package org.matrix.android.sdk.internal.database import com.zhuinden.monarchy.Monarchy +import io.realm.Realm import io.realm.RealmConfiguration import io.realm.RealmResults import kotlinx.coroutines.launch import kotlinx.coroutines.sync.Mutex import kotlinx.coroutines.sync.withLock +import org.matrix.android.sdk.api.session.events.model.Event import org.matrix.android.sdk.api.session.events.model.EventType +import org.matrix.android.sdk.api.session.events.model.content.EncryptedEventContent +import org.matrix.android.sdk.api.session.events.model.toModel import org.matrix.android.sdk.internal.database.mapper.asDomain import org.matrix.android.sdk.internal.database.model.EventEntity import org.matrix.android.sdk.internal.database.model.EventInsertEntity @@ -76,17 +80,17 @@ internal class EventInsertLiveObserver @Inject constructor( Timber.v("##Transaction: There are ${filteredEvents.size} events to process ") filteredEvents.forEach { eventInsert -> val eventId = eventInsert.eventId - val event = EventEntity.where(realm, eventId).findFirst() - if (event == null) { - Timber.v("Event $eventId not found") + val event = getEvent(realm, eventId) + if (event != null && canProcessEvent(event)) { + processors.filter { + it.shouldProcess(eventId, event.getClearType(), eventInsert.insertType) + }.forEach { + it.process(realm, event) + } + } else { + Timber.v("Cannot process event with id $eventId") return@forEach } - val domainEvent = event.asDomain() - processors.filter { - it.shouldProcess(eventId, domainEvent.getClearType(), eventInsert.insertType) - }.forEach { - it.process(realm, domainEvent) - } } realm.where(EventInsertEntity::class.java) .`in`(EventInsertEntityFields.EVENT_ID, idsToDeleteAfterProcess.toTypedArray()) @@ -104,6 +108,20 @@ internal class EventInsertLiveObserver @Inject constructor( } } + private fun getEvent(realm: Realm, eventId: String): Event? { + val event = EventEntity.where(realm, eventId).findFirst() + if (event == null) { + Timber.v("Event $eventId not found") + } + return event?.asDomain() + } + + private fun canProcessEvent(event: Event): Boolean { + // event should be either not encrypted or if encrypted it should contain relatesTo content + return event.getClearType() != EventType.ENCRYPTED || + event.content.toModel()?.relatesTo != null + } + private fun shouldProcess(eventInsertEntity: EventInsertEntity): Boolean { return processors.any { it.shouldProcess(eventInsertEntity.eventId, eventInsertEntity.eventType, eventInsertEntity.insertType) From cad5e732dfb6af7831017bdfb44792138da0521a Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Fri, 13 Jan 2023 10:36:03 +0100 Subject: [PATCH 20/40] Fix issue of send button not displayed when starting message with a space. --- .../home/room/detail/composer/MessageComposerViewModel.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/composer/MessageComposerViewModel.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/composer/MessageComposerViewModel.kt index c02eb1fa8a..56ee9ffb5a 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/composer/MessageComposerViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/composer/MessageComposerViewModel.kt @@ -138,7 +138,7 @@ class MessageComposerViewModel @AssistedInject constructor( } private fun handleOnTextChanged(action: MessageComposerAction.OnTextChanged) { - val needsSendButtonVisibilityUpdate = currentComposerText.isEmpty() != action.text.isEmpty() + val needsSendButtonVisibilityUpdate = currentComposerText.isBlank() != action.text.isBlank() currentComposerText = SpannableString(action.text) if (needsSendButtonVisibilityUpdate) { updateIsSendButtonVisibility(true) From 06ac22488e7f2d93d0c608ff8bbee632dfe3129f Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Fri, 13 Jan 2023 10:45:09 +0100 Subject: [PATCH 21/40] Fix typo. --- library/ui-strings/src/main/res/values/strings.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/ui-strings/src/main/res/values/strings.xml b/library/ui-strings/src/main/res/values/strings.xml index f1c2eb2c6d..405dae0e16 100644 --- a/library/ui-strings/src/main/res/values/strings.xml +++ b/library/ui-strings/src/main/res/values/strings.xml @@ -794,7 +794,7 @@ Shows all threads you’ve participated in Keep discussions organized with threads Threads help keep your conversations on-topic and easy to track. - You\'re homeserver does not support listing threads yet. + Your homeserver does not support listing threads yet. Tip: Long tap a message and use “%s”. From a Thread From 60e838a82c4d82b26e1141f79cb7028bab50a7c6 Mon Sep 17 00:00:00 2001 From: Maxime NATUREL <46314705+mnaturel@users.noreply.github.com> Date: Fri, 13 Jan 2023 16:48:53 +0100 Subject: [PATCH 22/40] Check encrypted event status using the Event model --- .../database/EventInsertLiveObserver.kt | 51 ++++++++++++------- 1 file changed, 34 insertions(+), 17 deletions(-) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/EventInsertLiveObserver.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/EventInsertLiveObserver.kt index 2cae6c09ed..a3f38cf2c6 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/EventInsertLiveObserver.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/EventInsertLiveObserver.kt @@ -55,32 +55,46 @@ internal class EventInsertLiveObserver @Inject constructor( if (!results.isLoaded || results.isEmpty()) { return@withLock } - val idsToDeleteAfterProcess = ArrayList() - val idsOfEncryptedEvents = ArrayList() - val filteredEvents = ArrayList(results.size) + val eventsToProcess = ArrayList(results.size) + val eventsToIgnore = ArrayList(results.size) + Timber.v("EventInsertEntity updated with ${results.size} results in db") results.forEach { - if (shouldProcess(it)) { - // don't use copy from realm over there - val copiedEvent = EventInsertEntity( - eventId = it.eventId, - eventType = it.eventType - ).apply { - insertType = it.insertType - } - filteredEvents.add(copiedEvent) + // don't use copy from realm over there + val copiedEvent = EventInsertEntity( + eventId = it.eventId, + eventType = it.eventType + ).apply { + insertType = it.insertType } - if (it.eventType == EventType.ENCRYPTED) { - idsOfEncryptedEvents.add(it.eventId) + + if (shouldProcess(it)) { + eventsToProcess.add(copiedEvent) } else { - idsToDeleteAfterProcess.add(it.eventId) + eventsToIgnore.add(copiedEvent) } } + awaitTransaction(realmConfiguration) { realm -> - Timber.v("##Transaction: There are ${filteredEvents.size} events to process ") - filteredEvents.forEach { eventInsert -> + Timber.v("##Transaction: There are ${eventsToProcess.size} events to process") + + val idsToDeleteAfterProcess = ArrayList() + val idsOfEncryptedEvents = ArrayList() + val getAndTriageEvent: (EventInsertEntity) -> Event? = { eventInsert -> val eventId = eventInsert.eventId val event = getEvent(realm, eventId) + if (event?.getClearType() == EventType.ENCRYPTED) { + idsOfEncryptedEvents.add(eventId) + } else { + idsToDeleteAfterProcess.add(eventId) + } + event + } + + eventsToProcess.forEach { eventInsert -> + val eventId = eventInsert.eventId + val event = getAndTriageEvent(eventInsert) + if (event != null && canProcessEvent(event)) { processors.filter { it.shouldProcess(eventId, event.getClearType(), eventInsert.insertType) @@ -92,6 +106,9 @@ internal class EventInsertLiveObserver @Inject constructor( return@forEach } } + + eventsToIgnore.forEach { getAndTriageEvent(it) } + realm.where(EventInsertEntity::class.java) .`in`(EventInsertEntityFields.EVENT_ID, idsToDeleteAfterProcess.toTypedArray()) .findAll() From a586e346dedad6e48c7780a0b120cc0bcab361c0 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Mon, 16 Jan 2023 13:52:15 +0100 Subject: [PATCH 23/40] Fix an issue on Breadcrumbs in dark theme on the draft indicator --- vector/src/main/res/layout/item_breadcrumbs.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/vector/src/main/res/layout/item_breadcrumbs.xml b/vector/src/main/res/layout/item_breadcrumbs.xml index 7584d6f9d5..83624036d9 100644 --- a/vector/src/main/res/layout/item_breadcrumbs.xml +++ b/vector/src/main/res/layout/item_breadcrumbs.xml @@ -85,6 +85,7 @@ app:layout_constraintCircle="@id/breadcrumbsImageView" app:layout_constraintCircleAngle="225" app:layout_constraintCircleRadius="28dp" + app:tint="?vctr_content_primary" tools:ignore="MissingConstraints" tools:visibility="visible" /> From a8d2f40475616269f4669e74589803510b027055 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 16 Jan 2023 23:02:58 +0000 Subject: [PATCH 24/40] Bump flipper from 0.176.1 to 0.177.0 Bumps `flipper` from 0.176.1 to 0.177.0. Updates `flipper` from 0.176.1 to 0.177.0 - [Release notes](https://github.com/facebook/flipper/releases) - [Commits](https://github.com/facebook/flipper/compare/v0.176.1...v0.177.0) Updates `flipper-network-plugin` from 0.176.1 to 0.177.0 - [Release notes](https://github.com/facebook/flipper/releases) - [Commits](https://github.com/facebook/flipper/compare/v0.176.1...v0.177.0) --- updated-dependencies: - dependency-name: com.facebook.flipper:flipper dependency-type: direct:production update-type: version-update:semver-minor - dependency-name: com.facebook.flipper:flipper-network-plugin dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- dependencies.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dependencies.gradle b/dependencies.gradle index f6be35edc0..ff94b18943 100644 --- a/dependencies.gradle +++ b/dependencies.gradle @@ -18,7 +18,7 @@ def markwon = "4.6.2" def moshi = "1.14.0" def lifecycle = "2.5.1" def flowBinding = "1.2.0" -def flipper = "0.176.1" +def flipper = "0.177.0" def epoxy = "5.0.0" def mavericks = "3.0.1" def glide = "4.14.2" From bc4f1f1ec054c382e9f6355ec7b4279105ee046e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 16 Jan 2023 23:03:48 +0000 Subject: [PATCH 25/40] Bump wysiwyg from 0.17.0 to 0.18.0 Bumps [wysiwyg](https://github.com/matrix-org/matrix-wysiwyg) from 0.17.0 to 0.18.0. - [Release notes](https://github.com/matrix-org/matrix-wysiwyg/releases) - [Changelog](https://github.com/matrix-org/matrix-rich-text-editor/blob/main/CHANGELOG.md) - [Commits](https://github.com/matrix-org/matrix-wysiwyg/compare/0.17.0...0.18.0) --- updated-dependencies: - dependency-name: io.element.android:wysiwyg dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- dependencies.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dependencies.gradle b/dependencies.gradle index f6be35edc0..03873f8d25 100644 --- a/dependencies.gradle +++ b/dependencies.gradle @@ -103,7 +103,7 @@ ext.libs = [ ], element : [ 'opusencoder' : "io.element.android:opusencoder:1.1.0", - 'wysiwyg' : "io.element.android:wysiwyg:0.17.0" + 'wysiwyg' : "io.element.android:wysiwyg:0.18.0" ], squareup : [ 'moshi' : "com.squareup.moshi:moshi:$moshi", From 479b573dbbb50467443b49e1acde79910e40897e Mon Sep 17 00:00:00 2001 From: Maxime NATUREL <46314705+mnaturel@users.noreply.github.com> Date: Wed, 11 Jan 2023 11:08:32 +0100 Subject: [PATCH 26/40] Adding changelog entry --- changelog.d/7864.wip | 1 + 1 file changed, 1 insertion(+) create mode 100644 changelog.d/7864.wip diff --git a/changelog.d/7864.wip b/changelog.d/7864.wip new file mode 100644 index 0000000000..703318ba2e --- /dev/null +++ b/changelog.d/7864.wip @@ -0,0 +1 @@ +[Poll] Load more UI mechanism From e8e94b51896d54ef0bf94160f93940a8c1ed6890 Mon Sep 17 00:00:00 2001 From: Maxime NATUREL <46314705+mnaturel@users.noreply.github.com> Date: Wed, 11 Jan 2023 14:41:25 +0100 Subject: [PATCH 27/40] Adding load more item at the end of the list of polls --- .../src/main/res/values/strings.xml | 1 + .../polls/list/RoomPollLoadMoreItem.kt | 42 +++++++++++++++++++ .../polls/list/RoomPollsController.kt | 11 +++++ .../polls/list/RoomPollsListFragment.kt | 6 +++ .../main/res/layout/item_poll_load_more.xml | 29 +++++++++++++ 5 files changed, 89 insertions(+) create mode 100644 vector/src/main/java/im/vector/app/features/roomprofile/polls/list/RoomPollLoadMoreItem.kt create mode 100644 vector/src/main/res/layout/item_poll_load_more.xml diff --git a/library/ui-strings/src/main/res/values/strings.xml b/library/ui-strings/src/main/res/values/strings.xml index 9fd121b3f7..f6e2491674 100644 --- a/library/ui-strings/src/main/res/values/strings.xml +++ b/library/ui-strings/src/main/res/values/strings.xml @@ -3201,6 +3201,7 @@ There are no active polls in this room Past polls There are no past polls in this room + Load more polls Share location diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/polls/list/RoomPollLoadMoreItem.kt b/vector/src/main/java/im/vector/app/features/roomprofile/polls/list/RoomPollLoadMoreItem.kt new file mode 100644 index 0000000000..a684fa5c9e --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/roomprofile/polls/list/RoomPollLoadMoreItem.kt @@ -0,0 +1,42 @@ +/* + * Copyright (c) 2022 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package im.vector.app.features.roomprofile.polls.list + +import android.widget.Button +import com.airbnb.epoxy.EpoxyAttribute +import com.airbnb.epoxy.EpoxyModelClass +import im.vector.app.R +import im.vector.app.core.epoxy.ClickListener +import im.vector.app.core.epoxy.VectorEpoxyHolder +import im.vector.app.core.epoxy.VectorEpoxyModel +import im.vector.app.core.epoxy.onClick + +@EpoxyModelClass +abstract class RoomPollLoadMoreItem : VectorEpoxyModel(R.layout.item_poll_load_more) { + + @EpoxyAttribute(EpoxyAttribute.Option.DoNotHash) + var clickListener: ClickListener? = null + + override fun bind(holder: Holder) { + super.bind(holder) + holder.loadMoreButton.onClick(clickListener) + } + + class Holder : VectorEpoxyHolder() { + val loadMoreButton by bind