From 72896f1c8a3c83c5452ab2d9ba1f14612a3a42c3 Mon Sep 17 00:00:00 2001 From: Florian Renaud Date: Fri, 20 May 2022 18:22:19 +0200 Subject: [PATCH 01/20] Create the DM when sending an event --- changelog.d/5525.wip | 1 + .../internal/crypto/tasks/SendEventTask.kt | 14 + .../sdk/internal/session/room/RoomModule.kt | 5 + .../create/CreateRoomFromLocalRoomTask.kt | 252 ++++++++++++++++++ .../session/room/state/SendStateTask.kt | 46 ++-- .../home/room/detail/TimelineViewModel.kt | 18 +- 6 files changed, 319 insertions(+), 17 deletions(-) create mode 100644 changelog.d/5525.wip create mode 100644 matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/create/CreateRoomFromLocalRoomTask.kt diff --git a/changelog.d/5525.wip b/changelog.d/5525.wip new file mode 100644 index 0000000000..0d54c06b6a --- /dev/null +++ b/changelog.d/5525.wip @@ -0,0 +1 @@ +Create DM room only on first message - Create the DM and navigate to the new room after sending an event diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/tasks/SendEventTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/tasks/SendEventTask.kt index bb14b417dd..f51a5165c0 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/tasks/SendEventTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/tasks/SendEventTask.kt @@ -16,10 +16,12 @@ package org.matrix.android.sdk.internal.crypto.tasks import org.matrix.android.sdk.api.session.events.model.Event +import org.matrix.android.sdk.api.session.room.model.localecho.RoomLocalEcho import org.matrix.android.sdk.api.session.room.send.SendState import org.matrix.android.sdk.internal.network.GlobalErrorReceiver import org.matrix.android.sdk.internal.network.executeRequest import org.matrix.android.sdk.internal.session.room.RoomAPI +import org.matrix.android.sdk.internal.session.room.create.CreateRoomFromLocalRoomTask import org.matrix.android.sdk.internal.session.room.membership.LoadRoomMembersTask import org.matrix.android.sdk.internal.session.room.send.LocalEchoRepository import org.matrix.android.sdk.internal.task.Task @@ -37,12 +39,18 @@ internal class DefaultSendEventTask @Inject constructor( private val localEchoRepository: LocalEchoRepository, private val encryptEventTask: EncryptEventTask, private val loadRoomMembersTask: LoadRoomMembersTask, + private val createRoomFromLocalRoomTask: CreateRoomFromLocalRoomTask, private val roomAPI: RoomAPI, private val globalErrorReceiver: GlobalErrorReceiver ) : SendEventTask { override suspend fun execute(params: SendEventTask.Params): String { try { + if (RoomLocalEcho.isLocalEchoId(params.event.roomId.orEmpty())) { + // Room is local, so create a real one and send the event to this new room + return createRoomAndSendEvent(params) + } + // Make sure to load all members in the room before sending the event. params.event.roomId ?.takeIf { params.encrypt } @@ -78,6 +86,12 @@ internal class DefaultSendEventTask @Inject constructor( } } + private suspend fun createRoomAndSendEvent(params: SendEventTask.Params): String { + val roomId = createRoomFromLocalRoomTask.execute(CreateRoomFromLocalRoomTask.Params(params.event.roomId.orEmpty())) + Timber.d("State event: convert local room (${params.event.roomId}) to existing room ($roomId) before sending the event.") + return execute(params.copy(event = params.event.copy(roomId = roomId))) + } + @Throws private suspend fun handleEncryption(params: SendEventTask.Params): Event { if (params.encrypt && !params.event.isEncrypted()) { diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/RoomModule.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/RoomModule.kt index d01324a35f..218ce0a4d5 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/RoomModule.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/RoomModule.kt @@ -44,8 +44,10 @@ import org.matrix.android.sdk.internal.session.room.alias.DeleteRoomAliasTask import org.matrix.android.sdk.internal.session.room.alias.GetRoomIdByAliasTask import org.matrix.android.sdk.internal.session.room.alias.GetRoomLocalAliasesTask import org.matrix.android.sdk.internal.session.room.create.CreateLocalRoomTask +import org.matrix.android.sdk.internal.session.room.create.CreateRoomFromLocalRoomTask import org.matrix.android.sdk.internal.session.room.create.CreateRoomTask import org.matrix.android.sdk.internal.session.room.create.DefaultCreateLocalRoomTask +import org.matrix.android.sdk.internal.session.room.create.DefaultCreateRoomFromLocalRoomTask import org.matrix.android.sdk.internal.session.room.create.DefaultCreateRoomTask import org.matrix.android.sdk.internal.session.room.delete.DefaultDeleteLocalRoomTask import org.matrix.android.sdk.internal.session.room.delete.DeleteLocalRoomTask @@ -213,6 +215,9 @@ internal abstract class RoomModule { @Binds abstract fun bindCreateLocalRoomTask(task: DefaultCreateLocalRoomTask): CreateLocalRoomTask + @Binds + abstract fun bindCreateRoomFromLocalRoomTask(task: DefaultCreateRoomFromLocalRoomTask): CreateRoomFromLocalRoomTask + @Binds abstract fun bindDeleteLocalRoomTask(task: DefaultDeleteLocalRoomTask): DeleteLocalRoomTask diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/create/CreateRoomFromLocalRoomTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/create/CreateRoomFromLocalRoomTask.kt new file mode 100644 index 0000000000..516220d452 --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/create/CreateRoomFromLocalRoomTask.kt @@ -0,0 +1,252 @@ +/* + * 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.room.create + +import android.util.Patterns +import androidx.core.net.toUri +import com.google.i18n.phonenumbers.NumberParseException +import com.google.i18n.phonenumbers.PhoneNumberUtil +import com.zhuinden.monarchy.Monarchy +import io.realm.Realm +import org.matrix.android.sdk.api.extensions.ensurePrefix +import org.matrix.android.sdk.api.query.QueryStringValue +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.toContent +import org.matrix.android.sdk.api.session.events.model.toModel +import org.matrix.android.sdk.api.session.identity.ThreePid +import org.matrix.android.sdk.api.session.room.model.PowerLevelsContent +import org.matrix.android.sdk.api.session.room.model.RoomAliasesContent +import org.matrix.android.sdk.api.session.room.model.RoomAvatarContent +import org.matrix.android.sdk.api.session.room.model.RoomCanonicalAliasContent +import org.matrix.android.sdk.api.session.room.model.RoomGuestAccessContent +import org.matrix.android.sdk.api.session.room.model.RoomHistoryVisibilityContent +import org.matrix.android.sdk.api.session.room.model.RoomJoinRules +import org.matrix.android.sdk.api.session.room.model.RoomJoinRulesContent +import org.matrix.android.sdk.api.session.room.model.RoomMemberContent +import org.matrix.android.sdk.api.session.room.model.RoomNameContent +import org.matrix.android.sdk.api.session.room.model.RoomTopicContent +import org.matrix.android.sdk.api.session.room.model.create.CreateRoomParams +import org.matrix.android.sdk.api.session.room.model.create.CreateRoomPreset +import org.matrix.android.sdk.api.session.room.model.tombstone.RoomTombstoneContent +import org.matrix.android.sdk.api.session.room.send.SendState +import org.matrix.android.sdk.internal.database.mapper.asDomain +import org.matrix.android.sdk.internal.database.mapper.toEntity +import org.matrix.android.sdk.internal.database.model.CurrentStateEventEntity +import org.matrix.android.sdk.internal.database.model.EventEntity +import org.matrix.android.sdk.internal.database.model.EventInsertType +import org.matrix.android.sdk.internal.database.query.copyToRealmOrIgnore +import org.matrix.android.sdk.internal.database.query.getOrCreate +import org.matrix.android.sdk.internal.database.query.where +import org.matrix.android.sdk.internal.database.query.whereRoomId +import org.matrix.android.sdk.internal.di.SessionDatabase +import org.matrix.android.sdk.internal.di.UserId +import org.matrix.android.sdk.internal.session.room.state.StateEventDataSource +import org.matrix.android.sdk.internal.task.Task +import org.matrix.android.sdk.internal.util.awaitTransaction +import org.matrix.android.sdk.internal.util.time.Clock +import java.util.UUID +import javax.inject.Inject + +/** + * Create a Room from a "fake" local room. + * The configuration of the local room will be use to configure the new room. + * The potential local room members will also be invited to this new room. + * + * A "fake" local tombstone event will be created to indicate that the local room has been replacing by the new one. + */ +internal interface CreateRoomFromLocalRoomTask : Task { + data class Params(val localRoomId: String) +} + +internal class DefaultCreateRoomFromLocalRoomTask @Inject constructor( + @UserId private val userId: String, + @SessionDatabase private val monarchy: Monarchy, + private val createRoomTask: CreateRoomTask, + private val stateEventDataSource: StateEventDataSource, + private val clock: Clock, +) : CreateRoomFromLocalRoomTask { + + override suspend fun execute(params: CreateRoomFromLocalRoomTask.Params): String { + val replacementRoomId = stateEventDataSource.getStateEvent(params.localRoomId, EventType.STATE_ROOM_TOMBSTONE, QueryStringValue.NoCondition) + ?.content?.toModel() + ?.replacementRoomId + + if (replacementRoomId != null) { + return replacementRoomId + } + + val createRoomParams = getCreateRoomParams(params) + val roomId = createRoomTask.execute(createRoomParams) + createTombstoneEvent(params, roomId) + return roomId + } + + /** + * Retrieve the room configuration by parsing the state events related to the local room. + */ + private suspend fun getCreateRoomParams(params: CreateRoomFromLocalRoomTask.Params): CreateRoomParams { + var createRoomParams = CreateRoomParams() + monarchy.awaitTransaction { realm -> + val stateEvents = CurrentStateEventEntity.whereRoomId(realm, params.localRoomId).findAll() + stateEvents.forEach { event -> + createRoomParams = when (event.type) { + EventType.STATE_ROOM_MEMBER -> handleRoomMemberEvent(realm, event, createRoomParams) + EventType.STATE_ROOM_HISTORY_VISIBILITY -> handleRoomHistoryVisibilityEvent(realm, event, createRoomParams) + EventType.STATE_ROOM_ALIASES -> handleRoomAliasesEvent(realm, event, createRoomParams) + EventType.STATE_ROOM_AVATAR -> handleRoomAvatarEvent(realm, event, createRoomParams) + EventType.STATE_ROOM_CANONICAL_ALIAS -> handleRoomCanonicalAliasEvent(realm, event, createRoomParams) + EventType.STATE_ROOM_GUEST_ACCESS -> handleRoomGuestAccessEvent(realm, event, createRoomParams) + EventType.STATE_ROOM_ENCRYPTION -> handleRoomEncryptionEvent(createRoomParams) + EventType.STATE_ROOM_POWER_LEVELS -> handleRoomPowerRoomLevelsEvent(realm, event, createRoomParams) + EventType.STATE_ROOM_NAME -> handleRoomNameEvent(realm, event, createRoomParams) + EventType.STATE_ROOM_TOPIC -> handleRoomTopicEvent(realm, event, createRoomParams) + EventType.STATE_ROOM_THIRD_PARTY_INVITE -> handleRoomThirdPartyInviteEvent(event, createRoomParams) + EventType.STATE_ROOM_JOIN_RULES -> handleRoomJoinRulesEvent(realm, event, createRoomParams) + else -> createRoomParams + } + } + } + return createRoomParams + } + + /** + * Create a Tombstone event to indicate that the local room has been replaced by a new one. + */ + private suspend fun createTombstoneEvent(params: CreateRoomFromLocalRoomTask.Params, roomId: String) { + val now = clock.epochMillis() + val event = Event( + type = EventType.STATE_ROOM_TOMBSTONE, + senderId = userId, + originServerTs = now, + stateKey = "", + eventId = UUID.randomUUID().toString(), + content = RoomTombstoneContent( + replacementRoomId = roomId + ).toContent() + ) + monarchy.awaitTransaction { realm -> + val eventEntity = event.toEntity(params.localRoomId, SendState.SYNCED, now).copyToRealmOrIgnore(realm, EventInsertType.INCREMENTAL_SYNC) + if (event.stateKey != null && event.type != null && event.eventId != null) { + CurrentStateEventEntity.getOrCreate(realm, params.localRoomId, event.stateKey, event.type).apply { + eventId = event.eventId + root = eventEntity + } + } + } + } + + /* ========================================================================================== + * Local events handling + * ========================================================================================== */ + + private fun handleRoomMemberEvent(realm: Realm, event: CurrentStateEventEntity, params: CreateRoomParams): CreateRoomParams = params.apply { + val content = getEventContent(realm, event.eventId) ?: return@apply + invitedUserIds.add(event.stateKey) + if (content.isDirect) { + setDirectMessage() + } + } + + private fun handleRoomHistoryVisibilityEvent(realm: Realm, event: CurrentStateEventEntity, params: CreateRoomParams): CreateRoomParams = params.apply { + val content = getEventContent(realm, event.eventId) ?: return@apply + historyVisibility = content.historyVisibility + } + + private fun handleRoomAliasesEvent(realm: Realm, event: CurrentStateEventEntity, params: CreateRoomParams): CreateRoomParams = params.apply { + val content = getEventContent(realm, event.eventId) ?: return@apply + roomAliasName = content.aliases.firstOrNull()?.substringAfter("#")?.substringBefore(":") + } + + private fun handleRoomAvatarEvent(realm: Realm, event: CurrentStateEventEntity, params: CreateRoomParams): CreateRoomParams = params.apply { + val content = getEventContent(realm, event.eventId) ?: return@apply + avatarUri = content.avatarUrl?.toUri() + } + + private fun handleRoomCanonicalAliasEvent(realm: Realm, event: CurrentStateEventEntity, params: CreateRoomParams): CreateRoomParams = params.apply { + val content = getEventContent(realm, event.eventId) ?: return@apply + roomAliasName = content.canonicalAlias?.substringAfter("#")?.substringBefore(":") + } + + private fun handleRoomGuestAccessEvent(realm: Realm, event: CurrentStateEventEntity, params: CreateRoomParams): CreateRoomParams = params.apply { + val content = getEventContent(realm, event.eventId) ?: return@apply + guestAccess = content.guestAccess + } + + private fun handleRoomEncryptionEvent(params: CreateRoomParams): CreateRoomParams = params.apply { + // Having an encryption event means the room is encrypted, so just enable it again + enableEncryption() + } + + private fun handleRoomPowerRoomLevelsEvent(realm: Realm, event: CurrentStateEventEntity, params: CreateRoomParams): CreateRoomParams = params.apply { + val content = getEventContent(realm, event.eventId) ?: return@apply + powerLevelContentOverride = content + } + + private fun handleRoomNameEvent(realm: Realm, event: CurrentStateEventEntity, params: CreateRoomParams): CreateRoomParams = params.apply { + val content = getEventContent(realm, event.eventId) ?: return@apply + name = content.name + } + + private fun handleRoomTopicEvent(realm: Realm, event: CurrentStateEventEntity, params: CreateRoomParams): CreateRoomParams = params.apply { + val content = getEventContent(realm, event.eventId) ?: return@apply + topic = content.topic + } + + private fun handleRoomThirdPartyInviteEvent(event: CurrentStateEventEntity, params: CreateRoomParams): CreateRoomParams = params.apply { + when { + event.stateKey.isEmail() -> invite3pids.add(ThreePid.Email(event.stateKey)) + event.stateKey.isMsisdn() -> invite3pids.add(ThreePid.Msisdn(event.stateKey)) + } + } + + private fun handleRoomJoinRulesEvent(realm: Realm, event: CurrentStateEventEntity, params: CreateRoomParams): CreateRoomParams = params.apply { + val content = getEventContent(realm, event.eventId) ?: return@apply + preset = when { + // If preset has already been set for direct chat, keep it + preset == CreateRoomPreset.PRESET_TRUSTED_PRIVATE_CHAT -> CreateRoomPreset.PRESET_TRUSTED_PRIVATE_CHAT + content.joinRules == RoomJoinRules.PUBLIC -> CreateRoomPreset.PRESET_PUBLIC_CHAT + content.joinRules == RoomJoinRules.INVITE -> CreateRoomPreset.PRESET_PRIVATE_CHAT + else -> null + } + } + + /* ========================================================================================== + * Helper methods + * ========================================================================================== */ + + private inline fun getEventContent(realm: Realm, eventId: String): T? { + return EventEntity.where(realm, eventId).findFirst()?.asDomain()?.getClearContent().toModel() + } + + /** + * Check if a CharSequence is an email. + */ + private fun CharSequence.isEmail() = Patterns.EMAIL_ADDRESS.matcher(this).matches() + + /** + * Check if a CharSequence is a phone number. + */ + private fun CharSequence.isMsisdn(): Boolean { + return try { + PhoneNumberUtil.getInstance().parse(ensurePrefix("+"), null) + true + } catch (e: NumberParseException) { + false + } + } +} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/state/SendStateTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/state/SendStateTask.kt index 59c9de2932..ecc452edb3 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/state/SendStateTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/state/SendStateTask.kt @@ -16,10 +16,12 @@ package org.matrix.android.sdk.internal.session.room.state +import org.matrix.android.sdk.api.session.room.model.localecho.RoomLocalEcho import org.matrix.android.sdk.api.util.JsonDict import org.matrix.android.sdk.internal.network.GlobalErrorReceiver import org.matrix.android.sdk.internal.network.executeRequest import org.matrix.android.sdk.internal.session.room.RoomAPI +import org.matrix.android.sdk.internal.session.room.create.CreateRoomFromLocalRoomTask import org.matrix.android.sdk.internal.task.Task import timber.log.Timber import javax.inject.Inject @@ -35,28 +37,40 @@ internal interface SendStateTask : Task { internal class DefaultSendStateTask @Inject constructor( private val roomAPI: RoomAPI, - private val globalErrorReceiver: GlobalErrorReceiver + private val globalErrorReceiver: GlobalErrorReceiver, + private val createRoomFromLocalRoomTask: CreateRoomFromLocalRoomTask, ) : SendStateTask { override suspend fun execute(params: SendStateTask.Params): String { return executeRequest(globalErrorReceiver) { - val response = if (params.stateKey.isEmpty()) { - roomAPI.sendStateEvent( - roomId = params.roomId, - stateEventType = params.eventType, - params = params.body - ) + if (RoomLocalEcho.isLocalEchoId(params.roomId)) { + // Room is local, so create a real one and send the event to this new room + createRoomAndSendEvent(params) } else { - roomAPI.sendStateEvent( - roomId = params.roomId, - stateEventType = params.eventType, - stateKey = params.stateKey, - params = params.body - ) - } - response.eventId.also { - Timber.d("State event: $it just sent in room ${params.roomId}") + val response = if (params.stateKey.isEmpty()) { + roomAPI.sendStateEvent( + roomId = params.roomId, + stateEventType = params.eventType, + params = params.body + ) + } else { + roomAPI.sendStateEvent( + roomId = params.roomId, + stateEventType = params.eventType, + stateKey = params.stateKey, + params = params.body + ) + } + response.eventId.also { + Timber.d("State event: $it just sent in room ${params.roomId}") + } } } } + + private suspend fun createRoomAndSendEvent(params: SendStateTask.Params): String { + val roomId = createRoomFromLocalRoomTask.execute(CreateRoomFromLocalRoomTask.Params(params.roomId)) + Timber.d("State event: convert local room (${params.roomId}) to existing room ($roomId) before sending the event.") + return execute(params.copy(roomId = roomId)) + } } diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/TimelineViewModel.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/TimelineViewModel.kt index c0f90aba7a..cea845a490 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/TimelineViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/TimelineViewModel.kt @@ -82,6 +82,7 @@ import org.matrix.android.sdk.api.query.QueryStringValue import org.matrix.android.sdk.api.raw.RawService import org.matrix.android.sdk.api.session.Session import org.matrix.android.sdk.api.session.crypto.MXCryptoError +import org.matrix.android.sdk.api.session.events.model.Event import org.matrix.android.sdk.api.session.events.model.EventType import org.matrix.android.sdk.api.session.events.model.LocalEcho import org.matrix.android.sdk.api.session.events.model.RelationType @@ -1269,11 +1270,26 @@ class TimelineViewModel @AssistedInject constructor( } } room.getStateEvent(EventType.STATE_ROOM_TOMBSTONE, QueryStringValue.IsEmpty)?.also { - setState { copy(tombstoneEvent = it) } + onRoomTombstoneUpdated(it) } } } + private var roomTombstoneHandled = false + private fun onRoomTombstoneUpdated(tombstoneEvent: Event) = withState { state -> + if (roomTombstoneHandled) return@withState + if (state.isLocalRoom()) { + // Local room has been replaced, so navigate to the new room + val roomId = tombstoneEvent.getClearContent()?.toModel() + ?.replacementRoomId + ?: return@withState + _viewEvents.post(RoomDetailViewEvents.OpenRoom(roomId, closeCurrentRoom = true)) + roomTombstoneHandled = true + } else { + setState { copy(tombstoneEvent = tombstoneEvent) } + } + } + /** * Navigates to the appropriate event (by paginating the thread timeline until the event is found * in the snapshot. The main reason for this function is to support the /relations api From b14e3c61b3463a534b8c3391ea1ab4dbd94cbe49 Mon Sep 17 00:00:00 2001 From: Florian Renaud Date: Thu, 16 Jun 2022 15:48:24 +0200 Subject: [PATCH 02/20] Start DM - Fix first message not encrypted --- .../room/create/CreateLocalRoomTask.kt | 5 ++ .../create/CreateRoomFromLocalRoomTask.kt | 66 ++++++++++++++----- .../session/room/create/CreateRoomTask.kt | 3 +- 3 files changed, 56 insertions(+), 18 deletions(-) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/create/CreateLocalRoomTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/create/CreateLocalRoomTask.kt index d57491a4c8..e1429d8e5c 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/create/CreateLocalRoomTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/create/CreateLocalRoomTask.kt @@ -41,6 +41,7 @@ import org.matrix.android.sdk.api.session.room.send.SendState import org.matrix.android.sdk.api.session.sync.model.RoomSyncSummary import org.matrix.android.sdk.api.session.user.UserService import org.matrix.android.sdk.api.session.user.model.User +import org.matrix.android.sdk.internal.crypto.DefaultCryptoService import org.matrix.android.sdk.internal.database.awaitNotEmptyResult import org.matrix.android.sdk.internal.database.helper.addTimelineEvent import org.matrix.android.sdk.internal.database.mapper.asDomain @@ -77,6 +78,7 @@ internal class DefaultCreateLocalRoomTask @Inject constructor( @SessionDatabase private val realmConfiguration: RealmConfiguration, private val createRoomBodyBuilder: CreateRoomBodyBuilder, private val userService: UserService, + private val cryptoService: DefaultCryptoService, private val clock: Clock, ) : CreateLocalRoomTask { @@ -169,6 +171,9 @@ internal class DefaultCreateLocalRoomTask @Inject constructor( roomMemberContentsByUser[event.stateKey] = event.getFixedRoomMemberContent() roomMemberEventHandler.handle(realm, roomId, event, false) } + + // Give info to crypto module + cryptoService.onStateEvent(roomId, event) } roomMemberContentsByUser.getOrPut(event.senderId) { diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/create/CreateRoomFromLocalRoomTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/create/CreateRoomFromLocalRoomTask.kt index 516220d452..0720f2b5ec 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/create/CreateRoomFromLocalRoomTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/create/CreateRoomFromLocalRoomTask.kt @@ -22,6 +22,7 @@ import com.google.i18n.phonenumbers.NumberParseException import com.google.i18n.phonenumbers.PhoneNumberUtil import com.zhuinden.monarchy.Monarchy import io.realm.Realm +import kotlinx.coroutines.TimeoutCancellationException import org.matrix.android.sdk.api.extensions.ensurePrefix import org.matrix.android.sdk.api.query.QueryStringValue import org.matrix.android.sdk.api.session.events.model.Event @@ -29,6 +30,7 @@ import org.matrix.android.sdk.api.session.events.model.EventType import org.matrix.android.sdk.api.session.events.model.toContent import org.matrix.android.sdk.api.session.events.model.toModel import org.matrix.android.sdk.api.session.identity.ThreePid +import org.matrix.android.sdk.api.session.room.failure.CreateRoomFailure import org.matrix.android.sdk.api.session.room.model.PowerLevelsContent import org.matrix.android.sdk.api.session.room.model.RoomAliasesContent import org.matrix.android.sdk.api.session.room.model.RoomAvatarContent @@ -44,11 +46,15 @@ import org.matrix.android.sdk.api.session.room.model.create.CreateRoomParams import org.matrix.android.sdk.api.session.room.model.create.CreateRoomPreset import org.matrix.android.sdk.api.session.room.model.tombstone.RoomTombstoneContent import org.matrix.android.sdk.api.session.room.send.SendState +import org.matrix.android.sdk.internal.database.awaitNotEmptyResult import org.matrix.android.sdk.internal.database.mapper.asDomain import org.matrix.android.sdk.internal.database.mapper.toEntity import org.matrix.android.sdk.internal.database.model.CurrentStateEventEntity 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.EventInsertType +import org.matrix.android.sdk.internal.database.model.RoomSummaryEntity +import org.matrix.android.sdk.internal.database.model.RoomSummaryEntityFields import org.matrix.android.sdk.internal.database.query.copyToRealmOrIgnore import org.matrix.android.sdk.internal.database.query.getOrCreate import org.matrix.android.sdk.internal.database.query.where @@ -60,6 +66,7 @@ import org.matrix.android.sdk.internal.task.Task import org.matrix.android.sdk.internal.util.awaitTransaction import org.matrix.android.sdk.internal.util.time.Clock import java.util.UUID +import java.util.concurrent.TimeUnit import javax.inject.Inject /** @@ -81,8 +88,11 @@ internal class DefaultCreateRoomFromLocalRoomTask @Inject constructor( private val clock: Clock, ) : CreateRoomFromLocalRoomTask { + private val realmConfiguration + get() = monarchy.realmConfiguration + override suspend fun execute(params: CreateRoomFromLocalRoomTask.Params): String { - val replacementRoomId = stateEventDataSource.getStateEvent(params.localRoomId, EventType.STATE_ROOM_TOMBSTONE, QueryStringValue.NoCondition) + val replacementRoomId = stateEventDataSource.getStateEvent(params.localRoomId, EventType.STATE_ROOM_TOMBSTONE, QueryStringValue.IsEmpty) ?.content?.toModel() ?.replacementRoomId @@ -92,6 +102,30 @@ internal class DefaultCreateRoomFromLocalRoomTask @Inject constructor( val createRoomParams = getCreateRoomParams(params) val roomId = createRoomTask.execute(createRoomParams) + + try { + awaitNotEmptyResult(realmConfiguration, TimeUnit.MINUTES.toMillis(1L)) { realm -> + realm.where(RoomSummaryEntity::class.java) + .equalTo(RoomSummaryEntityFields.ROOM_ID, roomId) + .equalTo( + RoomSummaryEntityFields.INVITED_MEMBERS_COUNT, + createRoomParams.invitedUserIds.size.minus(1) + createRoomParams.invite3pids.size + ) + } + awaitNotEmptyResult(realmConfiguration, TimeUnit.MINUTES.toMillis(1L)) { realm -> + EventEntity.whereRoomId(realm, roomId) + .equalTo(EventEntityFields.TYPE, EventType.STATE_ROOM_HISTORY_VISIBILITY) + } + if (createRoomParams.algorithm != null) { + awaitNotEmptyResult(realmConfiguration, TimeUnit.MINUTES.toMillis(1L)) { realm -> + EventEntity.whereRoomId(realm, roomId) + .equalTo(EventEntityFields.TYPE, EventType.STATE_ROOM_ENCRYPTION) + } + } + } catch (exception: TimeoutCancellationException) { + throw CreateRoomFailure.CreatedWithTimeout(roomId) + } + createTombstoneEvent(params, roomId) return roomId } @@ -105,19 +139,19 @@ internal class DefaultCreateRoomFromLocalRoomTask @Inject constructor( val stateEvents = CurrentStateEventEntity.whereRoomId(realm, params.localRoomId).findAll() stateEvents.forEach { event -> createRoomParams = when (event.type) { - EventType.STATE_ROOM_MEMBER -> handleRoomMemberEvent(realm, event, createRoomParams) + EventType.STATE_ROOM_MEMBER -> handleRoomMemberEvent(realm, event, createRoomParams) EventType.STATE_ROOM_HISTORY_VISIBILITY -> handleRoomHistoryVisibilityEvent(realm, event, createRoomParams) - EventType.STATE_ROOM_ALIASES -> handleRoomAliasesEvent(realm, event, createRoomParams) - EventType.STATE_ROOM_AVATAR -> handleRoomAvatarEvent(realm, event, createRoomParams) - EventType.STATE_ROOM_CANONICAL_ALIAS -> handleRoomCanonicalAliasEvent(realm, event, createRoomParams) - EventType.STATE_ROOM_GUEST_ACCESS -> handleRoomGuestAccessEvent(realm, event, createRoomParams) - EventType.STATE_ROOM_ENCRYPTION -> handleRoomEncryptionEvent(createRoomParams) - EventType.STATE_ROOM_POWER_LEVELS -> handleRoomPowerRoomLevelsEvent(realm, event, createRoomParams) - EventType.STATE_ROOM_NAME -> handleRoomNameEvent(realm, event, createRoomParams) - EventType.STATE_ROOM_TOPIC -> handleRoomTopicEvent(realm, event, createRoomParams) + EventType.STATE_ROOM_ALIASES -> handleRoomAliasesEvent(realm, event, createRoomParams) + EventType.STATE_ROOM_AVATAR -> handleRoomAvatarEvent(realm, event, createRoomParams) + EventType.STATE_ROOM_CANONICAL_ALIAS -> handleRoomCanonicalAliasEvent(realm, event, createRoomParams) + EventType.STATE_ROOM_GUEST_ACCESS -> handleRoomGuestAccessEvent(realm, event, createRoomParams) + EventType.STATE_ROOM_ENCRYPTION -> handleRoomEncryptionEvent(createRoomParams) + EventType.STATE_ROOM_POWER_LEVELS -> handleRoomPowerRoomLevelsEvent(realm, event, createRoomParams) + EventType.STATE_ROOM_NAME -> handleRoomNameEvent(realm, event, createRoomParams) + EventType.STATE_ROOM_TOPIC -> handleRoomTopicEvent(realm, event, createRoomParams) EventType.STATE_ROOM_THIRD_PARTY_INVITE -> handleRoomThirdPartyInviteEvent(event, createRoomParams) - EventType.STATE_ROOM_JOIN_RULES -> handleRoomJoinRulesEvent(realm, event, createRoomParams) - else -> createRoomParams + EventType.STATE_ROOM_JOIN_RULES -> handleRoomJoinRulesEvent(realm, event, createRoomParams) + else -> createRoomParams } } } @@ -209,7 +243,7 @@ internal class DefaultCreateRoomFromLocalRoomTask @Inject constructor( private fun handleRoomThirdPartyInviteEvent(event: CurrentStateEventEntity, params: CreateRoomParams): CreateRoomParams = params.apply { when { - event.stateKey.isEmail() -> invite3pids.add(ThreePid.Email(event.stateKey)) + event.stateKey.isEmail() -> invite3pids.add(ThreePid.Email(event.stateKey)) event.stateKey.isMsisdn() -> invite3pids.add(ThreePid.Msisdn(event.stateKey)) } } @@ -219,9 +253,9 @@ internal class DefaultCreateRoomFromLocalRoomTask @Inject constructor( preset = when { // If preset has already been set for direct chat, keep it preset == CreateRoomPreset.PRESET_TRUSTED_PRIVATE_CHAT -> CreateRoomPreset.PRESET_TRUSTED_PRIVATE_CHAT - content.joinRules == RoomJoinRules.PUBLIC -> CreateRoomPreset.PRESET_PUBLIC_CHAT - content.joinRules == RoomJoinRules.INVITE -> CreateRoomPreset.PRESET_PRIVATE_CHAT - else -> null + content.joinRules == RoomJoinRules.PUBLIC -> CreateRoomPreset.PRESET_PUBLIC_CHAT + content.joinRules == RoomJoinRules.INVITE -> CreateRoomPreset.PRESET_PRIVATE_CHAT + else -> null } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/create/CreateRoomTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/create/CreateRoomTask.kt index d76640573f..137134e43f 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/create/CreateRoomTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/create/CreateRoomTask.kt @@ -54,8 +54,7 @@ internal class DefaultCreateRoomTask @Inject constructor( private val directChatsHelper: DirectChatsHelper, private val updateUserAccountDataTask: UpdateUserAccountDataTask, private val readMarkersTask: SetReadMarkersTask, - @SessionDatabase - private val realmConfiguration: RealmConfiguration, + @SessionDatabase private val realmConfiguration: RealmConfiguration, private val createRoomBodyBuilder: CreateRoomBodyBuilder, private val globalErrorReceiver: GlobalErrorReceiver, private val clock: Clock, From 69917ebc2e84a073010c9e3f47a724f421694142 Mon Sep 17 00:00:00 2001 From: Florian Renaud Date: Sat, 2 Jul 2022 00:11:41 +0200 Subject: [PATCH 03/20] Start DM - Handle third party invites --- .../sdk/api/session/events/model/EventType.kt | 3 ++ .../LocalRoomThirdPartyInviteContent.kt | 39 +++++++++++++++++++ .../room/create/CreateLocalRoomTask.kt | 33 ++++++++++++++++ .../create/CreateRoomFromLocalRoomTask.kt | 21 ++++++++-- .../helper/TimelineEventVisibilityHelper.kt | 5 ++- 5 files changed, 95 insertions(+), 6 deletions(-) create mode 100644 matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/localecho/LocalRoomThirdPartyInviteContent.kt diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/events/model/EventType.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/events/model/EventType.kt index 8fdbba21c5..84c25776e7 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/events/model/EventType.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/events/model/EventType.kt @@ -70,6 +70,9 @@ object EventType { const val STATE_ROOM_ENCRYPTION = "m.room.encryption" const val STATE_ROOM_SERVER_ACL = "m.room.server_acl" + // This type is for local purposes, it should never be processed by the server + const val LOCAL_STATE_ROOM_THIRD_PARTY_INVITE = "local.room.third_party_invite" + // Call Events const val CALL_INVITE = "m.call.invite" const val CALL_CANDIDATES = "m.call.candidates" diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/localecho/LocalRoomThirdPartyInviteContent.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/localecho/LocalRoomThirdPartyInviteContent.kt new file mode 100644 index 0000000000..72e998b377 --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/localecho/LocalRoomThirdPartyInviteContent.kt @@ -0,0 +1,39 @@ +/* + * 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.api.session.room.model.localecho + +import com.squareup.moshi.Json +import com.squareup.moshi.JsonClass +import org.matrix.android.sdk.api.session.room.model.Membership + +/** + * Class representing the EventType.LOCAL_STATE_ROOM_THIRD_PARTY_INVITE state event content + * This class is only used to store the third party invite data of a local room. + */ +@JsonClass(generateAdapter = true) +data class LocalRoomThirdPartyInviteContent( + @Json(name = "membership") val membership: Membership, + @Json(name = "displayname") val displayName: String? = null, + @Json(name = "is_direct") val isDirect: Boolean = false, + @Json(name = "third_party_invite") val thirdPartyInvite: LocalThreePid? = null, +) + +@JsonClass(generateAdapter = true) +data class LocalThreePid( + val email: String? = null, + val msisdn: String? = null, +) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/create/CreateLocalRoomTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/create/CreateLocalRoomTask.kt index e1429d8e5c..4f8dc11d2c 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/create/CreateLocalRoomTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/create/CreateLocalRoomTask.kt @@ -34,8 +34,11 @@ import org.matrix.android.sdk.api.session.room.model.Membership import org.matrix.android.sdk.api.session.room.model.RoomDirectoryVisibility import org.matrix.android.sdk.api.session.room.model.RoomHistoryVisibility import org.matrix.android.sdk.api.session.room.model.RoomMemberContent +import org.matrix.android.sdk.api.session.room.model.RoomThirdPartyInviteContent import org.matrix.android.sdk.api.session.room.model.create.CreateRoomParams import org.matrix.android.sdk.api.session.room.model.create.RoomCreateContent +import org.matrix.android.sdk.api.session.room.model.localecho.LocalRoomThirdPartyInviteContent +import org.matrix.android.sdk.api.session.room.model.localecho.LocalThreePid import org.matrix.android.sdk.api.session.room.model.localecho.RoomLocalEcho import org.matrix.android.sdk.api.session.room.send.SendState import org.matrix.android.sdk.api.session.sync.model.RoomSyncSummary @@ -204,6 +207,7 @@ internal class DefaultCreateLocalRoomTask @Inject constructor( val myUser = userService.getUser(userId) ?: User(userId) val invitedUsers = createRoomBody.invitedUserIds.orEmpty() .mapNotNull { tryOrNull { userService.resolveUser(it) } } + val invited3Pids = createRoomBody.invite3pids.orEmpty() val createRoomEvent = createLocalEvent( type = EventType.STATE_ROOM_CREATE, @@ -233,11 +237,40 @@ internal class DefaultCreateLocalRoomTask @Inject constructor( ) } + val localRoomThreePidEvents = invited3Pids.map { body -> + createLocalEvent( + type = EventType.LOCAL_STATE_ROOM_THIRD_PARTY_INVITE, + content = LocalRoomThirdPartyInviteContent( + isDirect = createRoomBody.isDirect.orFalse(), + membership = Membership.INVITE, + displayName = body.address, + thirdPartyInvite = LocalThreePid( + msisdn = body.address.takeIf { body.medium == "msisdn" }, + email = body.address.takeIf { body.medium == "email" } + ) + ).toContent() + ) + } + + val roomThreePidEvents = invited3Pids.map { body -> + createLocalEvent( + type = EventType.STATE_ROOM_THIRD_PARTY_INVITE, + content = RoomThirdPartyInviteContent( + displayName = body.address, + keyValidityUrl = null, + publicKey = null, + publicKeys = null + ).toContent() + ) + } + return buildList { add(createRoomEvent) add(myRoomMemberEvent) addAll(createRoomBody.initialStates.orEmpty().map { createLocalEvent(it.type, it.content, it.stateKey) }) addAll(roomMemberEvents) + addAll(roomThreePidEvents) + addAll(localRoomThreePidEvents) } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/create/CreateRoomFromLocalRoomTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/create/CreateRoomFromLocalRoomTask.kt index 0720f2b5ec..1d569370db 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/create/CreateRoomFromLocalRoomTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/create/CreateRoomFromLocalRoomTask.kt @@ -44,6 +44,7 @@ import org.matrix.android.sdk.api.session.room.model.RoomNameContent import org.matrix.android.sdk.api.session.room.model.RoomTopicContent import org.matrix.android.sdk.api.session.room.model.create.CreateRoomParams import org.matrix.android.sdk.api.session.room.model.create.CreateRoomPreset +import org.matrix.android.sdk.api.session.room.model.localecho.LocalRoomThirdPartyInviteContent import org.matrix.android.sdk.api.session.room.model.tombstone.RoomTombstoneContent import org.matrix.android.sdk.api.session.room.send.SendState import org.matrix.android.sdk.internal.database.awaitNotEmptyResult @@ -104,13 +105,11 @@ internal class DefaultCreateRoomFromLocalRoomTask @Inject constructor( val roomId = createRoomTask.execute(createRoomParams) try { + // Wait for all the room events before triggering the replacement room awaitNotEmptyResult(realmConfiguration, TimeUnit.MINUTES.toMillis(1L)) { realm -> realm.where(RoomSummaryEntity::class.java) .equalTo(RoomSummaryEntityFields.ROOM_ID, roomId) - .equalTo( - RoomSummaryEntityFields.INVITED_MEMBERS_COUNT, - createRoomParams.invitedUserIds.size.minus(1) + createRoomParams.invite3pids.size - ) + .equalTo(RoomSummaryEntityFields.INVITED_MEMBERS_COUNT, createRoomParams.invitedUserIds.size.minus(1)) } awaitNotEmptyResult(realmConfiguration, TimeUnit.MINUTES.toMillis(1L)) { realm -> EventEntity.whereRoomId(realm, roomId) @@ -140,6 +139,7 @@ internal class DefaultCreateRoomFromLocalRoomTask @Inject constructor( stateEvents.forEach { event -> createRoomParams = when (event.type) { EventType.STATE_ROOM_MEMBER -> handleRoomMemberEvent(realm, event, createRoomParams) + EventType.LOCAL_STATE_ROOM_THIRD_PARTY_INVITE -> handleLocalRoomThirdPartyInviteEvent(realm, event, createRoomParams) EventType.STATE_ROOM_HISTORY_VISIBILITY -> handleRoomHistoryVisibilityEvent(realm, event, createRoomParams) EventType.STATE_ROOM_ALIASES -> handleRoomAliasesEvent(realm, event, createRoomParams) EventType.STATE_ROOM_AVATAR -> handleRoomAvatarEvent(realm, event, createRoomParams) @@ -196,6 +196,19 @@ internal class DefaultCreateRoomFromLocalRoomTask @Inject constructor( } } + private fun handleLocalRoomThirdPartyInviteEvent(realm: Realm, event: CurrentStateEventEntity, params: CreateRoomParams): CreateRoomParams = params.apply { + val content = getEventContent(realm, event.eventId) ?: return@apply + val threePid = when { + content.thirdPartyInvite?.email != null -> ThreePid.Email(content.thirdPartyInvite.email) + content.thirdPartyInvite?.msisdn != null -> ThreePid.Msisdn(content.thirdPartyInvite.msisdn) + else -> return@apply + } + invite3pids.add(threePid) + if (content.isDirect) { + setDirectMessage() + } + } + private fun handleRoomHistoryVisibilityEvent(realm: Realm, event: CurrentStateEventEntity, params: CreateRoomParams): CreateRoomParams = params.apply { val content = getEventContent(realm, event.eventId) ?: return@apply historyVisibility = content.historyVisibility diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/TimelineEventVisibilityHelper.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/TimelineEventVisibilityHelper.kt index e6765bf35a..d22b649b36 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/TimelineEventVisibilityHelper.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/TimelineEventVisibilityHelper.kt @@ -229,8 +229,9 @@ class TimelineEventVisibilityHelper @Inject constructor( // Hide fake events for local rooms if (RoomLocalEcho.isLocalEchoId(roomId) && - root.getClearType() == EventType.STATE_ROOM_MEMBER || - root.getClearType() == EventType.STATE_ROOM_HISTORY_VISIBILITY) { + (root.getClearType() == EventType.STATE_ROOM_MEMBER || + root.getClearType() == EventType.STATE_ROOM_HISTORY_VISIBILITY || + root.getClearType() == EventType.STATE_ROOM_THIRD_PARTY_INVITE)) { return true } From c96343f1d708733185d98636d08a902b09efc714 Mon Sep 17 00:00:00 2001 From: Florian Renaud Date: Fri, 5 Aug 2022 09:52:12 +0200 Subject: [PATCH 04/20] Persists CreateRoomParams into LocalRoomSummaryEntity --- dependencies.gradle | 1 + matrix-sdk-android/build.gradle | 1 + .../sdk/api/session/identity/ThreePid.kt | 4 + .../room/model/create/CreateRoomParams.kt | 23 ++- .../room/model/create/CreateRoomStateEvent.kt | 2 + .../LocalRoomThirdPartyInviteContent.kt | 9 +- .../database/RealmSessionStoreMigration.kt | 4 +- .../database/migration/MigrateSessionTo036.kt | 33 ++++ .../database/model/LocalRoomSummaryEntity.kt | 39 ++++ .../database/model/SessionRealmModule.kt | 1 + .../query/LocalRoomSummaryEntityQueries.kt | 31 +++ .../android/sdk/internal/di/MoshiProvider.kt | 8 + .../room/create/CreateLocalRoomTask.kt | 22 ++- .../session/room/create/CreateRoomBody.kt | 9 +- .../create/CreateRoomFromLocalRoomTask.kt | 186 ++---------------- .../session/room/create/CreateRoomTask.kt | 2 +- .../room/delete/DeleteLocalRoomTask.kt | 4 + .../membership/threepid/ThreePidInviteBody.kt | 8 + 18 files changed, 193 insertions(+), 194 deletions(-) create mode 100644 matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/migration/MigrateSessionTo036.kt create mode 100644 matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/LocalRoomSummaryEntity.kt create mode 100644 matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/query/LocalRoomSummaryEntityQueries.kt diff --git a/dependencies.gradle b/dependencies.gradle index 80dc203740..7a9ed3f931 100644 --- a/dependencies.gradle +++ b/dependencies.gradle @@ -104,6 +104,7 @@ ext.libs = [ 'moshi' : "com.squareup.moshi:moshi:$moshi", 'moshiKt' : "com.squareup.moshi:moshi-kotlin:$moshi", 'moshiKotlin' : "com.squareup.moshi:moshi-kotlin-codegen:$moshi", + 'moshiAdapters' : "com.squareup.moshi:moshi-adapters:$moshi", 'retrofit' : "com.squareup.retrofit2:retrofit:$retrofit", 'retrofitMoshi' : "com.squareup.retrofit2:converter-moshi:$retrofit" ], diff --git a/matrix-sdk-android/build.gradle b/matrix-sdk-android/build.gradle index fcd1e7d622..faa798c9dc 100644 --- a/matrix-sdk-android/build.gradle +++ b/matrix-sdk-android/build.gradle @@ -163,6 +163,7 @@ dependencies { implementation 'com.squareup.okhttp3:logging-interceptor' implementation libs.squareup.moshi + implementation libs.squareup.moshiAdapters kapt libs.squareup.moshiKotlin api "com.atlassian.commonmark:commonmark:0.13.0" diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/identity/ThreePid.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/identity/ThreePid.kt index 6bcf576824..24748f88e4 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/identity/ThreePid.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/identity/ThreePid.kt @@ -18,10 +18,14 @@ package org.matrix.android.sdk.api.session.identity import com.google.i18n.phonenumbers.NumberParseException import com.google.i18n.phonenumbers.PhoneNumberUtil +import com.squareup.moshi.JsonClass import org.matrix.android.sdk.internal.session.profile.ThirdPartyIdentifier sealed class ThreePid(open val value: String) { + @JsonClass(generateAdapter = true) data class Email(val email: String) : ThreePid(email) + + @JsonClass(generateAdapter = true) data class Msisdn(val msisdn: String) : ThreePid(msisdn) } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/create/CreateRoomParams.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/create/CreateRoomParams.kt index b7b0cc890b..e959dcc81c 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/create/CreateRoomParams.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/create/CreateRoomParams.kt @@ -17,13 +17,16 @@ package org.matrix.android.sdk.api.session.room.model.create import android.net.Uri +import com.squareup.moshi.JsonClass import org.matrix.android.sdk.api.crypto.MXCRYPTO_ALGORITHM_MEGOLM import org.matrix.android.sdk.api.session.identity.ThreePid import org.matrix.android.sdk.api.session.room.model.GuestAccess import org.matrix.android.sdk.api.session.room.model.PowerLevelsContent import org.matrix.android.sdk.api.session.room.model.RoomDirectoryVisibility import org.matrix.android.sdk.api.session.room.model.RoomHistoryVisibility +import org.matrix.android.sdk.internal.di.MoshiProvider +@JsonClass(generateAdapter = true) open class CreateRoomParams { /** * A public visibility indicates that the room will be shown in the published room list. @@ -61,12 +64,12 @@ open class CreateRoomParams { * A list of user IDs to invite to the room. * This will tell the server to invite everyone in the list to the newly created room. */ - val invitedUserIds = mutableListOf() + var invitedUserIds: MutableList = mutableListOf() /** * A list of objects representing third party IDs to invite into the room. */ - val invite3pids = mutableListOf() + var invite3pids = mutableListOf() /** * Initial Guest Access. @@ -99,14 +102,14 @@ open class CreateRoomParams { * The server will clobber the following keys: creator. * Future versions of the specification may allow the server to clobber other keys. */ - val creationContent = mutableMapOf() + var creationContent = mutableMapOf() /** * A list of state events to set in the new room. This allows the user to override the default state events * set in the new room. The expected format of the state events are an object with type, state_key and content keys set. * Takes precedence over events set by preset, but gets overridden by name and topic keys. */ - val initialStates = mutableListOf() + var initialStates = mutableListOf() /** * Set to true to disable federation of this room. @@ -151,7 +154,7 @@ open class CreateRoomParams { * Supported value: MXCRYPTO_ALGORITHM_MEGOLM. */ var algorithm: String? = null - private set + internal set var historyVisibility: RoomHistoryVisibility? = null @@ -161,10 +164,18 @@ open class CreateRoomParams { var roomVersion: String? = null - var featurePreset: RoomFeaturePreset? = null + @Transient var featurePreset: RoomFeaturePreset? = null companion object { private const val CREATION_CONTENT_KEY_M_FEDERATE = "m.federate" private const val CREATION_CONTENT_KEY_ROOM_TYPE = "type" + + fun fromJson(json: String?): CreateRoomParams? { + return json?.let { MoshiProvider.providesMoshi().adapter(CreateRoomParams::class.java).fromJson(it) } + } } } + +internal fun CreateRoomParams.toJSONString(): String { + return MoshiProvider.providesMoshi().adapter(CreateRoomParams::class.java).toJson(this) +} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/create/CreateRoomStateEvent.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/create/CreateRoomStateEvent.kt index fcfdc3e333..d89c72c513 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/create/CreateRoomStateEvent.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/create/CreateRoomStateEvent.kt @@ -16,8 +16,10 @@ package org.matrix.android.sdk.api.session.room.model.create +import com.squareup.moshi.JsonClass import org.matrix.android.sdk.api.session.events.model.Content +@JsonClass(generateAdapter = true) data class CreateRoomStateEvent( /** * Required. The type of event to send. diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/localecho/LocalRoomThirdPartyInviteContent.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/localecho/LocalRoomThirdPartyInviteContent.kt index 72e998b377..78347fcafc 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/localecho/LocalRoomThirdPartyInviteContent.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/localecho/LocalRoomThirdPartyInviteContent.kt @@ -18,6 +18,7 @@ package org.matrix.android.sdk.api.session.room.model.localecho import com.squareup.moshi.Json import com.squareup.moshi.JsonClass +import org.matrix.android.sdk.api.session.identity.ThreePid import org.matrix.android.sdk.api.session.room.model.Membership /** @@ -29,11 +30,5 @@ data class LocalRoomThirdPartyInviteContent( @Json(name = "membership") val membership: Membership, @Json(name = "displayname") val displayName: String? = null, @Json(name = "is_direct") val isDirect: Boolean = false, - @Json(name = "third_party_invite") val thirdPartyInvite: LocalThreePid? = null, -) - -@JsonClass(generateAdapter = true) -data class LocalThreePid( - val email: String? = null, - val msisdn: String? = null, + @Json(name = "third_party_invite") val thirdPartyInvite: ThreePid? = null, ) 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 b733aa6fc0..0b11863864 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 @@ -52,6 +52,7 @@ import org.matrix.android.sdk.internal.database.migration.MigrateSessionTo032 import org.matrix.android.sdk.internal.database.migration.MigrateSessionTo033 import org.matrix.android.sdk.internal.database.migration.MigrateSessionTo034 import org.matrix.android.sdk.internal.database.migration.MigrateSessionTo035 +import org.matrix.android.sdk.internal.database.migration.MigrateSessionTo036 import org.matrix.android.sdk.internal.util.Normalizer import org.matrix.android.sdk.internal.util.database.MatrixRealmMigration import javax.inject.Inject @@ -60,7 +61,7 @@ internal class RealmSessionStoreMigration @Inject constructor( private val normalizer: Normalizer ) : MatrixRealmMigration( dbName = "Session", - schemaVersion = 35L, + schemaVersion = 36L, ) { /** * Forces all RealmSessionStoreMigration instances to be equal. @@ -105,5 +106,6 @@ internal class RealmSessionStoreMigration @Inject constructor( if (oldVersion < 33) MigrateSessionTo033(realm).perform() if (oldVersion < 34) MigrateSessionTo034(realm).perform() if (oldVersion < 35) MigrateSessionTo035(realm).perform() + if (oldVersion < 36) MigrateSessionTo036(realm).perform() } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/migration/MigrateSessionTo036.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/migration/MigrateSessionTo036.kt new file mode 100644 index 0000000000..efcb181ecb --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/migration/MigrateSessionTo036.kt @@ -0,0 +1,33 @@ +/* + * 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.migration + +import io.realm.DynamicRealm +import org.matrix.android.sdk.internal.database.model.LocalRoomSummaryEntityFields +import org.matrix.android.sdk.internal.util.database.RealmMigrator + +internal class MigrateSessionTo036(realm: DynamicRealm) : RealmMigrator(realm, 36) { + + override fun doMigrate(realm: DynamicRealm) { + realm.schema.create("LocalRoomSummaryEntity") + .addField(LocalRoomSummaryEntityFields.ROOM_ID, String::class.java) + .addPrimaryKey(LocalRoomSummaryEntityFields.ROOM_ID) + .setRequired(LocalRoomSummaryEntityFields.ROOM_ID, true) + .addField(LocalRoomSummaryEntityFields.CREATE_ROOM_PARAMS_STR, String::class.java) + .addRealmObjectField(LocalRoomSummaryEntityFields.ROOM_SUMMARY_ENTITY.`$`, realm.schema.get("RoomSummaryEntity")!!) + } +} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/LocalRoomSummaryEntity.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/LocalRoomSummaryEntity.kt new file mode 100644 index 0000000000..fd8331e986 --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/LocalRoomSummaryEntity.kt @@ -0,0 +1,39 @@ +/* + * 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.model + +import io.realm.RealmObject +import io.realm.annotations.PrimaryKey +import org.matrix.android.sdk.api.session.room.model.create.CreateRoomParams +import org.matrix.android.sdk.api.session.room.model.create.toJSONString + +internal open class LocalRoomSummaryEntity( + @PrimaryKey var roomId: String = "", + var roomSummaryEntity: RoomSummaryEntity? = null, + private var createRoomParamsStr: String? = null +) : RealmObject() { + + var createRoomParams: CreateRoomParams? + get() { + return CreateRoomParams.fromJson(createRoomParamsStr) + } + set(value) { + createRoomParamsStr = value?.toJSONString() + } + + 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 d131589dd1..b222bcb710 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 @@ -35,6 +35,7 @@ import org.matrix.android.sdk.internal.database.model.threads.ThreadSummaryEntit ReadReceiptEntity::class, RoomEntity::class, RoomSummaryEntity::class, + LocalRoomSummaryEntity::class, RoomTagEntity::class, SyncEntity::class, PendingThreePidEntity::class, diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/query/LocalRoomSummaryEntityQueries.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/query/LocalRoomSummaryEntityQueries.kt new file mode 100644 index 0000000000..527350bedc --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/query/LocalRoomSummaryEntityQueries.kt @@ -0,0 +1,31 @@ +/* + * 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.query + +import io.realm.Realm +import io.realm.RealmQuery +import io.realm.kotlin.where +import org.matrix.android.sdk.internal.database.model.LocalRoomSummaryEntity +import org.matrix.android.sdk.internal.database.model.LocalRoomSummaryEntityFields + +internal fun LocalRoomSummaryEntity.Companion.where(realm: Realm, roomId: String? = null): RealmQuery { + val query = realm.where() + if (roomId != null) { + query.equalTo(LocalRoomSummaryEntityFields.ROOM_ID, roomId) + } + return query +} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/di/MoshiProvider.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/di/MoshiProvider.kt index 8f007f227c..0a737d5e64 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/di/MoshiProvider.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/di/MoshiProvider.kt @@ -17,6 +17,8 @@ package org.matrix.android.sdk.internal.di import com.squareup.moshi.Moshi +import com.squareup.moshi.adapters.PolymorphicJsonAdapterFactory +import org.matrix.android.sdk.api.session.identity.ThreePid import org.matrix.android.sdk.api.session.room.model.message.MessageAudioContent import org.matrix.android.sdk.api.session.room.model.message.MessageContent import org.matrix.android.sdk.api.session.room.model.message.MessageDefaultContent @@ -60,6 +62,12 @@ internal object MoshiProvider { .registerSubtype(MessagePollResponseContent::class.java, MessageType.MSGTYPE_POLL_RESPONSE) ) .add(SerializeNulls.JSON_ADAPTER_FACTORY) + .add( + PolymorphicJsonAdapterFactory.of(ThreePid::class.java, "type") + .withSubtype(ThreePid.Email::class.java, "email") + .withSubtype(ThreePid.Msisdn::class.java, "msisdn") + .withDefaultValue(null) + ) .build() fun providesMoshi(): Moshi { diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/create/CreateLocalRoomTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/create/CreateLocalRoomTask.kt index 4f8dc11d2c..78c8ebe099 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/create/CreateLocalRoomTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/create/CreateLocalRoomTask.kt @@ -38,7 +38,6 @@ import org.matrix.android.sdk.api.session.room.model.RoomThirdPartyInviteContent import org.matrix.android.sdk.api.session.room.model.create.CreateRoomParams import org.matrix.android.sdk.api.session.room.model.create.RoomCreateContent import org.matrix.android.sdk.api.session.room.model.localecho.LocalRoomThirdPartyInviteContent -import org.matrix.android.sdk.api.session.room.model.localecho.LocalThreePid import org.matrix.android.sdk.api.session.room.model.localecho.RoomLocalEcho import org.matrix.android.sdk.api.session.room.send.SendState import org.matrix.android.sdk.api.session.sync.model.RoomSyncSummary @@ -52,6 +51,7 @@ import org.matrix.android.sdk.internal.database.mapper.toEntity import org.matrix.android.sdk.internal.database.model.ChunkEntity import org.matrix.android.sdk.internal.database.model.CurrentStateEventEntity import org.matrix.android.sdk.internal.database.model.EventInsertType +import org.matrix.android.sdk.internal.database.model.LocalRoomSummaryEntity import org.matrix.android.sdk.internal.database.model.RoomEntity import org.matrix.android.sdk.internal.database.model.RoomMembersLoadStatusType import org.matrix.android.sdk.internal.database.model.RoomSummaryEntity @@ -63,6 +63,7 @@ import org.matrix.android.sdk.internal.di.SessionDatabase import org.matrix.android.sdk.internal.di.UserId import org.matrix.android.sdk.internal.session.events.getFixedRoomMemberContent import org.matrix.android.sdk.internal.session.room.membership.RoomMemberEventHandler +import org.matrix.android.sdk.internal.session.room.membership.threepid.toThreePid import org.matrix.android.sdk.internal.session.room.summary.RoomSummaryUpdater import org.matrix.android.sdk.internal.session.room.timeline.PaginationDirection import org.matrix.android.sdk.internal.task.Task @@ -90,7 +91,7 @@ internal class DefaultCreateLocalRoomTask @Inject constructor( val roomId = RoomLocalEcho.createLocalEchoId() monarchy.awaitTransaction { realm -> createLocalRoomEntity(realm, roomId, createRoomBody) - createLocalRoomSummaryEntity(realm, roomId, createRoomBody) + createLocalRoomSummaryEntity(realm, roomId, createRoomBody, params) } // Wait for room to be created in DB @@ -119,14 +120,18 @@ internal class DefaultCreateLocalRoomTask @Inject constructor( } } - private fun createLocalRoomSummaryEntity(realm: Realm, roomId: String, createRoomBody: CreateRoomBody) { - val otherUserId = createRoomBody.getDirectUserId() - if (otherUserId != null) { - RoomSummaryEntity.getOrCreate(realm, roomId).apply { + private fun createLocalRoomSummaryEntity(realm: Realm, roomId: String, createRoomBody: CreateRoomBody, createRoomParams: CreateRoomParams) { + val roomSummaryEntity = realm.createObject(roomId).apply { + val otherUserId = createRoomBody.getDirectUserId() + if (otherUserId != null) { isDirect = true directUserId = otherUserId } } + realm.createObject(roomId).also { + it.roomSummaryEntity = roomSummaryEntity + it.createRoomParams = createRoomParams + } roomSummaryUpdater.update( realm = realm, roomId = roomId, @@ -244,10 +249,7 @@ internal class DefaultCreateLocalRoomTask @Inject constructor( isDirect = createRoomBody.isDirect.orFalse(), membership = Membership.INVITE, displayName = body.address, - thirdPartyInvite = LocalThreePid( - msisdn = body.address.takeIf { body.medium == "msisdn" }, - email = body.address.takeIf { body.medium == "email" } - ) + thirdPartyInvite = body.toThreePid() ).toContent() ) } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/create/CreateRoomBody.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/create/CreateRoomBody.kt index b326c3618c..17e1aba6f6 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/create/CreateRoomBody.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/create/CreateRoomBody.kt @@ -22,6 +22,7 @@ import org.matrix.android.sdk.api.session.events.model.Event import org.matrix.android.sdk.api.session.room.model.PowerLevelsContent import org.matrix.android.sdk.api.session.room.model.RoomDirectoryVisibility import org.matrix.android.sdk.api.session.room.model.create.CreateRoomPreset +import org.matrix.android.sdk.internal.di.MoshiProvider import org.matrix.android.sdk.internal.session.room.membership.threepid.ThreePidInviteBody /** @@ -119,7 +120,13 @@ internal data class CreateRoomBody( */ @Json(name = "room_version") val roomVersion: String? -) +) { + companion object { + fun fromJson(json: String?): CreateRoomBody? { + return json?.let { MoshiProvider.providesMoshi().adapter(CreateRoomBody::class.java).fromJson(it) } + } + } +} /** * Tells if the created room can be a direct chat one. diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/create/CreateRoomFromLocalRoomTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/create/CreateRoomFromLocalRoomTask.kt index 1d569370db..c59c56ffde 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/create/CreateRoomFromLocalRoomTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/create/CreateRoomFromLocalRoomTask.kt @@ -16,49 +16,31 @@ package org.matrix.android.sdk.internal.session.room.create -import android.util.Patterns -import androidx.core.net.toUri -import com.google.i18n.phonenumbers.NumberParseException -import com.google.i18n.phonenumbers.PhoneNumberUtil import com.zhuinden.monarchy.Monarchy -import io.realm.Realm +import io.realm.kotlin.where import kotlinx.coroutines.TimeoutCancellationException -import org.matrix.android.sdk.api.extensions.ensurePrefix +import org.matrix.android.sdk.api.extensions.orFalse import org.matrix.android.sdk.api.query.QueryStringValue 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.toContent import org.matrix.android.sdk.api.session.events.model.toModel -import org.matrix.android.sdk.api.session.identity.ThreePid import org.matrix.android.sdk.api.session.room.failure.CreateRoomFailure -import org.matrix.android.sdk.api.session.room.model.PowerLevelsContent -import org.matrix.android.sdk.api.session.room.model.RoomAliasesContent -import org.matrix.android.sdk.api.session.room.model.RoomAvatarContent -import org.matrix.android.sdk.api.session.room.model.RoomCanonicalAliasContent -import org.matrix.android.sdk.api.session.room.model.RoomGuestAccessContent -import org.matrix.android.sdk.api.session.room.model.RoomHistoryVisibilityContent -import org.matrix.android.sdk.api.session.room.model.RoomJoinRules -import org.matrix.android.sdk.api.session.room.model.RoomJoinRulesContent -import org.matrix.android.sdk.api.session.room.model.RoomMemberContent -import org.matrix.android.sdk.api.session.room.model.RoomNameContent -import org.matrix.android.sdk.api.session.room.model.RoomTopicContent import org.matrix.android.sdk.api.session.room.model.create.CreateRoomParams -import org.matrix.android.sdk.api.session.room.model.create.CreateRoomPreset -import org.matrix.android.sdk.api.session.room.model.localecho.LocalRoomThirdPartyInviteContent import org.matrix.android.sdk.api.session.room.model.tombstone.RoomTombstoneContent import org.matrix.android.sdk.api.session.room.send.SendState import org.matrix.android.sdk.internal.database.awaitNotEmptyResult -import org.matrix.android.sdk.internal.database.mapper.asDomain import org.matrix.android.sdk.internal.database.mapper.toEntity import org.matrix.android.sdk.internal.database.model.CurrentStateEventEntity 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.EventInsertType +import org.matrix.android.sdk.internal.database.model.LocalRoomSummaryEntity +import org.matrix.android.sdk.internal.database.model.LocalRoomSummaryEntityFields import org.matrix.android.sdk.internal.database.model.RoomSummaryEntity import org.matrix.android.sdk.internal.database.model.RoomSummaryEntityFields import org.matrix.android.sdk.internal.database.query.copyToRealmOrIgnore import org.matrix.android.sdk.internal.database.query.getOrCreate -import org.matrix.android.sdk.internal.database.query.where import org.matrix.android.sdk.internal.database.query.whereRoomId import org.matrix.android.sdk.internal.di.SessionDatabase import org.matrix.android.sdk.internal.di.UserId @@ -101,21 +83,31 @@ internal class DefaultCreateRoomFromLocalRoomTask @Inject constructor( return replacementRoomId } - val createRoomParams = getCreateRoomParams(params) - val roomId = createRoomTask.execute(createRoomParams) + var createRoomParams: CreateRoomParams? = null + var isEncrypted = false + monarchy.doWithRealm { realm -> + realm.where() + .equalTo(LocalRoomSummaryEntityFields.ROOM_ID, params.localRoomId) + .findFirst() + ?.let { + createRoomParams = it.createRoomParams + isEncrypted = it.roomSummaryEntity?.isEncrypted.orFalse() + } + } + val roomId = createRoomTask.execute(createRoomParams!!) try { // Wait for all the room events before triggering the replacement room awaitNotEmptyResult(realmConfiguration, TimeUnit.MINUTES.toMillis(1L)) { realm -> realm.where(RoomSummaryEntity::class.java) .equalTo(RoomSummaryEntityFields.ROOM_ID, roomId) - .equalTo(RoomSummaryEntityFields.INVITED_MEMBERS_COUNT, createRoomParams.invitedUserIds.size.minus(1)) + .equalTo(RoomSummaryEntityFields.INVITED_MEMBERS_COUNT, createRoomParams?.invitedUserIds?.size ?: 0) } awaitNotEmptyResult(realmConfiguration, TimeUnit.MINUTES.toMillis(1L)) { realm -> EventEntity.whereRoomId(realm, roomId) .equalTo(EventEntityFields.TYPE, EventType.STATE_ROOM_HISTORY_VISIBILITY) } - if (createRoomParams.algorithm != null) { + if (isEncrypted) { awaitNotEmptyResult(realmConfiguration, TimeUnit.MINUTES.toMillis(1L)) { realm -> EventEntity.whereRoomId(realm, roomId) .equalTo(EventEntityFields.TYPE, EventType.STATE_ROOM_ENCRYPTION) @@ -129,35 +121,6 @@ internal class DefaultCreateRoomFromLocalRoomTask @Inject constructor( return roomId } - /** - * Retrieve the room configuration by parsing the state events related to the local room. - */ - private suspend fun getCreateRoomParams(params: CreateRoomFromLocalRoomTask.Params): CreateRoomParams { - var createRoomParams = CreateRoomParams() - monarchy.awaitTransaction { realm -> - val stateEvents = CurrentStateEventEntity.whereRoomId(realm, params.localRoomId).findAll() - stateEvents.forEach { event -> - createRoomParams = when (event.type) { - EventType.STATE_ROOM_MEMBER -> handleRoomMemberEvent(realm, event, createRoomParams) - EventType.LOCAL_STATE_ROOM_THIRD_PARTY_INVITE -> handleLocalRoomThirdPartyInviteEvent(realm, event, createRoomParams) - EventType.STATE_ROOM_HISTORY_VISIBILITY -> handleRoomHistoryVisibilityEvent(realm, event, createRoomParams) - EventType.STATE_ROOM_ALIASES -> handleRoomAliasesEvent(realm, event, createRoomParams) - EventType.STATE_ROOM_AVATAR -> handleRoomAvatarEvent(realm, event, createRoomParams) - EventType.STATE_ROOM_CANONICAL_ALIAS -> handleRoomCanonicalAliasEvent(realm, event, createRoomParams) - EventType.STATE_ROOM_GUEST_ACCESS -> handleRoomGuestAccessEvent(realm, event, createRoomParams) - EventType.STATE_ROOM_ENCRYPTION -> handleRoomEncryptionEvent(createRoomParams) - EventType.STATE_ROOM_POWER_LEVELS -> handleRoomPowerRoomLevelsEvent(realm, event, createRoomParams) - EventType.STATE_ROOM_NAME -> handleRoomNameEvent(realm, event, createRoomParams) - EventType.STATE_ROOM_TOPIC -> handleRoomTopicEvent(realm, event, createRoomParams) - EventType.STATE_ROOM_THIRD_PARTY_INVITE -> handleRoomThirdPartyInviteEvent(event, createRoomParams) - EventType.STATE_ROOM_JOIN_RULES -> handleRoomJoinRulesEvent(realm, event, createRoomParams) - else -> createRoomParams - } - } - } - return createRoomParams - } - /** * Create a Tombstone event to indicate that the local room has been replaced by a new one. */ @@ -183,117 +146,4 @@ internal class DefaultCreateRoomFromLocalRoomTask @Inject constructor( } } } - - /* ========================================================================================== - * Local events handling - * ========================================================================================== */ - - private fun handleRoomMemberEvent(realm: Realm, event: CurrentStateEventEntity, params: CreateRoomParams): CreateRoomParams = params.apply { - val content = getEventContent(realm, event.eventId) ?: return@apply - invitedUserIds.add(event.stateKey) - if (content.isDirect) { - setDirectMessage() - } - } - - private fun handleLocalRoomThirdPartyInviteEvent(realm: Realm, event: CurrentStateEventEntity, params: CreateRoomParams): CreateRoomParams = params.apply { - val content = getEventContent(realm, event.eventId) ?: return@apply - val threePid = when { - content.thirdPartyInvite?.email != null -> ThreePid.Email(content.thirdPartyInvite.email) - content.thirdPartyInvite?.msisdn != null -> ThreePid.Msisdn(content.thirdPartyInvite.msisdn) - else -> return@apply - } - invite3pids.add(threePid) - if (content.isDirect) { - setDirectMessage() - } - } - - private fun handleRoomHistoryVisibilityEvent(realm: Realm, event: CurrentStateEventEntity, params: CreateRoomParams): CreateRoomParams = params.apply { - val content = getEventContent(realm, event.eventId) ?: return@apply - historyVisibility = content.historyVisibility - } - - private fun handleRoomAliasesEvent(realm: Realm, event: CurrentStateEventEntity, params: CreateRoomParams): CreateRoomParams = params.apply { - val content = getEventContent(realm, event.eventId) ?: return@apply - roomAliasName = content.aliases.firstOrNull()?.substringAfter("#")?.substringBefore(":") - } - - private fun handleRoomAvatarEvent(realm: Realm, event: CurrentStateEventEntity, params: CreateRoomParams): CreateRoomParams = params.apply { - val content = getEventContent(realm, event.eventId) ?: return@apply - avatarUri = content.avatarUrl?.toUri() - } - - private fun handleRoomCanonicalAliasEvent(realm: Realm, event: CurrentStateEventEntity, params: CreateRoomParams): CreateRoomParams = params.apply { - val content = getEventContent(realm, event.eventId) ?: return@apply - roomAliasName = content.canonicalAlias?.substringAfter("#")?.substringBefore(":") - } - - private fun handleRoomGuestAccessEvent(realm: Realm, event: CurrentStateEventEntity, params: CreateRoomParams): CreateRoomParams = params.apply { - val content = getEventContent(realm, event.eventId) ?: return@apply - guestAccess = content.guestAccess - } - - private fun handleRoomEncryptionEvent(params: CreateRoomParams): CreateRoomParams = params.apply { - // Having an encryption event means the room is encrypted, so just enable it again - enableEncryption() - } - - private fun handleRoomPowerRoomLevelsEvent(realm: Realm, event: CurrentStateEventEntity, params: CreateRoomParams): CreateRoomParams = params.apply { - val content = getEventContent(realm, event.eventId) ?: return@apply - powerLevelContentOverride = content - } - - private fun handleRoomNameEvent(realm: Realm, event: CurrentStateEventEntity, params: CreateRoomParams): CreateRoomParams = params.apply { - val content = getEventContent(realm, event.eventId) ?: return@apply - name = content.name - } - - private fun handleRoomTopicEvent(realm: Realm, event: CurrentStateEventEntity, params: CreateRoomParams): CreateRoomParams = params.apply { - val content = getEventContent(realm, event.eventId) ?: return@apply - topic = content.topic - } - - private fun handleRoomThirdPartyInviteEvent(event: CurrentStateEventEntity, params: CreateRoomParams): CreateRoomParams = params.apply { - when { - event.stateKey.isEmail() -> invite3pids.add(ThreePid.Email(event.stateKey)) - event.stateKey.isMsisdn() -> invite3pids.add(ThreePid.Msisdn(event.stateKey)) - } - } - - private fun handleRoomJoinRulesEvent(realm: Realm, event: CurrentStateEventEntity, params: CreateRoomParams): CreateRoomParams = params.apply { - val content = getEventContent(realm, event.eventId) ?: return@apply - preset = when { - // If preset has already been set for direct chat, keep it - preset == CreateRoomPreset.PRESET_TRUSTED_PRIVATE_CHAT -> CreateRoomPreset.PRESET_TRUSTED_PRIVATE_CHAT - content.joinRules == RoomJoinRules.PUBLIC -> CreateRoomPreset.PRESET_PUBLIC_CHAT - content.joinRules == RoomJoinRules.INVITE -> CreateRoomPreset.PRESET_PRIVATE_CHAT - else -> null - } - } - - /* ========================================================================================== - * Helper methods - * ========================================================================================== */ - - private inline fun getEventContent(realm: Realm, eventId: String): T? { - return EventEntity.where(realm, eventId).findFirst()?.asDomain()?.getClearContent().toModel() - } - - /** - * Check if a CharSequence is an email. - */ - private fun CharSequence.isEmail() = Patterns.EMAIL_ADDRESS.matcher(this).matches() - - /** - * Check if a CharSequence is a phone number. - */ - private fun CharSequence.isMsisdn(): Boolean { - return try { - PhoneNumberUtil.getInstance().parse(ensurePrefix("+"), null) - true - } catch (e: NumberParseException) { - false - } - } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/create/CreateRoomTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/create/CreateRoomTask.kt index 137134e43f..e558d34ff9 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/create/CreateRoomTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/create/CreateRoomTask.kt @@ -70,7 +70,6 @@ internal class DefaultCreateRoomTask @Inject constructor( } val createRoomBody = createRoomBodyBuilder.build(params) - val createRoomResponse = try { executeRequest(globalErrorReceiver) { roomAPI.createRoom(createRoomBody) @@ -89,6 +88,7 @@ internal class DefaultCreateRoomTask @Inject constructor( } throw throwable } + val roomId = createRoomResponse.roomId // Wait for room to come back from the sync (but it can maybe be in the DB if the sync response is received before) try { diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/delete/DeleteLocalRoomTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/delete/DeleteLocalRoomTask.kt index 936c94e520..49951d2da0 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/delete/DeleteLocalRoomTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/delete/DeleteLocalRoomTask.kt @@ -21,6 +21,7 @@ import org.matrix.android.sdk.api.session.room.model.localecho.RoomLocalEcho import org.matrix.android.sdk.internal.database.model.ChunkEntity import org.matrix.android.sdk.internal.database.model.CurrentStateEventEntity import org.matrix.android.sdk.internal.database.model.EventEntity +import org.matrix.android.sdk.internal.database.model.LocalRoomSummaryEntity import org.matrix.android.sdk.internal.database.model.RoomEntity import org.matrix.android.sdk.internal.database.model.RoomMemberSummaryEntity import org.matrix.android.sdk.internal.database.model.RoomSummaryEntity @@ -70,6 +71,9 @@ internal class DefaultDeleteLocalRoomTask @Inject constructor( RoomEntity.where(realm, roomId = roomId).findAll() ?.also { Timber.i("## DeleteLocalRoomTask - RoomEntity - delete ${it.size} entries") } ?.deleteAllFromRealm() + LocalRoomSummaryEntity.where(realm, roomId = roomId).findAll() + ?.also { Timber.i("## DeleteLocalRoomTask - LocalRoomSummaryEntity - delete ${it.size} entries") } + ?.deleteAllFromRealm() } } else { Timber.i("## DeleteLocalRoomTask - Failed to remove room with id $roomId: not a local room") diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/membership/threepid/ThreePidInviteBody.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/membership/threepid/ThreePidInviteBody.kt index 3141c052c3..d7b78faea8 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/membership/threepid/ThreePidInviteBody.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/membership/threepid/ThreePidInviteBody.kt @@ -18,6 +18,8 @@ package org.matrix.android.sdk.internal.session.room.membership.threepid import com.squareup.moshi.Json import com.squareup.moshi.JsonClass +import org.matrix.android.sdk.api.session.identity.ThreePid +import org.matrix.android.sdk.internal.auth.data.ThreePidMedium @JsonClass(generateAdapter = true) internal data class ThreePidInviteBody( @@ -43,3 +45,9 @@ internal data class ThreePidInviteBody( @Json(name = "address") val address: String ) + +internal fun ThreePidInviteBody.toThreePid() = when (medium) { + ThreePidMedium.EMAIL -> ThreePid.Email(address) + ThreePidMedium.MSISDN -> ThreePid.Msisdn(address) + else -> null +} From 5df71c6161ac403e311db78b269f97b8cb5576f2 Mon Sep 17 00:00:00 2001 From: Florian Renaud Date: Mon, 8 Aug 2022 16:09:54 +0200 Subject: [PATCH 05/20] Update CreateRoomParams from the potential FeaturePreset before persisting --- .../session/room/create/CreateLocalRoomTask.kt | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/create/CreateLocalRoomTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/create/CreateLocalRoomTask.kt index 78c8ebe099..52a30d937e 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/create/CreateLocalRoomTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/create/CreateLocalRoomTask.kt @@ -91,7 +91,7 @@ internal class DefaultCreateLocalRoomTask @Inject constructor( val roomId = RoomLocalEcho.createLocalEchoId() monarchy.awaitTransaction { realm -> createLocalRoomEntity(realm, roomId, createRoomBody) - createLocalRoomSummaryEntity(realm, roomId, createRoomBody, params) + createLocalRoomSummaryEntity(realm, roomId, params, createRoomBody) } // Wait for room to be created in DB @@ -120,7 +120,8 @@ internal class DefaultCreateLocalRoomTask @Inject constructor( } } - private fun createLocalRoomSummaryEntity(realm: Realm, roomId: String, createRoomBody: CreateRoomBody, createRoomParams: CreateRoomParams) { + private fun createLocalRoomSummaryEntity(realm: Realm, roomId: String, createRoomParams: CreateRoomParams, createRoomBody: CreateRoomBody) { + // Create the room summary entity val roomSummaryEntity = realm.createObject(roomId).apply { val otherUserId = createRoomBody.getDirectUserId() if (otherUserId != null) { @@ -128,10 +129,20 @@ internal class DefaultCreateLocalRoomTask @Inject constructor( directUserId = otherUserId } } + + // Update the createRoomParams from the potential feature preset before saving + createRoomParams.featurePreset?.let { featurePreset -> + featurePreset.updateRoomParams(createRoomParams) + createRoomParams.initialStates.addAll(featurePreset.setupInitialStates().orEmpty()) + } + + // Create a LocalRoomSummaryEntity decorated by the related RoomSummaryEntity and the updated CreateRoomParams realm.createObject(roomId).also { it.roomSummaryEntity = roomSummaryEntity it.createRoomParams = createRoomParams } + + // Update the RoomSummaryEntity by simulating a fake sync response roomSummaryUpdater.update( realm = realm, roomId = roomId, From 7216f6bd64e689641369e7244666b641e09c578f Mon Sep 17 00:00:00 2001 From: Florian Renaud Date: Mon, 8 Aug 2022 17:43:49 +0200 Subject: [PATCH 06/20] Fix local events generation following the specification --- .../room/model/create/CreateRoomParams.kt | 4 +- .../room/create/CreateLocalRoomTask.kt | 273 +++++++++++++----- 2 files changed, 208 insertions(+), 69 deletions(-) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/create/CreateRoomParams.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/create/CreateRoomParams.kt index e959dcc81c..9b708b692f 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/create/CreateRoomParams.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/create/CreateRoomParams.kt @@ -167,8 +167,8 @@ open class CreateRoomParams { @Transient var featurePreset: RoomFeaturePreset? = null companion object { - private const val CREATION_CONTENT_KEY_M_FEDERATE = "m.federate" - private const val CREATION_CONTENT_KEY_ROOM_TYPE = "type" + internal const val CREATION_CONTENT_KEY_M_FEDERATE = "m.federate" + internal const val CREATION_CONTENT_KEY_ROOM_TYPE = "type" fun fromJson(json: String?): CreateRoomParams? { return json?.let { MoshiProvider.providesMoshi().adapter(CreateRoomParams::class.java).fromJson(it) } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/create/CreateLocalRoomTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/create/CreateLocalRoomTask.kt index 52a30d937e..160d9bfb24 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/create/CreateLocalRoomTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/create/CreateLocalRoomTask.kt @@ -21,6 +21,7 @@ import io.realm.Realm import io.realm.RealmConfiguration import io.realm.kotlin.createObject import kotlinx.coroutines.TimeoutCancellationException +import org.matrix.android.sdk.api.MatrixPatterns.getServerName import org.matrix.android.sdk.api.extensions.orFalse import org.matrix.android.sdk.api.extensions.tryOrNull import org.matrix.android.sdk.api.session.events.model.Content @@ -31,18 +32,32 @@ import org.matrix.android.sdk.api.session.events.model.toContent import org.matrix.android.sdk.api.session.room.failure.CreateRoomFailure import org.matrix.android.sdk.api.session.room.model.GuestAccess import org.matrix.android.sdk.api.session.room.model.Membership -import org.matrix.android.sdk.api.session.room.model.RoomDirectoryVisibility +import org.matrix.android.sdk.api.session.room.model.PowerLevelsContent +import org.matrix.android.sdk.api.session.room.model.RoomCanonicalAliasContent +import org.matrix.android.sdk.api.session.room.model.RoomGuestAccessContent import org.matrix.android.sdk.api.session.room.model.RoomHistoryVisibility +import org.matrix.android.sdk.api.session.room.model.RoomHistoryVisibilityContent +import org.matrix.android.sdk.api.session.room.model.RoomJoinRules +import org.matrix.android.sdk.api.session.room.model.RoomJoinRulesContent import org.matrix.android.sdk.api.session.room.model.RoomMemberContent +import org.matrix.android.sdk.api.session.room.model.RoomNameContent import org.matrix.android.sdk.api.session.room.model.RoomThirdPartyInviteContent +import org.matrix.android.sdk.api.session.room.model.RoomTopicContent +import org.matrix.android.sdk.api.session.room.model.banOrDefault import org.matrix.android.sdk.api.session.room.model.create.CreateRoomParams +import org.matrix.android.sdk.api.session.room.model.create.CreateRoomPreset import org.matrix.android.sdk.api.session.room.model.create.RoomCreateContent +import org.matrix.android.sdk.api.session.room.model.eventsDefaultOrDefault +import org.matrix.android.sdk.api.session.room.model.inviteOrDefault +import org.matrix.android.sdk.api.session.room.model.kickOrDefault import org.matrix.android.sdk.api.session.room.model.localecho.LocalRoomThirdPartyInviteContent import org.matrix.android.sdk.api.session.room.model.localecho.RoomLocalEcho +import org.matrix.android.sdk.api.session.room.model.redactOrDefault +import org.matrix.android.sdk.api.session.room.model.stateDefaultOrDefault +import org.matrix.android.sdk.api.session.room.model.usersDefaultOrDefault import org.matrix.android.sdk.api.session.room.send.SendState import org.matrix.android.sdk.api.session.sync.model.RoomSyncSummary import org.matrix.android.sdk.api.session.user.UserService -import org.matrix.android.sdk.api.session.user.model.User import org.matrix.android.sdk.internal.crypto.DefaultCryptoService import org.matrix.android.sdk.internal.database.awaitNotEmptyResult import org.matrix.android.sdk.internal.database.helper.addTimelineEvent @@ -75,7 +90,7 @@ import javax.inject.Inject internal interface CreateLocalRoomTask : Task internal class DefaultCreateLocalRoomTask @Inject constructor( - @UserId private val userId: String, + @UserId private val myUserId: String, @SessionDatabase private val monarchy: Monarchy, private val roomMemberEventHandler: RoomMemberEventHandler, private val roomSummaryUpdater: RoomSummaryUpdater, @@ -87,7 +102,7 @@ internal class DefaultCreateLocalRoomTask @Inject constructor( ) : CreateLocalRoomTask { override suspend fun execute(params: CreateRoomParams): String { - val createRoomBody = createRoomBodyBuilder.build(params.withDefault()) + val createRoomBody = createRoomBodyBuilder.build(params) val roomId = RoomLocalEcho.createLocalEchoId() monarchy.awaitTransaction { realm -> createLocalRoomEntity(realm, roomId, createRoomBody) @@ -213,77 +228,210 @@ internal class DefaultCreateLocalRoomTask @Inject constructor( } /** - * Build the list of the events related to the room creation params. + * Build the list of state events related to the room creation body. + * The events list is ordered following the specification: https://spec.matrix.org/latest/client-server-api/#post_matrixclientv3createroom * - * @param createRoomBody the room creation params + * @param createRoomBody the room creation body * * @return the list of events */ private suspend fun createLocalRoomEvents(createRoomBody: CreateRoomBody): List { - val myUser = userService.getUser(userId) ?: User(userId) - val invitedUsers = createRoomBody.invitedUserIds.orEmpty() - .mapNotNull { tryOrNull { userService.resolveUser(it) } } - val invited3Pids = createRoomBody.invite3pids.orEmpty() + return buildList { + createRoomCreateEvent(createRoomBody) + createRoomMemberEvents(listOf(myUserId)) + createRoomPowerLevelsEvent(createRoomBody) + createRoomAliasEvent(createRoomBody) + createRoomPresetEvents(createRoomBody) + createRoomInitialStateEvents(createRoomBody) + createRoomNameAndTopicStateEvents(createRoomBody) + createRoomMemberEvents(createRoomBody.invitedUserIds.orEmpty()) + createRoomThreePidEvents(createRoomBody) + createRoomDefaultEvents() + } + } - val createRoomEvent = createLocalEvent( + /** + * Generate the create state event related to this room. + */ + private fun MutableList.createRoomCreateEvent(createRoomBody: CreateRoomBody) = apply { + val roomCreateEvent = createLocalEvent( type = EventType.STATE_ROOM_CREATE, content = RoomCreateContent( - creator = userId - ).toContent() - ) - val myRoomMemberEvent = createLocalEvent( - type = EventType.STATE_ROOM_MEMBER, - content = RoomMemberContent( - membership = Membership.JOIN, - displayName = myUser.displayName, - avatarUrl = myUser.avatarUrl - ).toContent(), - stateKey = userId - ) - val roomMemberEvents = invitedUsers.map { - createLocalEvent( - type = EventType.STATE_ROOM_MEMBER, - content = RoomMemberContent( - isDirect = createRoomBody.isDirect.orFalse(), - membership = Membership.INVITE, - displayName = it.displayName, - avatarUrl = it.avatarUrl - ).toContent(), - stateKey = it.userId - ) - } + creator = myUserId, + roomVersion = createRoomBody.roomVersion, + type = (createRoomBody.creationContent as? Map<*, *>)?.get(CreateRoomParams.CREATION_CONTENT_KEY_ROOM_TYPE) as? String - val localRoomThreePidEvents = invited3Pids.map { body -> - createLocalEvent( + ).toContent(), + stateKey = "" + ) + add(roomCreateEvent) + } + + /** + * Generate the create state event related to the power levels using the given overridden values or the default values according to the specification. + * Ref: https://spec.matrix.org/latest/client-server-api/#mroompower_levels + */ + private fun MutableList.createRoomPowerLevelsEvent(createRoomBody: CreateRoomBody) = apply { + val powerLevelsContent = createLocalEvent( + type = EventType.STATE_ROOM_POWER_LEVELS, + content = (createRoomBody.powerLevelContentOverride ?: PowerLevelsContent()).let { + it.copy( + ban = it.banOrDefault(), + eventsDefault = it.eventsDefaultOrDefault(), + invite = it.inviteOrDefault(), + kick = it.kickOrDefault(), + redact = it.redactOrDefault(), + stateDefault = it.stateDefaultOrDefault(), + usersDefault = it.usersDefaultOrDefault(), + ) + }.toContent(), + stateKey = "" + ) + add(powerLevelsContent) + } + + /** + * Generate the local room member state events related to the given user ids, if any. + */ + private suspend fun MutableList.createRoomMemberEvents(userIds: List, isDirect: Boolean? = null) = apply { + val memberEvents = userIds + .mapNotNull { tryOrNull { userService.resolveUser(it) } } + .map { user -> + createLocalEvent( + type = EventType.STATE_ROOM_MEMBER, + content = RoomMemberContent( + isDirect = isDirect.orFalse(), + membership = if (user.userId == myUserId) Membership.JOIN else Membership.INVITE, + displayName = user.displayName, + avatarUrl = user.avatarUrl + ).toContent(), + stateKey = user.userId + ) + } + addAll(memberEvents) + } + + /** + * Generate the local state events related to the given third party invites, if any. + */ + private fun MutableList.createRoomThreePidEvents(createRoomBody: CreateRoomBody) = apply { + val threePidEvents = createRoomBody.invite3pids.orEmpty().map { body -> + val localThirdPartyInviteEvent = createLocalEvent( type = EventType.LOCAL_STATE_ROOM_THIRD_PARTY_INVITE, content = LocalRoomThirdPartyInviteContent( isDirect = createRoomBody.isDirect.orFalse(), membership = Membership.INVITE, displayName = body.address, thirdPartyInvite = body.toThreePid() - ).toContent() + ).toContent(), + stateKey = "" ) - } - - val roomThreePidEvents = invited3Pids.map { body -> - createLocalEvent( + val thirdPartyInviteEvent = createLocalEvent( type = EventType.STATE_ROOM_THIRD_PARTY_INVITE, - content = RoomThirdPartyInviteContent( - displayName = body.address, - keyValidityUrl = null, - publicKey = null, - publicKeys = null - ).toContent() + content = RoomThirdPartyInviteContent(body.address, null, null, null).toContent(), + stateKey = "" ) + listOf(localThirdPartyInviteEvent, thirdPartyInviteEvent) + }.flatten() + addAll(threePidEvents) + } + + /** + * Generate the local state event related to the given alias, if any. + */ + fun MutableList.createRoomAliasEvent(createRoomBody: CreateRoomBody) = apply { + if (createRoomBody.roomAliasName != null) { + val canonicalAliasContent = createLocalEvent( + type = EventType.STATE_ROOM_CANONICAL_ALIAS, + content = RoomCanonicalAliasContent( + canonicalAlias = "${createRoomBody.roomAliasName}:${myUserId.getServerName()}" + ).toContent(), + stateKey = "" + ) + add(canonicalAliasContent) + } + } + + /** + * Generate the local state events related to the given [CreateRoomPreset]. + * Ref: https://spec.matrix.org/latest/client-server-api/#post_matrixclientv3createroom + */ + private fun MutableList.createRoomPresetEvents(createRoomBody: CreateRoomBody) = apply { + createRoomBody.preset ?: return@apply + + var joinRules: RoomJoinRules? = null + var historyVisibility: RoomHistoryVisibility? = null + var guestAccess: GuestAccess? = null + when (createRoomBody.preset) { + CreateRoomPreset.PRESET_PRIVATE_CHAT, + CreateRoomPreset.PRESET_TRUSTED_PRIVATE_CHAT -> { + joinRules = RoomJoinRules.INVITE + historyVisibility = RoomHistoryVisibility.SHARED + guestAccess = GuestAccess.CanJoin + } + CreateRoomPreset.PRESET_PUBLIC_CHAT -> { + joinRules = RoomJoinRules.PUBLIC + historyVisibility = RoomHistoryVisibility.SHARED + guestAccess = GuestAccess.Forbidden + } } - return buildList { - add(createRoomEvent) - add(myRoomMemberEvent) - addAll(createRoomBody.initialStates.orEmpty().map { createLocalEvent(it.type, it.content, it.stateKey) }) - addAll(roomMemberEvents) - addAll(roomThreePidEvents) - addAll(localRoomThreePidEvents) + add(createLocalEvent(EventType.STATE_ROOM_JOIN_RULES, RoomJoinRulesContent(joinRules.value).toContent(), "")) + add(createLocalEvent(EventType.STATE_ROOM_HISTORY_VISIBILITY, RoomHistoryVisibilityContent(historyVisibility.value).toContent(), "")) + add(createLocalEvent(EventType.STATE_ROOM_GUEST_ACCESS, RoomGuestAccessContent(guestAccess.value).toContent(), "")) + } + + /** + * Generate the local state events related to the given initial states, if any. + * The given initial state events override the potential existing ones of the same type. + */ + private fun MutableList.createRoomInitialStateEvents(createRoomBody: CreateRoomBody) = apply { + createRoomBody.initialStates ?: return@apply + + val initialStateEvents = createRoomBody.initialStates.map { createLocalEvent(it.type, it.content, it.stateKey) } + // Erase existing events of the same type + removeAll { event -> event.type in initialStateEvents.map { it.type } } + // Add the initial state events to the list + addAll(initialStateEvents) + } + + /** + * Generate the local events related to the given room name and topic, if any. + */ + private fun MutableList.createRoomNameAndTopicStateEvents(createRoomBody: CreateRoomBody) = apply { + if (createRoomBody.name != null) { + add(createLocalEvent(EventType.STATE_ROOM_NAME, RoomNameContent(createRoomBody.name).toContent(), "")) + } + if (createRoomBody.topic != null) { + add(createLocalEvent(EventType.STATE_ROOM_TOPIC, RoomTopicContent(createRoomBody.topic).toContent(), "")) + } + } + + /** + * Generate the local events which have not been set and are in that case provided by the server with default values: + * - m.room.history_visibility (https://spec.matrix.org/latest/client-server-api/#server-behaviour-5) + * - m.room.guest_access (https://spec.matrix.org/latest/client-server-api/#mroomguest_access) + */ + private fun MutableList.createRoomDefaultEvents() = apply { + // HistoryVisibility + if (none { it.type == EventType.STATE_ROOM_HISTORY_VISIBILITY }) { + add( + createLocalEvent( + type = EventType.STATE_ROOM_HISTORY_VISIBILITY, + content = RoomHistoryVisibilityContent(RoomHistoryVisibility.SHARED.value).toContent(), + stateKey = "" + ) + ) + } + // GuestAccess + if (none { it.type == EventType.STATE_ROOM_GUEST_ACCESS }) { + add( + createLocalEvent( + type = EventType.STATE_ROOM_GUEST_ACCESS, + content = RoomGuestAccessContent(GuestAccess.Forbidden.value).toContent(), + stateKey = "" + ) + ) } } @@ -294,25 +442,16 @@ internal class DefaultCreateLocalRoomTask @Inject constructor( * @param content the content of the Event * @param stateKey the stateKey, if any * - * @return a fake event + * @return a local event */ - private fun createLocalEvent(type: String?, content: Content?, stateKey: String? = ""): Event { + private fun createLocalEvent(type: String?, content: Content?, stateKey: String?): Event { return Event( type = type, - senderId = userId, + senderId = myUserId, stateKey = stateKey, content = content, originServerTs = clock.epochMillis(), eventId = LocalEcho.createLocalEchoId() ) } - - /** - * Setup default values to the CreateRoomParams as the room is created locally (the default values will not be defined by the server). - */ - private fun CreateRoomParams.withDefault() = this.apply { - if (visibility == null) visibility = RoomDirectoryVisibility.PRIVATE - if (historyVisibility == null) historyVisibility = RoomHistoryVisibility.SHARED - if (guestAccess == null) guestAccess = GuestAccess.Forbidden - } } From dee8484618d6a0aea7b2b6a04c0c320aeb0936a1 Mon Sep 17 00:00:00 2001 From: Florian Renaud Date: Wed, 10 Aug 2022 00:58:18 +0200 Subject: [PATCH 07/20] Create local room state events in dedicated task --- .../sdk/internal/session/room/RoomModule.kt | 5 + .../create/CreateLocalRoomStateEventsTask.kt | 296 ++++++++++++++++++ .../room/create/CreateLocalRoomTask.kt | 262 +--------------- 3 files changed, 303 insertions(+), 260 deletions(-) create mode 100644 matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/create/CreateLocalRoomStateEventsTask.kt diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/RoomModule.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/RoomModule.kt index 218ce0a4d5..1475b67276 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/RoomModule.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/RoomModule.kt @@ -43,9 +43,11 @@ import org.matrix.android.sdk.internal.session.room.alias.DefaultGetRoomLocalAli import org.matrix.android.sdk.internal.session.room.alias.DeleteRoomAliasTask import org.matrix.android.sdk.internal.session.room.alias.GetRoomIdByAliasTask import org.matrix.android.sdk.internal.session.room.alias.GetRoomLocalAliasesTask +import org.matrix.android.sdk.internal.session.room.create.CreateLocalRoomStateEventsTask import org.matrix.android.sdk.internal.session.room.create.CreateLocalRoomTask import org.matrix.android.sdk.internal.session.room.create.CreateRoomFromLocalRoomTask import org.matrix.android.sdk.internal.session.room.create.CreateRoomTask +import org.matrix.android.sdk.internal.session.room.create.DefaultCreateLocalRoomStateEventsTask import org.matrix.android.sdk.internal.session.room.create.DefaultCreateLocalRoomTask import org.matrix.android.sdk.internal.session.room.create.DefaultCreateRoomFromLocalRoomTask import org.matrix.android.sdk.internal.session.room.create.DefaultCreateRoomTask @@ -215,6 +217,9 @@ internal abstract class RoomModule { @Binds abstract fun bindCreateLocalRoomTask(task: DefaultCreateLocalRoomTask): CreateLocalRoomTask + @Binds + abstract fun bindCreateLocalRoomStateEventsTask(task: DefaultCreateLocalRoomStateEventsTask): CreateLocalRoomStateEventsTask + @Binds abstract fun bindCreateRoomFromLocalRoomTask(task: DefaultCreateRoomFromLocalRoomTask): CreateRoomFromLocalRoomTask diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/create/CreateLocalRoomStateEventsTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/create/CreateLocalRoomStateEventsTask.kt new file mode 100644 index 0000000000..45980514dd --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/create/CreateLocalRoomStateEventsTask.kt @@ -0,0 +1,296 @@ +/* + * 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.room.create + +import org.matrix.android.sdk.api.MatrixPatterns.getServerName +import org.matrix.android.sdk.api.extensions.orFalse +import org.matrix.android.sdk.api.extensions.tryOrNull +import org.matrix.android.sdk.api.session.events.model.Content +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.toContent +import org.matrix.android.sdk.api.session.room.model.GuestAccess +import org.matrix.android.sdk.api.session.room.model.Membership +import org.matrix.android.sdk.api.session.room.model.PowerLevelsContent +import org.matrix.android.sdk.api.session.room.model.RoomCanonicalAliasContent +import org.matrix.android.sdk.api.session.room.model.RoomGuestAccessContent +import org.matrix.android.sdk.api.session.room.model.RoomHistoryVisibility +import org.matrix.android.sdk.api.session.room.model.RoomHistoryVisibilityContent +import org.matrix.android.sdk.api.session.room.model.RoomJoinRules +import org.matrix.android.sdk.api.session.room.model.RoomJoinRulesContent +import org.matrix.android.sdk.api.session.room.model.RoomMemberContent +import org.matrix.android.sdk.api.session.room.model.RoomNameContent +import org.matrix.android.sdk.api.session.room.model.RoomThirdPartyInviteContent +import org.matrix.android.sdk.api.session.room.model.RoomTopicContent +import org.matrix.android.sdk.api.session.room.model.banOrDefault +import org.matrix.android.sdk.api.session.room.model.create.CreateRoomParams +import org.matrix.android.sdk.api.session.room.model.create.CreateRoomPreset +import org.matrix.android.sdk.api.session.room.model.create.RoomCreateContent +import org.matrix.android.sdk.api.session.room.model.eventsDefaultOrDefault +import org.matrix.android.sdk.api.session.room.model.inviteOrDefault +import org.matrix.android.sdk.api.session.room.model.kickOrDefault +import org.matrix.android.sdk.api.session.room.model.localecho.LocalRoomThirdPartyInviteContent +import org.matrix.android.sdk.api.session.room.model.redactOrDefault +import org.matrix.android.sdk.api.session.room.model.stateDefaultOrDefault +import org.matrix.android.sdk.api.session.room.model.usersDefaultOrDefault +import org.matrix.android.sdk.api.session.user.UserService +import org.matrix.android.sdk.internal.session.room.create.CreateLocalRoomStateEventsTask.Params +import org.matrix.android.sdk.internal.session.room.membership.threepid.toThreePid +import org.matrix.android.sdk.internal.task.Task +import org.matrix.android.sdk.internal.util.time.Clock +import javax.inject.Inject + +internal interface CreateLocalRoomStateEventsTask : Task> { + data class Params( + val roomCreatorUserId: String, + val createRoomBody: CreateRoomBody + ) +} + +internal class DefaultCreateLocalRoomStateEventsTask @Inject constructor( + private val userService: UserService, + private val clock: Clock, +) : CreateLocalRoomStateEventsTask { + + private lateinit var createRoomBody: CreateRoomBody + private lateinit var roomCreatorUserId: String + + override suspend fun execute(params: Params): List { + createRoomBody = params.createRoomBody + roomCreatorUserId = params.roomCreatorUserId + + return buildList { + createRoomCreateEvent() + createRoomMemberEvents(listOf(roomCreatorUserId)) + createRoomPowerLevelsEvent() + createRoomAliasEvent() + createRoomPresetEvents() + createRoomInitialStateEvents() + createRoomNameAndTopicStateEvents() + createRoomMemberEvents(createRoomBody.invitedUserIds.orEmpty()) + createRoomThreePidEvents() + createRoomDefaultEvents() + } + } + + /** + * Generate the create state event related to this room. + */ + private fun MutableList.createRoomCreateEvent() = apply { + val roomCreateEvent = createLocalEvent( + type = EventType.STATE_ROOM_CREATE, + content = RoomCreateContent( + creator = roomCreatorUserId, + roomVersion = createRoomBody.roomVersion, + type = (createRoomBody.creationContent as? Map<*, *>)?.get(CreateRoomParams.CREATION_CONTENT_KEY_ROOM_TYPE) as? String + + ).toContent(), + stateKey = "" + ) + add(roomCreateEvent) + } + + /** + * Generate the create state event related to the power levels using the given overridden values or the default values according to the specification. + * Ref: https://spec.matrix.org/latest/client-server-api/#mroompower_levels + */ + private fun MutableList.createRoomPowerLevelsEvent() = apply { + val powerLevelsContent = createLocalEvent( + type = EventType.STATE_ROOM_POWER_LEVELS, + content = (createRoomBody.powerLevelContentOverride ?: PowerLevelsContent()).let { + it.copy( + ban = it.banOrDefault(), + eventsDefault = it.eventsDefaultOrDefault(), + invite = it.inviteOrDefault(), + kick = it.kickOrDefault(), + redact = it.redactOrDefault(), + stateDefault = it.stateDefaultOrDefault(), + usersDefault = it.usersDefaultOrDefault(), + ) + }.toContent(), + stateKey = "" + ) + add(powerLevelsContent) + } + + /** + * Generate the local room member state events related to the given user ids, if any. + */ + private suspend fun MutableList.createRoomMemberEvents(userIds: List) = apply { + val memberEvents = userIds + .mapNotNull { tryOrNull { userService.resolveUser(it) } } + .map { user -> + createLocalEvent( + type = EventType.STATE_ROOM_MEMBER, + content = RoomMemberContent( + isDirect = createRoomBody.isDirect.takeUnless { user.userId == roomCreatorUserId }.orFalse(), + membership = if (user.userId == roomCreatorUserId) Membership.JOIN else Membership.INVITE, + displayName = user.displayName, + avatarUrl = user.avatarUrl + ).toContent(), + stateKey = user.userId + ) + } + addAll(memberEvents) + } + + /** + * Generate the local state events related to the given third party invites, if any. + */ + private fun MutableList.createRoomThreePidEvents() = apply { + val threePidEvents = createRoomBody.invite3pids.orEmpty().map { body -> + val localThirdPartyInviteEvent = createLocalEvent( + type = EventType.LOCAL_STATE_ROOM_THIRD_PARTY_INVITE, + content = LocalRoomThirdPartyInviteContent( + isDirect = createRoomBody.isDirect.orFalse(), + membership = Membership.INVITE, + displayName = body.address, + thirdPartyInvite = body.toThreePid() + ).toContent(), + stateKey = "" + ) + val thirdPartyInviteEvent = createLocalEvent( + type = EventType.STATE_ROOM_THIRD_PARTY_INVITE, + content = RoomThirdPartyInviteContent(body.address, null, null, null).toContent(), + stateKey = "" + ) + listOf(localThirdPartyInviteEvent, thirdPartyInviteEvent) + }.flatten() + addAll(threePidEvents) + } + + /** + * Generate the local state event related to the given alias, if any. + */ + fun MutableList.createRoomAliasEvent() = apply { + if (createRoomBody.roomAliasName != null) { + val canonicalAliasContent = createLocalEvent( + type = EventType.STATE_ROOM_CANONICAL_ALIAS, + content = RoomCanonicalAliasContent( + canonicalAlias = "${createRoomBody.roomAliasName}:${roomCreatorUserId.getServerName()}" + ).toContent(), + stateKey = "" + ) + add(canonicalAliasContent) + } + } + + /** + * Generate the local state events related to the given [CreateRoomPreset]. + * Ref: https://spec.matrix.org/latest/client-server-api/#post_matrixclientv3createroom + */ + private fun MutableList.createRoomPresetEvents() = apply { + val preset = createRoomBody.preset ?: return@apply + + var joinRules: RoomJoinRules? = null + var historyVisibility: RoomHistoryVisibility? = null + var guestAccess: GuestAccess? = null + when (preset) { + CreateRoomPreset.PRESET_PRIVATE_CHAT, + CreateRoomPreset.PRESET_TRUSTED_PRIVATE_CHAT -> { + joinRules = RoomJoinRules.INVITE + historyVisibility = RoomHistoryVisibility.SHARED + guestAccess = GuestAccess.CanJoin + } + CreateRoomPreset.PRESET_PUBLIC_CHAT -> { + joinRules = RoomJoinRules.PUBLIC + historyVisibility = RoomHistoryVisibility.SHARED + guestAccess = GuestAccess.Forbidden + } + } + + add(createLocalEvent(EventType.STATE_ROOM_JOIN_RULES, RoomJoinRulesContent(joinRules.value).toContent(), "")) + add(createLocalEvent(EventType.STATE_ROOM_HISTORY_VISIBILITY, RoomHistoryVisibilityContent(historyVisibility.value).toContent(), "")) + add(createLocalEvent(EventType.STATE_ROOM_GUEST_ACCESS, RoomGuestAccessContent(guestAccess.value).toContent(), "")) + } + + /** + * Generate the local state events related to the given initial states, if any. + * The given initial state events override the potential existing ones of the same type. + */ + private fun MutableList.createRoomInitialStateEvents() = apply { + val initialStates = createRoomBody.initialStates ?: return@apply + + val initialStateEvents = initialStates.map { createLocalEvent(it.type, it.content, it.stateKey) } + // Erase existing events of the same type + removeAll { event -> event.type in initialStateEvents.map { it.type } } + // Add the initial state events to the list + addAll(initialStateEvents) + } + + /** + * Generate the local events related to the given room name and topic, if any. + */ + private fun MutableList.createRoomNameAndTopicStateEvents() = apply { + if (createRoomBody.name != null) { + add(createLocalEvent(EventType.STATE_ROOM_NAME, RoomNameContent(createRoomBody.name).toContent(), "")) + } + if (createRoomBody.topic != null) { + add(createLocalEvent(EventType.STATE_ROOM_TOPIC, RoomTopicContent(createRoomBody.topic).toContent(), "")) + } + } + + /** + * Generate the local events which have not been set and are in that case provided by the server with default values. + * Default events: + * - m.room.history_visibility (https://spec.matrix.org/latest/client-server-api/#server-behaviour-5) + * - m.room.guest_access (https://spec.matrix.org/latest/client-server-api/#mroomguest_access) + */ + private fun MutableList.createRoomDefaultEvents() = apply { + // HistoryVisibility + if (none { it.type == EventType.STATE_ROOM_HISTORY_VISIBILITY }) { + add( + createLocalEvent( + type = EventType.STATE_ROOM_HISTORY_VISIBILITY, + content = RoomHistoryVisibilityContent(RoomHistoryVisibility.SHARED.value).toContent(), + stateKey = "" + ) + ) + } + // GuestAccess + if (none { it.type == EventType.STATE_ROOM_GUEST_ACCESS }) { + add( + createLocalEvent( + type = EventType.STATE_ROOM_GUEST_ACCESS, + content = RoomGuestAccessContent(GuestAccess.Forbidden.value).toContent(), + stateKey = "" + ) + ) + } + } + + /** + * Generate a local event from the given parameters. + * + * @param type the event type, see [EventType] + * @param content the content of the Event + * @param stateKey the stateKey, if any + * + * @return a local event + */ + private fun createLocalEvent(type: String?, content: Content?, stateKey: String?): Event { + return Event( + type = type, + senderId = roomCreatorUserId, + stateKey = stateKey, + content = content, + originServerTs = clock.epochMillis(), + eventId = LocalEcho.createLocalEchoId() + ) + } +} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/create/CreateLocalRoomTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/create/CreateLocalRoomTask.kt index 160d9bfb24..5b45fbf6ae 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/create/CreateLocalRoomTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/create/CreateLocalRoomTask.kt @@ -21,43 +21,14 @@ import io.realm.Realm import io.realm.RealmConfiguration import io.realm.kotlin.createObject import kotlinx.coroutines.TimeoutCancellationException -import org.matrix.android.sdk.api.MatrixPatterns.getServerName -import org.matrix.android.sdk.api.extensions.orFalse -import org.matrix.android.sdk.api.extensions.tryOrNull -import org.matrix.android.sdk.api.session.events.model.Content -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.toContent import org.matrix.android.sdk.api.session.room.failure.CreateRoomFailure -import org.matrix.android.sdk.api.session.room.model.GuestAccess import org.matrix.android.sdk.api.session.room.model.Membership -import org.matrix.android.sdk.api.session.room.model.PowerLevelsContent -import org.matrix.android.sdk.api.session.room.model.RoomCanonicalAliasContent -import org.matrix.android.sdk.api.session.room.model.RoomGuestAccessContent -import org.matrix.android.sdk.api.session.room.model.RoomHistoryVisibility -import org.matrix.android.sdk.api.session.room.model.RoomHistoryVisibilityContent -import org.matrix.android.sdk.api.session.room.model.RoomJoinRules -import org.matrix.android.sdk.api.session.room.model.RoomJoinRulesContent import org.matrix.android.sdk.api.session.room.model.RoomMemberContent -import org.matrix.android.sdk.api.session.room.model.RoomNameContent -import org.matrix.android.sdk.api.session.room.model.RoomThirdPartyInviteContent -import org.matrix.android.sdk.api.session.room.model.RoomTopicContent -import org.matrix.android.sdk.api.session.room.model.banOrDefault import org.matrix.android.sdk.api.session.room.model.create.CreateRoomParams -import org.matrix.android.sdk.api.session.room.model.create.CreateRoomPreset -import org.matrix.android.sdk.api.session.room.model.create.RoomCreateContent -import org.matrix.android.sdk.api.session.room.model.eventsDefaultOrDefault -import org.matrix.android.sdk.api.session.room.model.inviteOrDefault -import org.matrix.android.sdk.api.session.room.model.kickOrDefault -import org.matrix.android.sdk.api.session.room.model.localecho.LocalRoomThirdPartyInviteContent import org.matrix.android.sdk.api.session.room.model.localecho.RoomLocalEcho -import org.matrix.android.sdk.api.session.room.model.redactOrDefault -import org.matrix.android.sdk.api.session.room.model.stateDefaultOrDefault -import org.matrix.android.sdk.api.session.room.model.usersDefaultOrDefault import org.matrix.android.sdk.api.session.room.send.SendState import org.matrix.android.sdk.api.session.sync.model.RoomSyncSummary -import org.matrix.android.sdk.api.session.user.UserService import org.matrix.android.sdk.internal.crypto.DefaultCryptoService import org.matrix.android.sdk.internal.database.awaitNotEmptyResult import org.matrix.android.sdk.internal.database.helper.addTimelineEvent @@ -78,7 +49,6 @@ import org.matrix.android.sdk.internal.di.SessionDatabase import org.matrix.android.sdk.internal.di.UserId import org.matrix.android.sdk.internal.session.events.getFixedRoomMemberContent import org.matrix.android.sdk.internal.session.room.membership.RoomMemberEventHandler -import org.matrix.android.sdk.internal.session.room.membership.threepid.toThreePid import org.matrix.android.sdk.internal.session.room.summary.RoomSummaryUpdater import org.matrix.android.sdk.internal.session.room.timeline.PaginationDirection import org.matrix.android.sdk.internal.task.Task @@ -96,9 +66,9 @@ internal class DefaultCreateLocalRoomTask @Inject constructor( private val roomSummaryUpdater: RoomSummaryUpdater, @SessionDatabase private val realmConfiguration: RealmConfiguration, private val createRoomBodyBuilder: CreateRoomBodyBuilder, - private val userService: UserService, private val cryptoService: DefaultCryptoService, private val clock: Clock, + private val createLocalRoomStateEventsTask: CreateLocalRoomStateEventsTask, ) : CreateLocalRoomTask { override suspend fun execute(params: CreateRoomParams): String { @@ -186,7 +156,7 @@ internal class DefaultCreateLocalRoomTask @Inject constructor( isLastForward = true } - val eventList = createLocalRoomEvents(createRoomBody) + val eventList = createLocalRoomStateEventsTask.execute(CreateLocalRoomStateEventsTask.Params(myUserId, createRoomBody)) val roomMemberContentsByUser = HashMap() for (event in eventList) { @@ -226,232 +196,4 @@ internal class DefaultCreateLocalRoomTask @Inject constructor( return chunkEntity } - - /** - * Build the list of state events related to the room creation body. - * The events list is ordered following the specification: https://spec.matrix.org/latest/client-server-api/#post_matrixclientv3createroom - * - * @param createRoomBody the room creation body - * - * @return the list of events - */ - private suspend fun createLocalRoomEvents(createRoomBody: CreateRoomBody): List { - return buildList { - createRoomCreateEvent(createRoomBody) - createRoomMemberEvents(listOf(myUserId)) - createRoomPowerLevelsEvent(createRoomBody) - createRoomAliasEvent(createRoomBody) - createRoomPresetEvents(createRoomBody) - createRoomInitialStateEvents(createRoomBody) - createRoomNameAndTopicStateEvents(createRoomBody) - createRoomMemberEvents(createRoomBody.invitedUserIds.orEmpty()) - createRoomThreePidEvents(createRoomBody) - createRoomDefaultEvents() - } - } - - /** - * Generate the create state event related to this room. - */ - private fun MutableList.createRoomCreateEvent(createRoomBody: CreateRoomBody) = apply { - val roomCreateEvent = createLocalEvent( - type = EventType.STATE_ROOM_CREATE, - content = RoomCreateContent( - creator = myUserId, - roomVersion = createRoomBody.roomVersion, - type = (createRoomBody.creationContent as? Map<*, *>)?.get(CreateRoomParams.CREATION_CONTENT_KEY_ROOM_TYPE) as? String - - ).toContent(), - stateKey = "" - ) - add(roomCreateEvent) - } - - /** - * Generate the create state event related to the power levels using the given overridden values or the default values according to the specification. - * Ref: https://spec.matrix.org/latest/client-server-api/#mroompower_levels - */ - private fun MutableList.createRoomPowerLevelsEvent(createRoomBody: CreateRoomBody) = apply { - val powerLevelsContent = createLocalEvent( - type = EventType.STATE_ROOM_POWER_LEVELS, - content = (createRoomBody.powerLevelContentOverride ?: PowerLevelsContent()).let { - it.copy( - ban = it.banOrDefault(), - eventsDefault = it.eventsDefaultOrDefault(), - invite = it.inviteOrDefault(), - kick = it.kickOrDefault(), - redact = it.redactOrDefault(), - stateDefault = it.stateDefaultOrDefault(), - usersDefault = it.usersDefaultOrDefault(), - ) - }.toContent(), - stateKey = "" - ) - add(powerLevelsContent) - } - - /** - * Generate the local room member state events related to the given user ids, if any. - */ - private suspend fun MutableList.createRoomMemberEvents(userIds: List, isDirect: Boolean? = null) = apply { - val memberEvents = userIds - .mapNotNull { tryOrNull { userService.resolveUser(it) } } - .map { user -> - createLocalEvent( - type = EventType.STATE_ROOM_MEMBER, - content = RoomMemberContent( - isDirect = isDirect.orFalse(), - membership = if (user.userId == myUserId) Membership.JOIN else Membership.INVITE, - displayName = user.displayName, - avatarUrl = user.avatarUrl - ).toContent(), - stateKey = user.userId - ) - } - addAll(memberEvents) - } - - /** - * Generate the local state events related to the given third party invites, if any. - */ - private fun MutableList.createRoomThreePidEvents(createRoomBody: CreateRoomBody) = apply { - val threePidEvents = createRoomBody.invite3pids.orEmpty().map { body -> - val localThirdPartyInviteEvent = createLocalEvent( - type = EventType.LOCAL_STATE_ROOM_THIRD_PARTY_INVITE, - content = LocalRoomThirdPartyInviteContent( - isDirect = createRoomBody.isDirect.orFalse(), - membership = Membership.INVITE, - displayName = body.address, - thirdPartyInvite = body.toThreePid() - ).toContent(), - stateKey = "" - ) - val thirdPartyInviteEvent = createLocalEvent( - type = EventType.STATE_ROOM_THIRD_PARTY_INVITE, - content = RoomThirdPartyInviteContent(body.address, null, null, null).toContent(), - stateKey = "" - ) - listOf(localThirdPartyInviteEvent, thirdPartyInviteEvent) - }.flatten() - addAll(threePidEvents) - } - - /** - * Generate the local state event related to the given alias, if any. - */ - fun MutableList.createRoomAliasEvent(createRoomBody: CreateRoomBody) = apply { - if (createRoomBody.roomAliasName != null) { - val canonicalAliasContent = createLocalEvent( - type = EventType.STATE_ROOM_CANONICAL_ALIAS, - content = RoomCanonicalAliasContent( - canonicalAlias = "${createRoomBody.roomAliasName}:${myUserId.getServerName()}" - ).toContent(), - stateKey = "" - ) - add(canonicalAliasContent) - } - } - - /** - * Generate the local state events related to the given [CreateRoomPreset]. - * Ref: https://spec.matrix.org/latest/client-server-api/#post_matrixclientv3createroom - */ - private fun MutableList.createRoomPresetEvents(createRoomBody: CreateRoomBody) = apply { - createRoomBody.preset ?: return@apply - - var joinRules: RoomJoinRules? = null - var historyVisibility: RoomHistoryVisibility? = null - var guestAccess: GuestAccess? = null - when (createRoomBody.preset) { - CreateRoomPreset.PRESET_PRIVATE_CHAT, - CreateRoomPreset.PRESET_TRUSTED_PRIVATE_CHAT -> { - joinRules = RoomJoinRules.INVITE - historyVisibility = RoomHistoryVisibility.SHARED - guestAccess = GuestAccess.CanJoin - } - CreateRoomPreset.PRESET_PUBLIC_CHAT -> { - joinRules = RoomJoinRules.PUBLIC - historyVisibility = RoomHistoryVisibility.SHARED - guestAccess = GuestAccess.Forbidden - } - } - - add(createLocalEvent(EventType.STATE_ROOM_JOIN_RULES, RoomJoinRulesContent(joinRules.value).toContent(), "")) - add(createLocalEvent(EventType.STATE_ROOM_HISTORY_VISIBILITY, RoomHistoryVisibilityContent(historyVisibility.value).toContent(), "")) - add(createLocalEvent(EventType.STATE_ROOM_GUEST_ACCESS, RoomGuestAccessContent(guestAccess.value).toContent(), "")) - } - - /** - * Generate the local state events related to the given initial states, if any. - * The given initial state events override the potential existing ones of the same type. - */ - private fun MutableList.createRoomInitialStateEvents(createRoomBody: CreateRoomBody) = apply { - createRoomBody.initialStates ?: return@apply - - val initialStateEvents = createRoomBody.initialStates.map { createLocalEvent(it.type, it.content, it.stateKey) } - // Erase existing events of the same type - removeAll { event -> event.type in initialStateEvents.map { it.type } } - // Add the initial state events to the list - addAll(initialStateEvents) - } - - /** - * Generate the local events related to the given room name and topic, if any. - */ - private fun MutableList.createRoomNameAndTopicStateEvents(createRoomBody: CreateRoomBody) = apply { - if (createRoomBody.name != null) { - add(createLocalEvent(EventType.STATE_ROOM_NAME, RoomNameContent(createRoomBody.name).toContent(), "")) - } - if (createRoomBody.topic != null) { - add(createLocalEvent(EventType.STATE_ROOM_TOPIC, RoomTopicContent(createRoomBody.topic).toContent(), "")) - } - } - - /** - * Generate the local events which have not been set and are in that case provided by the server with default values: - * - m.room.history_visibility (https://spec.matrix.org/latest/client-server-api/#server-behaviour-5) - * - m.room.guest_access (https://spec.matrix.org/latest/client-server-api/#mroomguest_access) - */ - private fun MutableList.createRoomDefaultEvents() = apply { - // HistoryVisibility - if (none { it.type == EventType.STATE_ROOM_HISTORY_VISIBILITY }) { - add( - createLocalEvent( - type = EventType.STATE_ROOM_HISTORY_VISIBILITY, - content = RoomHistoryVisibilityContent(RoomHistoryVisibility.SHARED.value).toContent(), - stateKey = "" - ) - ) - } - // GuestAccess - if (none { it.type == EventType.STATE_ROOM_GUEST_ACCESS }) { - add( - createLocalEvent( - type = EventType.STATE_ROOM_GUEST_ACCESS, - content = RoomGuestAccessContent(GuestAccess.Forbidden.value).toContent(), - stateKey = "" - ) - ) - } - } - - /** - * Generate a local event from the given parameters. - * - * @param type the event type, see [EventType] - * @param content the content of the Event - * @param stateKey the stateKey, if any - * - * @return a local event - */ - private fun createLocalEvent(type: String?, content: Content?, stateKey: String?): Event { - return Event( - type = type, - senderId = myUserId, - stateKey = stateKey, - content = content, - originServerTs = clock.epochMillis(), - eventId = LocalEcho.createLocalEchoId() - ) - } } From e22ce0d842b36d61d014d3325f79b9951e8d5890 Mon Sep 17 00:00:00 2001 From: Florian Renaud Date: Wed, 10 Aug 2022 01:07:54 +0200 Subject: [PATCH 08/20] Set stateKey as empty by default --- .../create/CreateLocalRoomStateEventsTask.kt | 43 ++++++++----------- 1 file changed, 18 insertions(+), 25 deletions(-) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/create/CreateLocalRoomStateEventsTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/create/CreateLocalRoomStateEventsTask.kt index 45980514dd..21b8e3a5d8 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/create/CreateLocalRoomStateEventsTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/create/CreateLocalRoomStateEventsTask.kt @@ -92,7 +92,7 @@ internal class DefaultCreateLocalRoomStateEventsTask @Inject constructor( * Generate the create state event related to this room. */ private fun MutableList.createRoomCreateEvent() = apply { - val roomCreateEvent = createLocalEvent( + val roomCreateEvent = createLocalStateEvent( type = EventType.STATE_ROOM_CREATE, content = RoomCreateContent( creator = roomCreatorUserId, @@ -100,7 +100,6 @@ internal class DefaultCreateLocalRoomStateEventsTask @Inject constructor( type = (createRoomBody.creationContent as? Map<*, *>)?.get(CreateRoomParams.CREATION_CONTENT_KEY_ROOM_TYPE) as? String ).toContent(), - stateKey = "" ) add(roomCreateEvent) } @@ -110,7 +109,7 @@ internal class DefaultCreateLocalRoomStateEventsTask @Inject constructor( * Ref: https://spec.matrix.org/latest/client-server-api/#mroompower_levels */ private fun MutableList.createRoomPowerLevelsEvent() = apply { - val powerLevelsContent = createLocalEvent( + val powerLevelsContent = createLocalStateEvent( type = EventType.STATE_ROOM_POWER_LEVELS, content = (createRoomBody.powerLevelContentOverride ?: PowerLevelsContent()).let { it.copy( @@ -123,7 +122,6 @@ internal class DefaultCreateLocalRoomStateEventsTask @Inject constructor( usersDefault = it.usersDefaultOrDefault(), ) }.toContent(), - stateKey = "" ) add(powerLevelsContent) } @@ -135,7 +133,7 @@ internal class DefaultCreateLocalRoomStateEventsTask @Inject constructor( val memberEvents = userIds .mapNotNull { tryOrNull { userService.resolveUser(it) } } .map { user -> - createLocalEvent( + createLocalStateEvent( type = EventType.STATE_ROOM_MEMBER, content = RoomMemberContent( isDirect = createRoomBody.isDirect.takeUnless { user.userId == roomCreatorUserId }.orFalse(), @@ -154,7 +152,7 @@ internal class DefaultCreateLocalRoomStateEventsTask @Inject constructor( */ private fun MutableList.createRoomThreePidEvents() = apply { val threePidEvents = createRoomBody.invite3pids.orEmpty().map { body -> - val localThirdPartyInviteEvent = createLocalEvent( + val localThirdPartyInviteEvent = createLocalStateEvent( type = EventType.LOCAL_STATE_ROOM_THIRD_PARTY_INVITE, content = LocalRoomThirdPartyInviteContent( isDirect = createRoomBody.isDirect.orFalse(), @@ -162,12 +160,10 @@ internal class DefaultCreateLocalRoomStateEventsTask @Inject constructor( displayName = body.address, thirdPartyInvite = body.toThreePid() ).toContent(), - stateKey = "" ) - val thirdPartyInviteEvent = createLocalEvent( + val thirdPartyInviteEvent = createLocalStateEvent( type = EventType.STATE_ROOM_THIRD_PARTY_INVITE, content = RoomThirdPartyInviteContent(body.address, null, null, null).toContent(), - stateKey = "" ) listOf(localThirdPartyInviteEvent, thirdPartyInviteEvent) }.flatten() @@ -179,12 +175,11 @@ internal class DefaultCreateLocalRoomStateEventsTask @Inject constructor( */ fun MutableList.createRoomAliasEvent() = apply { if (createRoomBody.roomAliasName != null) { - val canonicalAliasContent = createLocalEvent( + val canonicalAliasContent = createLocalStateEvent( type = EventType.STATE_ROOM_CANONICAL_ALIAS, content = RoomCanonicalAliasContent( canonicalAlias = "${createRoomBody.roomAliasName}:${roomCreatorUserId.getServerName()}" ).toContent(), - stateKey = "" ) add(canonicalAliasContent) } @@ -214,9 +209,9 @@ internal class DefaultCreateLocalRoomStateEventsTask @Inject constructor( } } - add(createLocalEvent(EventType.STATE_ROOM_JOIN_RULES, RoomJoinRulesContent(joinRules.value).toContent(), "")) - add(createLocalEvent(EventType.STATE_ROOM_HISTORY_VISIBILITY, RoomHistoryVisibilityContent(historyVisibility.value).toContent(), "")) - add(createLocalEvent(EventType.STATE_ROOM_GUEST_ACCESS, RoomGuestAccessContent(guestAccess.value).toContent(), "")) + add(createLocalStateEvent(EventType.STATE_ROOM_JOIN_RULES, RoomJoinRulesContent(joinRules.value).toContent())) + add(createLocalStateEvent(EventType.STATE_ROOM_HISTORY_VISIBILITY, RoomHistoryVisibilityContent(historyVisibility.value).toContent())) + add(createLocalStateEvent(EventType.STATE_ROOM_GUEST_ACCESS, RoomGuestAccessContent(guestAccess.value).toContent())) } /** @@ -226,7 +221,7 @@ internal class DefaultCreateLocalRoomStateEventsTask @Inject constructor( private fun MutableList.createRoomInitialStateEvents() = apply { val initialStates = createRoomBody.initialStates ?: return@apply - val initialStateEvents = initialStates.map { createLocalEvent(it.type, it.content, it.stateKey) } + val initialStateEvents = initialStates.map { createLocalStateEvent(it.type, it.content, it.stateKey) } // Erase existing events of the same type removeAll { event -> event.type in initialStateEvents.map { it.type } } // Add the initial state events to the list @@ -238,10 +233,10 @@ internal class DefaultCreateLocalRoomStateEventsTask @Inject constructor( */ private fun MutableList.createRoomNameAndTopicStateEvents() = apply { if (createRoomBody.name != null) { - add(createLocalEvent(EventType.STATE_ROOM_NAME, RoomNameContent(createRoomBody.name).toContent(), "")) + add(createLocalStateEvent(EventType.STATE_ROOM_NAME, RoomNameContent(createRoomBody.name).toContent())) } if (createRoomBody.topic != null) { - add(createLocalEvent(EventType.STATE_ROOM_TOPIC, RoomTopicContent(createRoomBody.topic).toContent(), "")) + add(createLocalStateEvent(EventType.STATE_ROOM_TOPIC, RoomTopicContent(createRoomBody.topic).toContent())) } } @@ -255,35 +250,33 @@ internal class DefaultCreateLocalRoomStateEventsTask @Inject constructor( // HistoryVisibility if (none { it.type == EventType.STATE_ROOM_HISTORY_VISIBILITY }) { add( - createLocalEvent( + createLocalStateEvent( type = EventType.STATE_ROOM_HISTORY_VISIBILITY, content = RoomHistoryVisibilityContent(RoomHistoryVisibility.SHARED.value).toContent(), - stateKey = "" ) ) } // GuestAccess if (none { it.type == EventType.STATE_ROOM_GUEST_ACCESS }) { add( - createLocalEvent( + createLocalStateEvent( type = EventType.STATE_ROOM_GUEST_ACCESS, content = RoomGuestAccessContent(GuestAccess.Forbidden.value).toContent(), - stateKey = "" ) ) } } /** - * Generate a local event from the given parameters. + * Generate a local state event from the given parameters. * * @param type the event type, see [EventType] - * @param content the content of the Event + * @param content the content of the event * @param stateKey the stateKey, if any * - * @return a local event + * @return a local state event */ - private fun createLocalEvent(type: String?, content: Content?, stateKey: String?): Event { + private fun createLocalStateEvent(type: String?, content: Content?, stateKey: String? = ""): Event { return Event( type = type, senderId = roomCreatorUserId, From 882065f6cdb08d8ee3c3710ee59df3ee33852959 Mon Sep 17 00:00:00 2001 From: Florian Renaud Date: Tue, 16 Aug 2022 10:51:11 +0200 Subject: [PATCH 09/20] Add unit tests for CreateLocalRoomStateEventsTask --- ...faultCreateLocalRoomStateEventsTaskTest.kt | 463 ++++++++++++++++++ 1 file changed, 463 insertions(+) create mode 100644 matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/session/room/create/DefaultCreateLocalRoomStateEventsTaskTest.kt diff --git a/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/session/room/create/DefaultCreateLocalRoomStateEventsTaskTest.kt b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/session/room/create/DefaultCreateLocalRoomStateEventsTaskTest.kt new file mode 100644 index 0000000000..383bcac30c --- /dev/null +++ b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/session/room/create/DefaultCreateLocalRoomStateEventsTaskTest.kt @@ -0,0 +1,463 @@ +/* + * 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.create + +import io.mockk.coEvery +import io.mockk.every +import io.mockk.mockk +import io.mockk.unmockkAll +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.test.runTest +import org.amshove.kluent.shouldBeEqualTo +import org.amshove.kluent.shouldNotBeNull +import org.junit.After +import org.junit.Before +import org.junit.Test +import org.matrix.android.sdk.api.MatrixPatterns.getServerName +import org.matrix.android.sdk.api.crypto.MXCRYPTO_ALGORITHM_MEGOLM +import org.matrix.android.sdk.api.extensions.orFalse +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.EncryptionEventContent +import org.matrix.android.sdk.api.session.events.model.toContent +import org.matrix.android.sdk.api.session.events.model.toModel +import org.matrix.android.sdk.api.session.identity.ThreePid +import org.matrix.android.sdk.api.session.room.model.GuestAccess +import org.matrix.android.sdk.api.session.room.model.Membership +import org.matrix.android.sdk.api.session.room.model.PowerLevelsContent +import org.matrix.android.sdk.api.session.room.model.RoomCanonicalAliasContent +import org.matrix.android.sdk.api.session.room.model.RoomGuestAccessContent +import org.matrix.android.sdk.api.session.room.model.RoomHistoryVisibility +import org.matrix.android.sdk.api.session.room.model.RoomHistoryVisibilityContent +import org.matrix.android.sdk.api.session.room.model.RoomJoinRules +import org.matrix.android.sdk.api.session.room.model.RoomJoinRulesContent +import org.matrix.android.sdk.api.session.room.model.RoomMemberContent +import org.matrix.android.sdk.api.session.room.model.RoomNameContent +import org.matrix.android.sdk.api.session.room.model.RoomThirdPartyInviteContent +import org.matrix.android.sdk.api.session.room.model.RoomTopicContent +import org.matrix.android.sdk.api.session.room.model.create.CreateRoomPreset +import org.matrix.android.sdk.api.session.room.model.create.RoomCreateContent +import org.matrix.android.sdk.api.session.room.model.localecho.LocalRoomThirdPartyInviteContent +import org.matrix.android.sdk.api.session.room.powerlevels.Role +import org.matrix.android.sdk.api.session.user.UserService +import org.matrix.android.sdk.api.session.user.model.User +import org.matrix.android.sdk.internal.session.profile.ThirdPartyIdentifier.Companion.MEDIUM_EMAIL +import org.matrix.android.sdk.internal.session.profile.ThirdPartyIdentifier.Companion.MEDIUM_MSISDN +import org.matrix.android.sdk.internal.session.room.membership.threepid.ThreePidInviteBody +import org.matrix.android.sdk.internal.session.room.membership.threepid.toThreePid +import org.matrix.android.sdk.internal.util.time.DefaultClock + +private const val MY_USER_ID = "my-user-id" +private const val MY_USER_DISPLAY_NAME = "my-user-display-name" +private const val MY_USER_AVATAR = "my-user-avatar" + +@ExperimentalCoroutinesApi +internal class DefaultCreateLocalRoomStateEventsTaskTest { + + private val clock = DefaultClock() + private val userService = mockk() + + private val defaultCreateLocalRoomStateEventsTask = DefaultCreateLocalRoomStateEventsTask( + userService = userService, + clock = clock + ) + + lateinit var createRoomBody: CreateRoomBody + + @Before + fun setup() { + createRoomBody = mockk { + every { roomVersion } returns null + every { creationContent } returns null + every { roomAliasName } returns null + every { topic } returns null + every { name } returns null + every { powerLevelContentOverride } returns null + every { initialStates } returns null + every { invite3pids } returns null + every { preset } returns null + every { isDirect } returns null + every { invitedUserIds } returns null + } + coEvery { userService.resolveUser(any()) } answers { User(firstArg()) } + } + + @After + fun tearDown() { + unmockkAll() + } + + @Test + fun `given a CreateRoomBody when execute then the resulting list of events contains the correct room create state event`() = runTest { + // Given + val aRoomCreator = MY_USER_ID + val aRoomVersion = "a_room_version" + + every { createRoomBody.roomVersion } returns aRoomVersion + + // When + val params = CreateLocalRoomStateEventsTask.Params(aRoomCreator, createRoomBody) + val result = defaultCreateLocalRoomStateEventsTask.execute(params) + + // Then + val roomCreateEvent = result.find { it.type == EventType.STATE_ROOM_CREATE } + val roomCreateContent = roomCreateEvent?.content.toModel() + + roomCreateContent?.creator shouldBeEqualTo aRoomCreator + roomCreateContent?.roomVersion shouldBeEqualTo aRoomVersion + } + + @Test + fun `given a CreateRoomBody when execute then the resulting list of events contains the correct name and topic state events`() = runTest { + // Given + val aRoomName = "a_room_name" + val aRoomTopic = "a_room_topic" + + every { createRoomBody.name } returns aRoomName + every { createRoomBody.topic } returns aRoomTopic + + // When + val params = CreateLocalRoomStateEventsTask.Params(MY_USER_ID, createRoomBody) + val result = defaultCreateLocalRoomStateEventsTask.execute(params) + + // Then + val roomNameEvent = result.find { it.type == EventType.STATE_ROOM_NAME } + val roomTopicEvent = result.find { it.type == EventType.STATE_ROOM_TOPIC } + + roomNameEvent?.content.toModel()?.name shouldBeEqualTo aRoomName + roomTopicEvent?.content.toModel()?.topic shouldBeEqualTo aRoomTopic + } + + @Test + fun `given a CreateRoomBody when execute then the resulting list of events contains the correct room member events`() = runTest { + // Given + data class RoomMember(val user: User, val membership: Membership) + + val aRoomMemberList: List = listOf( + RoomMember(User(MY_USER_ID, MY_USER_DISPLAY_NAME, MY_USER_AVATAR), Membership.JOIN), + RoomMember(User("userA_id", "userA_display_name", "userA_avatar"), Membership.INVITE), + RoomMember(User("userB_id", "userB_display_name", "userB_avatar"), Membership.INVITE) + ) + + every { createRoomBody.invitedUserIds } returns aRoomMemberList.filter { it.membership == Membership.INVITE }.map { it.user.userId } + coEvery { userService.resolveUser(any()) } answers { + aRoomMemberList.map { it.user }.find { it.userId == firstArg() } ?: User(firstArg()) + } + + // When + val params = CreateLocalRoomStateEventsTask.Params(MY_USER_ID, createRoomBody) + val result = defaultCreateLocalRoomStateEventsTask.execute(params) + + // Then + val roomMemberEvents = result.filter { it.type == EventType.STATE_ROOM_MEMBER } + + roomMemberEvents.map { it.stateKey } shouldBeEqualTo aRoomMemberList.map { it.user.userId } + roomMemberEvents.forEach { event -> + val roomMemberContent = event.content.toModel() + val roomMember = aRoomMemberList.find { it.user.userId == event.stateKey } + + roomMember.shouldNotBeNull() + roomMemberContent?.avatarUrl shouldBeEqualTo roomMember.user.avatarUrl + roomMemberContent?.displayName shouldBeEqualTo roomMember.user.displayName + roomMemberContent?.membership shouldBeEqualTo roomMember.membership + } + } + + @Test + fun `given a CreateRoomBody when execute then the resulting list of events contains the correct power levels event`() = runTest { + // Given + val aPowerLevelsContent = PowerLevelsContent( + ban = 1, + kick = 2, + invite = 3, + redact = 4, + eventsDefault = 5, + events = null, + usersDefault = 6, + users = null, + stateDefault = 7, + notifications = null + ) + + every { createRoomBody.powerLevelContentOverride } returns aPowerLevelsContent + + // When + val params = CreateLocalRoomStateEventsTask.Params(MY_USER_ID, createRoomBody) + val result = defaultCreateLocalRoomStateEventsTask.execute(params) + + // Then + val roomPowerLevelsEvent = result.find { it.type == EventType.STATE_ROOM_POWER_LEVELS } + roomPowerLevelsEvent?.content.toModel() shouldBeEqualTo aPowerLevelsContent + } + + @Test + fun `given a CreateRoomBody when execute then the resulting list of events contains the correct canonical alias event`() = runTest { + // Given + val aRoomAlias = "a_room_alias" + val expectedCanonicalAlias = "$aRoomAlias:${MY_USER_ID.getServerName()}" + + every { createRoomBody.roomAliasName } returns aRoomAlias + + // When + val params = CreateLocalRoomStateEventsTask.Params(MY_USER_ID, createRoomBody) + val result = defaultCreateLocalRoomStateEventsTask.execute(params) + + // Then + val roomPowerLevelsEvent = result.find { it.type == EventType.STATE_ROOM_CANONICAL_ALIAS } + roomPowerLevelsEvent?.content.toModel()?.canonicalAlias shouldBeEqualTo expectedCanonicalAlias + } + + @Test + fun `given a CreateRoomBody when execute then the resulting list of events contains the correct preset related events`() = runTest { + data class ExpectedResult(val joinRules: RoomJoinRules, val historyVisibility: RoomHistoryVisibility, val guestAccess: GuestAccess) + data class Case(val preset: CreateRoomPreset, val expectedResult: ExpectedResult) + + CreateRoomPreset.values().forEach { aRoomPreset -> + // Given + val case = when (aRoomPreset) { + CreateRoomPreset.PRESET_PRIVATE_CHAT -> Case( + CreateRoomPreset.PRESET_PRIVATE_CHAT, + ExpectedResult(RoomJoinRules.INVITE, RoomHistoryVisibility.SHARED, GuestAccess.CanJoin) + ) + CreateRoomPreset.PRESET_TRUSTED_PRIVATE_CHAT -> Case( + CreateRoomPreset.PRESET_TRUSTED_PRIVATE_CHAT, + ExpectedResult(RoomJoinRules.INVITE, RoomHistoryVisibility.SHARED, GuestAccess.CanJoin) + ) + CreateRoomPreset.PRESET_PUBLIC_CHAT -> Case( + CreateRoomPreset.PRESET_PUBLIC_CHAT, + ExpectedResult(RoomJoinRules.PUBLIC, RoomHistoryVisibility.SHARED, GuestAccess.Forbidden) + ) + } + every { createRoomBody.preset } returns case.preset + + // When + val params = CreateLocalRoomStateEventsTask.Params(MY_USER_ID, createRoomBody) + val result = defaultCreateLocalRoomStateEventsTask.execute(params) + + // Then + result.find { it.type == EventType.STATE_ROOM_JOIN_RULES } + ?.content.toModel() + ?.joinRules shouldBeEqualTo case.expectedResult.joinRules + result.find { it.type == EventType.STATE_ROOM_HISTORY_VISIBILITY } + ?.content.toModel() + ?.historyVisibility shouldBeEqualTo case.expectedResult.historyVisibility + result.find { it.type == EventType.STATE_ROOM_GUEST_ACCESS } + ?.content.toModel() + ?.guestAccess shouldBeEqualTo case.expectedResult.guestAccess + } + } + + @Test + fun `given a CreateRoomBody when execute then the resulting list of events contains the initial state events`() = runTest { + // Given + val aListOfInitialStateEvents = listOf( + Event( + type = EventType.STATE_ROOM_ENCRYPTION, + stateKey = "", + content = EncryptionEventContent(MXCRYPTO_ALGORITHM_MEGOLM).toContent() + ), + Event( + type = "a_custom_type", + content = mapOf("a_custom_map_to_integer" to 42), + stateKey = "a_state_key" + ), + Event( + type = "another_custom_type", + content = mapOf("a_custom_map_to_boolean" to false), + stateKey = "another_state_key" + ) + ) + + every { createRoomBody.initialStates } returns aListOfInitialStateEvents + + // When + val params = CreateLocalRoomStateEventsTask.Params(MY_USER_ID, createRoomBody) + val result = defaultCreateLocalRoomStateEventsTask.execute(params) + + // Then + aListOfInitialStateEvents.forEach { expected -> + val found = result.find { it.type == expected.type } + found.shouldNotBeNull() + found.content shouldBeEqualTo expected.content + found.stateKey shouldBeEqualTo expected.stateKey + } + } + + @Test + fun `given a CreateRoomBody when execute then the resulting list of events contains the correct third party invite events`() = runTest { + // Given + val aListOfThreePids = listOf( + ThreePid.Email("bob@matrix.org"), + ThreePid.Msisdn("+11111111111"), + ThreePid.Email("alice@matrix.org"), + ThreePid.Msisdn("+22222222222"), + ) + val aListOf3pids = aListOfThreePids.mapIndexed { index, threePid -> + ThreePidInviteBody( + idServer = "an_id_server_$index", + idAccessToken = "an_id_access_token_$index", + medium = when (threePid) { + is ThreePid.Email -> MEDIUM_EMAIL + is ThreePid.Msisdn -> MEDIUM_MSISDN + }, + address = threePid.value + ) + } + every { createRoomBody.invite3pids } returns aListOf3pids + + // When + val params = CreateLocalRoomStateEventsTask.Params(MY_USER_ID, createRoomBody) + val result = defaultCreateLocalRoomStateEventsTask.execute(params) + + // Then + val thirdPartyInviteEvents = result.filter { it.type == EventType.STATE_ROOM_THIRD_PARTY_INVITE } + val thirdPartyInviteContents = thirdPartyInviteEvents.map { it.content.toModel() } + val localThirdPartyInviteEvents = result.filter { it.type == EventType.LOCAL_STATE_ROOM_THIRD_PARTY_INVITE } + val localThirdPartyInviteContents = localThirdPartyInviteEvents.map { it.content.toModel() } + + thirdPartyInviteEvents.size shouldBeEqualTo aListOf3pids.size + localThirdPartyInviteEvents.size shouldBeEqualTo aListOf3pids.size + + aListOf3pids.forEach { expected -> + thirdPartyInviteContents.find { it?.displayName == expected.address }.shouldNotBeNull() + + val localThirdPartyInviteContent = localThirdPartyInviteContents.find { it?.thirdPartyInvite == expected.toThreePid() } + localThirdPartyInviteContent.shouldNotBeNull() + localThirdPartyInviteContent.membership shouldBeEqualTo Membership.INVITE + localThirdPartyInviteContent.isDirect shouldBeEqualTo createRoomBody.isDirect.orFalse() + localThirdPartyInviteContent.displayName shouldBeEqualTo expected.address + } + } + + @Test + fun `given a CreateRoomBody with default values when execute then the resulting list of events is correct`() = runTest { + // Given + // map of expected event types to occurrences + val expectedEventTypes = mapOf( + EventType.STATE_ROOM_CREATE to 1, + EventType.STATE_ROOM_POWER_LEVELS to 1, + EventType.STATE_ROOM_MEMBER to 1, + EventType.STATE_ROOM_GUEST_ACCESS to 1, + EventType.STATE_ROOM_HISTORY_VISIBILITY to 1, + ) + coEvery { userService.resolveUser(any()) } answers { + if (firstArg() == MY_USER_ID) User(MY_USER_ID, MY_USER_DISPLAY_NAME, MY_USER_AVATAR) else User(firstArg()) + } + + // When + val params = CreateLocalRoomStateEventsTask.Params(MY_USER_ID, createRoomBody) + val result = defaultCreateLocalRoomStateEventsTask.execute(params) + + // Then + result.size shouldBeEqualTo expectedEventTypes.values.sum() + result.map { it.type }.toSet() shouldBeEqualTo expectedEventTypes.keys + + // Room create + result.find { it.type == EventType.STATE_ROOM_CREATE }.shouldNotBeNull() + // Room member + result.singleOrNull { it.type == EventType.STATE_ROOM_MEMBER }?.stateKey shouldBeEqualTo MY_USER_ID + // Power levels + val powerLevelsContent = result.find { it.type == EventType.STATE_ROOM_POWER_LEVELS }?.content.toModel() + powerLevelsContent.shouldNotBeNull() + powerLevelsContent.ban shouldBeEqualTo Role.Moderator.value + powerLevelsContent.kick shouldBeEqualTo Role.Moderator.value + powerLevelsContent.invite shouldBeEqualTo Role.Moderator.value + powerLevelsContent.redact shouldBeEqualTo Role.Moderator.value + powerLevelsContent.eventsDefault shouldBeEqualTo Role.Default.value + powerLevelsContent.usersDefault shouldBeEqualTo Role.Default.value + powerLevelsContent.stateDefault shouldBeEqualTo Role.Moderator.value + // Guest access + result.find { it.type == EventType.STATE_ROOM_GUEST_ACCESS } + ?.content.toModel()?.guestAccess shouldBeEqualTo GuestAccess.Forbidden + // History visibility + result.find { it.type == EventType.STATE_ROOM_HISTORY_VISIBILITY } + ?.content.toModel()?.historyVisibility shouldBeEqualTo RoomHistoryVisibility.SHARED + } + + @Test + fun `given a CreateRoomBody when execute then the resulting list of events is correctly ordered with the right values`() = runTest { + // Given + val expectedIsDirect = true + val expectedHistoryVisibility = RoomHistoryVisibility.WORLD_READABLE + + every { createRoomBody.roomVersion } returns "a_room_version" + every { createRoomBody.roomAliasName } returns "a_room_alias_name" + every { createRoomBody.name } returns "a_name" + every { createRoomBody.topic } returns "a_topic" + every { createRoomBody.powerLevelContentOverride } returns PowerLevelsContent( + ban = 1, + kick = 2, + invite = 3, + redact = 4, + eventsDefault = 5, + events = null, + usersDefault = 6, + users = null, + stateDefault = 7, + notifications = null + ) + every { createRoomBody.invite3pids } returns listOf( + ThreePidInviteBody( + idServer = "an_id_server", + idAccessToken = "an_id_access_token", + medium = MEDIUM_EMAIL, + address = "an_email@example.org" + ) + ) + every { createRoomBody.preset } returns CreateRoomPreset.PRESET_TRUSTED_PRIVATE_CHAT + every { createRoomBody.initialStates } returns listOf( + Event(type = "a_custom_type", stateKey = ""), + // override the value from the preset + Event( + type = EventType.STATE_ROOM_HISTORY_VISIBILITY, + stateKey = "", + content = RoomHistoryVisibilityContent(expectedHistoryVisibility.value).toContent() + ) + ) + every { createRoomBody.isDirect } returns expectedIsDirect + every { createRoomBody.invitedUserIds } returns listOf("a_user_id") + + val orderedExpectedEventType = listOf( + EventType.STATE_ROOM_CREATE, + EventType.STATE_ROOM_MEMBER, + EventType.STATE_ROOM_POWER_LEVELS, + EventType.STATE_ROOM_CANONICAL_ALIAS, + EventType.STATE_ROOM_JOIN_RULES, + EventType.STATE_ROOM_GUEST_ACCESS, + "a_custom_type", + EventType.STATE_ROOM_HISTORY_VISIBILITY, + EventType.STATE_ROOM_NAME, + EventType.STATE_ROOM_TOPIC, + EventType.STATE_ROOM_MEMBER, + EventType.LOCAL_STATE_ROOM_THIRD_PARTY_INVITE, + EventType.STATE_ROOM_THIRD_PARTY_INVITE, + ) + + // When + val params = CreateLocalRoomStateEventsTask.Params(MY_USER_ID, createRoomBody) + val result = defaultCreateLocalRoomStateEventsTask.execute(params) + + // Then + result.map { it.type } shouldBeEqualTo orderedExpectedEventType + result.find { it.type == EventType.STATE_ROOM_HISTORY_VISIBILITY } + ?.content.toModel()?.historyVisibility shouldBeEqualTo expectedHistoryVisibility + result.lastOrNull { it.type == EventType.STATE_ROOM_MEMBER } + ?.content.toModel()?.isDirect shouldBeEqualTo expectedIsDirect + result.lastOrNull { it.type == EventType.LOCAL_STATE_ROOM_THIRD_PARTY_INVITE } + ?.content.toModel()?.isDirect shouldBeEqualTo expectedIsDirect + } +} From 3905e564bd571d64b1820d93207430d44096298a Mon Sep 17 00:00:00 2001 From: Florian Renaud Date: Wed, 17 Aug 2022 11:41:17 +0200 Subject: [PATCH 10/20] Add unit tests for CreateRoomFromLocalRoomTask --- .../DefaultCreateRoomFromLocalRoomTaskTest.kt | 147 ++++++++++++++++++ .../android/sdk/test/fakes/FakeMonarchy.kt | 8 +- 2 files changed, 154 insertions(+), 1 deletion(-) create mode 100644 matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/session/room/create/DefaultCreateRoomFromLocalRoomTaskTest.kt diff --git a/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/session/room/create/DefaultCreateRoomFromLocalRoomTaskTest.kt b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/session/room/create/DefaultCreateRoomFromLocalRoomTaskTest.kt new file mode 100644 index 0000000000..3bb161f69e --- /dev/null +++ b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/session/room/create/DefaultCreateRoomFromLocalRoomTaskTest.kt @@ -0,0 +1,147 @@ +/* + * 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.create + +import io.mockk.coEvery +import io.mockk.coJustRun +import io.mockk.coVerify +import io.mockk.every +import io.mockk.mockk +import io.mockk.mockkStatic +import io.mockk.unmockkAll +import io.realm.kotlin.where +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.test.runTest +import org.amshove.kluent.shouldBeEqualTo +import org.junit.After +import org.junit.Before +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.toContent +import org.matrix.android.sdk.api.session.room.model.create.CreateRoomParams +import org.matrix.android.sdk.api.session.room.model.tombstone.RoomTombstoneContent +import org.matrix.android.sdk.internal.database.awaitNotEmptyResult +import org.matrix.android.sdk.internal.database.model.CurrentStateEventEntity +import org.matrix.android.sdk.internal.database.model.EventEntity +import org.matrix.android.sdk.internal.database.model.LocalRoomSummaryEntity +import org.matrix.android.sdk.internal.database.model.LocalRoomSummaryEntityFields +import org.matrix.android.sdk.internal.database.query.copyToRealmOrIgnore +import org.matrix.android.sdk.internal.database.query.getOrCreate +import org.matrix.android.sdk.internal.util.time.DefaultClock +import org.matrix.android.sdk.test.fakes.FakeMonarchy +import org.matrix.android.sdk.test.fakes.FakeStateEventDataSource + +private const val A_LOCAL_ROOM_ID = "local.a-local-room-id" +private const val AN_EXISTING_ROOM_ID = "an-existing-room-id" +private const val A_ROOM_ID = "a-room-id" +private const val MY_USER_ID = "my-user-id" + +@ExperimentalCoroutinesApi +internal class DefaultCreateRoomFromLocalRoomTaskTest { + + private val fakeMonarchy = FakeMonarchy() + private val clock = DefaultClock() + private val createRoomTask = mockk() + private val fakeStateEventDataSource = FakeStateEventDataSource() + + private val defaultCreateRoomFromLocalRoomTask = DefaultCreateRoomFromLocalRoomTask( + userId = MY_USER_ID, + monarchy = fakeMonarchy.instance, + createRoomTask = createRoomTask, + stateEventDataSource = fakeStateEventDataSource.instance, + clock = clock + ) + + @Before + fun setup() { + mockkStatic("org.matrix.android.sdk.internal.database.RealmQueryLatchKt") + coJustRun { awaitNotEmptyResult(realmConfiguration = any(), timeoutMillis = any(), builder = any()) } + + mockkStatic("org.matrix.android.sdk.internal.database.query.EventEntityQueriesKt") + coEvery { any().copyToRealmOrIgnore(fakeMonarchy.fakeRealm.instance, any()) } answers { firstArg() } + + mockkStatic("org.matrix.android.sdk.internal.database.query.CurrentStateEventEntityQueriesKt") + every { CurrentStateEventEntity.getOrCreate(fakeMonarchy.fakeRealm.instance, any(), any(), any()) } answers { + CurrentStateEventEntity(roomId = arg(2), stateKey = arg(3), type = arg(4)) + } + } + + @After + fun tearDown() { + unmockkAll() + } + + @Test + fun `given a local room id when execute then the existing room id is kept`() = runTest { + // Given + givenATombstoneEvent( + Event( + roomId = A_LOCAL_ROOM_ID, + type = EventType.STATE_ROOM_TOMBSTONE, + stateKey = "", + content = RoomTombstoneContent(replacementRoomId = AN_EXISTING_ROOM_ID).toContent() + ) + ) + + // When + val params = CreateRoomFromLocalRoomTask.Params(A_LOCAL_ROOM_ID) + val result = defaultCreateRoomFromLocalRoomTask.execute(params) + + // Then + result shouldBeEqualTo AN_EXISTING_ROOM_ID + } + + @Test + fun `given a local room id when execute then it is correctly executed`() = runTest { + // Given + val aCreateRoomParams = mockk() + val aLocalRoomSummaryEntity = mockk { + every { roomSummaryEntity } returns mockk(relaxed = true) + every { createRoomParams } returns aCreateRoomParams + } + givenATombstoneEvent(null) + givenALocalRoomSummaryEntity(aLocalRoomSummaryEntity) + + coEvery { createRoomTask.execute(any()) } returns A_ROOM_ID + + // When + val params = CreateRoomFromLocalRoomTask.Params(A_LOCAL_ROOM_ID) + val result = defaultCreateRoomFromLocalRoomTask.execute(params) + + // Then + // CreateRoomTask has been called with the initial CreateRoomParams + coVerify { createRoomTask.execute(aCreateRoomParams) } + // The resulting roomId matches the roomId returned by the createRoomTask + result shouldBeEqualTo A_ROOM_ID + // A tombstone state event has been created + coVerify { CurrentStateEventEntity.getOrCreate(realm = any(), roomId = A_LOCAL_ROOM_ID, stateKey = any(), type = EventType.STATE_ROOM_TOMBSTONE) } + } + + private fun givenATombstoneEvent(event: Event?) { + fakeStateEventDataSource.givenGetStateEventReturns(event) + } + + private fun givenALocalRoomSummaryEntity(localRoomSummaryEntity: LocalRoomSummaryEntity) { + every { + fakeMonarchy.fakeRealm.instance + .where() + .equalTo(LocalRoomSummaryEntityFields.ROOM_ID, A_LOCAL_ROOM_ID) + .findFirst() + } returns localRoomSummaryEntity + } +} diff --git a/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fakes/FakeMonarchy.kt b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fakes/FakeMonarchy.kt index d77084fe3b..2d501f12af 100644 --- a/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fakes/FakeMonarchy.kt +++ b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fakes/FakeMonarchy.kt @@ -33,7 +33,7 @@ import org.matrix.android.sdk.internal.util.awaitTransaction internal class FakeMonarchy { val instance = mockk() - private val fakeRealm = FakeRealm() + val fakeRealm = FakeRealm() init { mockkStatic("org.matrix.android.sdk.internal.util.MonarchyKt") @@ -42,6 +42,12 @@ internal class FakeMonarchy { } coAnswers { secondArg Any>().invoke(fakeRealm.instance) } + coEvery { + instance.doWithRealm(any()) + } coAnswers { + firstArg().doWithRealm(fakeRealm.instance) + } + every { instance.realmConfiguration } returns mockk() } inline fun givenWhere(): RealmQuery { From 725537d8fe1159f690b5beb69a07598c973c54ba Mon Sep 17 00:00:00 2001 From: Florian Renaud Date: Wed, 17 Aug 2022 16:19:33 +0200 Subject: [PATCH 11/20] Remove safe call --- .../internal/session/room/create/CreateRoomFromLocalRoomTask.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/create/CreateRoomFromLocalRoomTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/create/CreateRoomFromLocalRoomTask.kt index c59c56ffde..f861977dd1 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/create/CreateRoomFromLocalRoomTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/create/CreateRoomFromLocalRoomTask.kt @@ -76,7 +76,7 @@ internal class DefaultCreateRoomFromLocalRoomTask @Inject constructor( override suspend fun execute(params: CreateRoomFromLocalRoomTask.Params): String { val replacementRoomId = stateEventDataSource.getStateEvent(params.localRoomId, EventType.STATE_ROOM_TOMBSTONE, QueryStringValue.IsEmpty) - ?.content?.toModel() + ?.content.toModel() ?.replacementRoomId if (replacementRoomId != null) { From cbf9dbf290415bacaffdbc07ec767cc2268057d5 Mon Sep 17 00:00:00 2001 From: Florian Renaud Date: Wed, 17 Aug 2022 16:20:06 +0200 Subject: [PATCH 12/20] Verify tombstone event --- .../create/DefaultCreateRoomFromLocalRoomTaskTest.kt | 11 +++++++++++ .../DefaultGetActiveBeaconInfoForUserTaskTest.kt | 3 ++- .../sdk/test/fakes/FakeStateEventDataSource.kt | 6 +++--- 3 files changed, 16 insertions(+), 4 deletions(-) diff --git a/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/session/room/create/DefaultCreateRoomFromLocalRoomTaskTest.kt b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/session/room/create/DefaultCreateRoomFromLocalRoomTaskTest.kt index 3bb161f69e..d3732363b5 100644 --- a/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/session/room/create/DefaultCreateRoomFromLocalRoomTaskTest.kt +++ b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/session/room/create/DefaultCreateRoomFromLocalRoomTaskTest.kt @@ -30,9 +30,11 @@ import org.amshove.kluent.shouldBeEqualTo import org.junit.After import org.junit.Before import org.junit.Test +import org.matrix.android.sdk.api.query.QueryStringValue 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.toContent +import org.matrix.android.sdk.api.session.events.model.toModel import org.matrix.android.sdk.api.session.room.model.create.CreateRoomParams import org.matrix.android.sdk.api.session.room.model.tombstone.RoomTombstoneContent import org.matrix.android.sdk.internal.database.awaitNotEmptyResult @@ -103,6 +105,7 @@ internal class DefaultCreateRoomFromLocalRoomTaskTest { val result = defaultCreateRoomFromLocalRoomTask.execute(params) // Then + verifyTombstoneEvent(AN_EXISTING_ROOM_ID) result shouldBeEqualTo AN_EXISTING_ROOM_ID } @@ -124,6 +127,7 @@ internal class DefaultCreateRoomFromLocalRoomTaskTest { val result = defaultCreateRoomFromLocalRoomTask.execute(params) // Then + verifyTombstoneEvent(null) // CreateRoomTask has been called with the initial CreateRoomParams coVerify { createRoomTask.execute(aCreateRoomParams) } // The resulting roomId matches the roomId returned by the createRoomTask @@ -144,4 +148,11 @@ internal class DefaultCreateRoomFromLocalRoomTaskTest { .findFirst() } returns localRoomSummaryEntity } + + private fun verifyTombstoneEvent(expectedRoomId: String?) { + fakeStateEventDataSource.verifyGetStateEvent(A_LOCAL_ROOM_ID, EventType.STATE_ROOM_TOMBSTONE, QueryStringValue.IsEmpty) + fakeStateEventDataSource.instance.getStateEvent(A_LOCAL_ROOM_ID, EventType.STATE_ROOM_TOMBSTONE, QueryStringValue.IsEmpty) + ?.content.toModel() + ?.replacementRoomId shouldBeEqualTo expectedRoomId + } } diff --git a/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/session/room/location/DefaultGetActiveBeaconInfoForUserTaskTest.kt b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/session/room/location/DefaultGetActiveBeaconInfoForUserTaskTest.kt index 588bfaa979..d51ed77399 100644 --- a/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/session/room/location/DefaultGetActiveBeaconInfoForUserTaskTest.kt +++ b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/session/room/location/DefaultGetActiveBeaconInfoForUserTaskTest.kt @@ -22,6 +22,7 @@ import kotlinx.coroutines.test.runTest import org.amshove.kluent.shouldBeEqualTo import org.junit.After import org.junit.Test +import org.matrix.android.sdk.api.query.QueryStringValue 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.toContent @@ -69,7 +70,7 @@ class DefaultGetActiveBeaconInfoForUserTaskTest { fakeStateEventDataSource.verifyGetStateEvent( roomId = params.roomId, eventType = EventType.STATE_ROOM_BEACON_INFO.first(), - stateKey = A_USER_ID + stateKey = QueryStringValue.Equals(A_USER_ID) ) } } diff --git a/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fakes/FakeStateEventDataSource.kt b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fakes/FakeStateEventDataSource.kt index ca03316fa7..ebb2a1d7a0 100644 --- a/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fakes/FakeStateEventDataSource.kt +++ b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fakes/FakeStateEventDataSource.kt @@ -19,7 +19,7 @@ package org.matrix.android.sdk.test.fakes import io.mockk.every import io.mockk.mockk import io.mockk.verify -import org.matrix.android.sdk.api.query.QueryStringValue +import org.matrix.android.sdk.api.query.QueryStateEventValue import org.matrix.android.sdk.api.session.events.model.Event import org.matrix.android.sdk.internal.session.room.state.StateEventDataSource @@ -37,12 +37,12 @@ internal class FakeStateEventDataSource { } returns event } - fun verifyGetStateEvent(roomId: String, eventType: String, stateKey: String) { + fun verifyGetStateEvent(roomId: String, eventType: String, stateKey: QueryStateEventValue) { verify { instance.getStateEvent( roomId = roomId, eventType = eventType, - stateKey = QueryStringValue.Equals(stateKey) + stateKey = stateKey ) } } From 128ff0d6ec13edb7d2467986c27da6faf588868d Mon Sep 17 00:00:00 2001 From: Florian Renaud Date: Wed, 17 Aug 2022 16:36:52 +0200 Subject: [PATCH 13/20] Extract condition to reduce code complexity --- .../android/sdk/internal/crypto/tasks/SendEventTask.kt | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/tasks/SendEventTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/tasks/SendEventTask.kt index f51a5165c0..405757e3b3 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/tasks/SendEventTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/tasks/SendEventTask.kt @@ -46,8 +46,7 @@ internal class DefaultSendEventTask @Inject constructor( override suspend fun execute(params: SendEventTask.Params): String { try { - if (RoomLocalEcho.isLocalEchoId(params.event.roomId.orEmpty())) { - // Room is local, so create a real one and send the event to this new room + if (params.event.isLocalRoomEvent) { return createRoomAndSendEvent(params) } @@ -105,4 +104,7 @@ internal class DefaultSendEventTask @Inject constructor( } return params.event } + + private val Event.isLocalRoomEvent + get() = RoomLocalEcho.isLocalEchoId(roomId.orEmpty()) } From 5d1124aa9545e450444ad21d995d814473d2062c Mon Sep 17 00:00:00 2001 From: Florian Renaud Date: Wed, 17 Aug 2022 16:52:20 +0200 Subject: [PATCH 14/20] Update doc --- .../room/create/CreateLocalRoomStateEventsTask.kt | 9 +++++++++ .../session/room/create/CreateRoomFromLocalRoomTask.kt | 4 ++-- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/create/CreateLocalRoomStateEventsTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/create/CreateLocalRoomStateEventsTask.kt index 21b8e3a5d8..47af08292e 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/create/CreateLocalRoomStateEventsTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/create/CreateLocalRoomStateEventsTask.kt @@ -55,6 +55,13 @@ import org.matrix.android.sdk.internal.task.Task import org.matrix.android.sdk.internal.util.time.Clock import javax.inject.Inject +/** + * Generate a list of local state events from the given [CreateRoomBody]. + * The states events are generated according to the given configuration and following the matrix specification. + * This list reflects as much as possible a list of state events related to a real room configured and got from the server. + * + * Ref: https://spec.matrix.org/latest/client-server-api/#post_matrixclientv3createroom + */ internal interface CreateLocalRoomStateEventsTask : Task> { data class Params( val roomCreatorUserId: String, @@ -74,6 +81,8 @@ internal class DefaultCreateLocalRoomStateEventsTask @Inject constructor( createRoomBody = params.createRoomBody roomCreatorUserId = params.roomCreatorUserId + // Build the list of the state events following the priorities from the matrix specification + // Changing the order of the events might break the correct display of the room on the client side return buildList { createRoomCreateEvent() createRoomMemberEvents(listOf(roomCreatorUserId)) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/create/CreateRoomFromLocalRoomTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/create/CreateRoomFromLocalRoomTask.kt index f861977dd1..02538a5cc3 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/create/CreateRoomFromLocalRoomTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/create/CreateRoomFromLocalRoomTask.kt @@ -53,11 +53,11 @@ import java.util.concurrent.TimeUnit import javax.inject.Inject /** - * Create a Room from a "fake" local room. + * Create a room on the server from a local room. * The configuration of the local room will be use to configure the new room. * The potential local room members will also be invited to this new room. * - * A "fake" local tombstone event will be created to indicate that the local room has been replacing by the new one. + * A local tombstone event will be created to indicate that the local room has been replacing by the new one. */ internal interface CreateRoomFromLocalRoomTask : Task { data class Params(val localRoomId: String) From 110cabaca17a3e195279a48ba28cf18bbba0264c Mon Sep 17 00:00:00 2001 From: Florian Renaud Date: Thu, 25 Aug 2022 14:00:58 +0200 Subject: [PATCH 15/20] Remove useless apply in CreateLocalRoomStateEventsTask --- .../create/CreateLocalRoomStateEventsTask.kt | 22 +++++++++---------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/create/CreateLocalRoomStateEventsTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/create/CreateLocalRoomStateEventsTask.kt index 47af08292e..ad2bfcef79 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/create/CreateLocalRoomStateEventsTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/create/CreateLocalRoomStateEventsTask.kt @@ -100,7 +100,7 @@ internal class DefaultCreateLocalRoomStateEventsTask @Inject constructor( /** * Generate the create state event related to this room. */ - private fun MutableList.createRoomCreateEvent() = apply { + private fun MutableList.createRoomCreateEvent() { val roomCreateEvent = createLocalStateEvent( type = EventType.STATE_ROOM_CREATE, content = RoomCreateContent( @@ -117,7 +117,7 @@ internal class DefaultCreateLocalRoomStateEventsTask @Inject constructor( * Generate the create state event related to the power levels using the given overridden values or the default values according to the specification. * Ref: https://spec.matrix.org/latest/client-server-api/#mroompower_levels */ - private fun MutableList.createRoomPowerLevelsEvent() = apply { + private fun MutableList.createRoomPowerLevelsEvent() { val powerLevelsContent = createLocalStateEvent( type = EventType.STATE_ROOM_POWER_LEVELS, content = (createRoomBody.powerLevelContentOverride ?: PowerLevelsContent()).let { @@ -138,7 +138,7 @@ internal class DefaultCreateLocalRoomStateEventsTask @Inject constructor( /** * Generate the local room member state events related to the given user ids, if any. */ - private suspend fun MutableList.createRoomMemberEvents(userIds: List) = apply { + private suspend fun MutableList.createRoomMemberEvents(userIds: List) { val memberEvents = userIds .mapNotNull { tryOrNull { userService.resolveUser(it) } } .map { user -> @@ -159,7 +159,7 @@ internal class DefaultCreateLocalRoomStateEventsTask @Inject constructor( /** * Generate the local state events related to the given third party invites, if any. */ - private fun MutableList.createRoomThreePidEvents() = apply { + private fun MutableList.createRoomThreePidEvents() { val threePidEvents = createRoomBody.invite3pids.orEmpty().map { body -> val localThirdPartyInviteEvent = createLocalStateEvent( type = EventType.LOCAL_STATE_ROOM_THIRD_PARTY_INVITE, @@ -182,7 +182,7 @@ internal class DefaultCreateLocalRoomStateEventsTask @Inject constructor( /** * Generate the local state event related to the given alias, if any. */ - fun MutableList.createRoomAliasEvent() = apply { + fun MutableList.createRoomAliasEvent() { if (createRoomBody.roomAliasName != null) { val canonicalAliasContent = createLocalStateEvent( type = EventType.STATE_ROOM_CANONICAL_ALIAS, @@ -198,8 +198,8 @@ internal class DefaultCreateLocalRoomStateEventsTask @Inject constructor( * Generate the local state events related to the given [CreateRoomPreset]. * Ref: https://spec.matrix.org/latest/client-server-api/#post_matrixclientv3createroom */ - private fun MutableList.createRoomPresetEvents() = apply { - val preset = createRoomBody.preset ?: return@apply + private fun MutableList.createRoomPresetEvents() { + val preset = createRoomBody.preset ?: return var joinRules: RoomJoinRules? = null var historyVisibility: RoomHistoryVisibility? = null @@ -227,8 +227,8 @@ internal class DefaultCreateLocalRoomStateEventsTask @Inject constructor( * Generate the local state events related to the given initial states, if any. * The given initial state events override the potential existing ones of the same type. */ - private fun MutableList.createRoomInitialStateEvents() = apply { - val initialStates = createRoomBody.initialStates ?: return@apply + private fun MutableList.createRoomInitialStateEvents() { + val initialStates = createRoomBody.initialStates ?: return val initialStateEvents = initialStates.map { createLocalStateEvent(it.type, it.content, it.stateKey) } // Erase existing events of the same type @@ -240,7 +240,7 @@ internal class DefaultCreateLocalRoomStateEventsTask @Inject constructor( /** * Generate the local events related to the given room name and topic, if any. */ - private fun MutableList.createRoomNameAndTopicStateEvents() = apply { + private fun MutableList.createRoomNameAndTopicStateEvents() { if (createRoomBody.name != null) { add(createLocalStateEvent(EventType.STATE_ROOM_NAME, RoomNameContent(createRoomBody.name).toContent())) } @@ -255,7 +255,7 @@ internal class DefaultCreateLocalRoomStateEventsTask @Inject constructor( * - m.room.history_visibility (https://spec.matrix.org/latest/client-server-api/#server-behaviour-5) * - m.room.guest_access (https://spec.matrix.org/latest/client-server-api/#mroomguest_access) */ - private fun MutableList.createRoomDefaultEvents() = apply { + private fun MutableList.createRoomDefaultEvents() { // HistoryVisibility if (none { it.type == EventType.STATE_ROOM_HISTORY_VISIBILITY }) { add( From 2be2a057959f2c9588d2f8b8cc3af91a6abd277a Mon Sep 17 00:00:00 2001 From: Florian Renaud Date: Thu, 25 Aug 2022 14:05:25 +0200 Subject: [PATCH 16/20] Change visibility of LocalRoomThirdPartyInviteContent to internal --- .../session/room/create/CreateLocalRoomStateEventsTask.kt | 1 - .../room/create}/LocalRoomThirdPartyInviteContent.kt | 8 ++++---- .../create/DefaultCreateLocalRoomStateEventsTaskTest.kt | 1 - 3 files changed, 4 insertions(+), 6 deletions(-) rename matrix-sdk-android/src/main/java/org/matrix/android/sdk/{api/session/room/model/localecho => internal/session/room/create}/LocalRoomThirdPartyInviteContent.kt (85%) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/create/CreateLocalRoomStateEventsTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/create/CreateLocalRoomStateEventsTask.kt index ad2bfcef79..2b95f9bdf2 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/create/CreateLocalRoomStateEventsTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/create/CreateLocalRoomStateEventsTask.kt @@ -44,7 +44,6 @@ import org.matrix.android.sdk.api.session.room.model.create.RoomCreateContent import org.matrix.android.sdk.api.session.room.model.eventsDefaultOrDefault import org.matrix.android.sdk.api.session.room.model.inviteOrDefault import org.matrix.android.sdk.api.session.room.model.kickOrDefault -import org.matrix.android.sdk.api.session.room.model.localecho.LocalRoomThirdPartyInviteContent import org.matrix.android.sdk.api.session.room.model.redactOrDefault import org.matrix.android.sdk.api.session.room.model.stateDefaultOrDefault import org.matrix.android.sdk.api.session.room.model.usersDefaultOrDefault diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/localecho/LocalRoomThirdPartyInviteContent.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/create/LocalRoomThirdPartyInviteContent.kt similarity index 85% rename from matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/localecho/LocalRoomThirdPartyInviteContent.kt rename to matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/create/LocalRoomThirdPartyInviteContent.kt index 78347fcafc..31d161a16d 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/localecho/LocalRoomThirdPartyInviteContent.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/create/LocalRoomThirdPartyInviteContent.kt @@ -1,11 +1,11 @@ /* - * Copyright 2022 The Matrix.org Foundation C.I.C. + * 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 + * 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, @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.matrix.android.sdk.api.session.room.model.localecho +package org.matrix.android.sdk.internal.session.room.create import com.squareup.moshi.Json import com.squareup.moshi.JsonClass @@ -26,7 +26,7 @@ import org.matrix.android.sdk.api.session.room.model.Membership * This class is only used to store the third party invite data of a local room. */ @JsonClass(generateAdapter = true) -data class LocalRoomThirdPartyInviteContent( +internal data class LocalRoomThirdPartyInviteContent( @Json(name = "membership") val membership: Membership, @Json(name = "displayname") val displayName: String? = null, @Json(name = "is_direct") val isDirect: Boolean = false, diff --git a/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/session/room/create/DefaultCreateLocalRoomStateEventsTaskTest.kt b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/session/room/create/DefaultCreateLocalRoomStateEventsTaskTest.kt index 383bcac30c..db6e139456 100644 --- a/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/session/room/create/DefaultCreateLocalRoomStateEventsTaskTest.kt +++ b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/session/room/create/DefaultCreateLocalRoomStateEventsTaskTest.kt @@ -51,7 +51,6 @@ import org.matrix.android.sdk.api.session.room.model.RoomThirdPartyInviteContent import org.matrix.android.sdk.api.session.room.model.RoomTopicContent import org.matrix.android.sdk.api.session.room.model.create.CreateRoomPreset import org.matrix.android.sdk.api.session.room.model.create.RoomCreateContent -import org.matrix.android.sdk.api.session.room.model.localecho.LocalRoomThirdPartyInviteContent import org.matrix.android.sdk.api.session.room.powerlevels.Role import org.matrix.android.sdk.api.session.user.UserService import org.matrix.android.sdk.api.session.user.model.User From 90d688c222e1429e7e27456964489d7f9b80e5a8 Mon Sep 17 00:00:00 2001 From: Florian Renaud Date: Thu, 25 Aug 2022 14:07:50 +0200 Subject: [PATCH 17/20] Remove useless explicit field type --- .../sdk/api/session/room/model/create/CreateRoomParams.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/create/CreateRoomParams.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/create/CreateRoomParams.kt index 9b708b692f..d6eb7b30d3 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/create/CreateRoomParams.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/create/CreateRoomParams.kt @@ -64,7 +64,7 @@ open class CreateRoomParams { * A list of user IDs to invite to the room. * This will tell the server to invite everyone in the list to the newly created room. */ - var invitedUserIds: MutableList = mutableListOf() + var invitedUserIds = mutableListOf() /** * A list of objects representing third party IDs to invite into the room. From eab4ebc3b1dcbba8eedf2812e9427fd3e9d228ed Mon Sep 17 00:00:00 2001 From: Florian Renaud Date: Thu, 25 Aug 2022 14:16:20 +0200 Subject: [PATCH 18/20] Remove roomCreatorUserId and use current userId by default --- .../create/CreateLocalRoomStateEventsTask.kt | 21 +++++++--------- .../room/create/CreateLocalRoomTask.kt | 4 +--- ...faultCreateLocalRoomStateEventsTaskTest.kt | 24 +++++++++---------- 3 files changed, 22 insertions(+), 27 deletions(-) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/create/CreateLocalRoomStateEventsTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/create/CreateLocalRoomStateEventsTask.kt index 2b95f9bdf2..855e07eaf6 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/create/CreateLocalRoomStateEventsTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/create/CreateLocalRoomStateEventsTask.kt @@ -48,6 +48,7 @@ import org.matrix.android.sdk.api.session.room.model.redactOrDefault import org.matrix.android.sdk.api.session.room.model.stateDefaultOrDefault import org.matrix.android.sdk.api.session.room.model.usersDefaultOrDefault import org.matrix.android.sdk.api.session.user.UserService +import org.matrix.android.sdk.internal.di.UserId import org.matrix.android.sdk.internal.session.room.create.CreateLocalRoomStateEventsTask.Params import org.matrix.android.sdk.internal.session.room.membership.threepid.toThreePid import org.matrix.android.sdk.internal.task.Task @@ -62,29 +63,25 @@ import javax.inject.Inject * Ref: https://spec.matrix.org/latest/client-server-api/#post_matrixclientv3createroom */ internal interface CreateLocalRoomStateEventsTask : Task> { - data class Params( - val roomCreatorUserId: String, - val createRoomBody: CreateRoomBody - ) + data class Params(val createRoomBody: CreateRoomBody) } internal class DefaultCreateLocalRoomStateEventsTask @Inject constructor( + @UserId private val myUserId: String, private val userService: UserService, private val clock: Clock, ) : CreateLocalRoomStateEventsTask { private lateinit var createRoomBody: CreateRoomBody - private lateinit var roomCreatorUserId: String override suspend fun execute(params: Params): List { createRoomBody = params.createRoomBody - roomCreatorUserId = params.roomCreatorUserId // Build the list of the state events following the priorities from the matrix specification // Changing the order of the events might break the correct display of the room on the client side return buildList { createRoomCreateEvent() - createRoomMemberEvents(listOf(roomCreatorUserId)) + createRoomMemberEvents(listOf(myUserId)) createRoomPowerLevelsEvent() createRoomAliasEvent() createRoomPresetEvents() @@ -103,7 +100,7 @@ internal class DefaultCreateLocalRoomStateEventsTask @Inject constructor( val roomCreateEvent = createLocalStateEvent( type = EventType.STATE_ROOM_CREATE, content = RoomCreateContent( - creator = roomCreatorUserId, + creator = myUserId, roomVersion = createRoomBody.roomVersion, type = (createRoomBody.creationContent as? Map<*, *>)?.get(CreateRoomParams.CREATION_CONTENT_KEY_ROOM_TYPE) as? String @@ -144,8 +141,8 @@ internal class DefaultCreateLocalRoomStateEventsTask @Inject constructor( createLocalStateEvent( type = EventType.STATE_ROOM_MEMBER, content = RoomMemberContent( - isDirect = createRoomBody.isDirect.takeUnless { user.userId == roomCreatorUserId }.orFalse(), - membership = if (user.userId == roomCreatorUserId) Membership.JOIN else Membership.INVITE, + isDirect = createRoomBody.isDirect.takeUnless { user.userId == myUserId }.orFalse(), + membership = if (user.userId == myUserId) Membership.JOIN else Membership.INVITE, displayName = user.displayName, avatarUrl = user.avatarUrl ).toContent(), @@ -186,7 +183,7 @@ internal class DefaultCreateLocalRoomStateEventsTask @Inject constructor( val canonicalAliasContent = createLocalStateEvent( type = EventType.STATE_ROOM_CANONICAL_ALIAS, content = RoomCanonicalAliasContent( - canonicalAlias = "${createRoomBody.roomAliasName}:${roomCreatorUserId.getServerName()}" + canonicalAlias = "${createRoomBody.roomAliasName}:${myUserId.getServerName()}" ).toContent(), ) add(canonicalAliasContent) @@ -287,7 +284,7 @@ internal class DefaultCreateLocalRoomStateEventsTask @Inject constructor( private fun createLocalStateEvent(type: String?, content: Content?, stateKey: String? = ""): Event { return Event( type = type, - senderId = roomCreatorUserId, + senderId = myUserId, stateKey = stateKey, content = content, originServerTs = clock.epochMillis(), diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/create/CreateLocalRoomTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/create/CreateLocalRoomTask.kt index 5b45fbf6ae..03c2b2a47e 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/create/CreateLocalRoomTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/create/CreateLocalRoomTask.kt @@ -46,7 +46,6 @@ import org.matrix.android.sdk.internal.database.query.copyToRealmOrIgnore import org.matrix.android.sdk.internal.database.query.getOrCreate import org.matrix.android.sdk.internal.database.query.getOrNull import org.matrix.android.sdk.internal.di.SessionDatabase -import org.matrix.android.sdk.internal.di.UserId import org.matrix.android.sdk.internal.session.events.getFixedRoomMemberContent import org.matrix.android.sdk.internal.session.room.membership.RoomMemberEventHandler import org.matrix.android.sdk.internal.session.room.summary.RoomSummaryUpdater @@ -60,7 +59,6 @@ import javax.inject.Inject internal interface CreateLocalRoomTask : Task internal class DefaultCreateLocalRoomTask @Inject constructor( - @UserId private val myUserId: String, @SessionDatabase private val monarchy: Monarchy, private val roomMemberEventHandler: RoomMemberEventHandler, private val roomSummaryUpdater: RoomSummaryUpdater, @@ -156,7 +154,7 @@ internal class DefaultCreateLocalRoomTask @Inject constructor( isLastForward = true } - val eventList = createLocalRoomStateEventsTask.execute(CreateLocalRoomStateEventsTask.Params(myUserId, createRoomBody)) + val eventList = createLocalRoomStateEventsTask.execute(CreateLocalRoomStateEventsTask.Params(createRoomBody)) val roomMemberContentsByUser = HashMap() for (event in eventList) { diff --git a/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/session/room/create/DefaultCreateLocalRoomStateEventsTaskTest.kt b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/session/room/create/DefaultCreateLocalRoomStateEventsTaskTest.kt index db6e139456..1c2cf293b6 100644 --- a/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/session/room/create/DefaultCreateLocalRoomStateEventsTaskTest.kt +++ b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/session/room/create/DefaultCreateLocalRoomStateEventsTaskTest.kt @@ -71,6 +71,7 @@ internal class DefaultCreateLocalRoomStateEventsTaskTest { private val userService = mockk() private val defaultCreateLocalRoomStateEventsTask = DefaultCreateLocalRoomStateEventsTask( + myUserId = MY_USER_ID, userService = userService, clock = clock ) @@ -103,20 +104,19 @@ internal class DefaultCreateLocalRoomStateEventsTaskTest { @Test fun `given a CreateRoomBody when execute then the resulting list of events contains the correct room create state event`() = runTest { // Given - val aRoomCreator = MY_USER_ID val aRoomVersion = "a_room_version" every { createRoomBody.roomVersion } returns aRoomVersion // When - val params = CreateLocalRoomStateEventsTask.Params(aRoomCreator, createRoomBody) + val params = CreateLocalRoomStateEventsTask.Params(createRoomBody) val result = defaultCreateLocalRoomStateEventsTask.execute(params) // Then val roomCreateEvent = result.find { it.type == EventType.STATE_ROOM_CREATE } val roomCreateContent = roomCreateEvent?.content.toModel() - roomCreateContent?.creator shouldBeEqualTo aRoomCreator + roomCreateContent?.creator shouldBeEqualTo MY_USER_ID roomCreateContent?.roomVersion shouldBeEqualTo aRoomVersion } @@ -130,7 +130,7 @@ internal class DefaultCreateLocalRoomStateEventsTaskTest { every { createRoomBody.topic } returns aRoomTopic // When - val params = CreateLocalRoomStateEventsTask.Params(MY_USER_ID, createRoomBody) + val params = CreateLocalRoomStateEventsTask.Params(createRoomBody) val result = defaultCreateLocalRoomStateEventsTask.execute(params) // Then @@ -158,7 +158,7 @@ internal class DefaultCreateLocalRoomStateEventsTaskTest { } // When - val params = CreateLocalRoomStateEventsTask.Params(MY_USER_ID, createRoomBody) + val params = CreateLocalRoomStateEventsTask.Params(createRoomBody) val result = defaultCreateLocalRoomStateEventsTask.execute(params) // Then @@ -195,7 +195,7 @@ internal class DefaultCreateLocalRoomStateEventsTaskTest { every { createRoomBody.powerLevelContentOverride } returns aPowerLevelsContent // When - val params = CreateLocalRoomStateEventsTask.Params(MY_USER_ID, createRoomBody) + val params = CreateLocalRoomStateEventsTask.Params(createRoomBody) val result = defaultCreateLocalRoomStateEventsTask.execute(params) // Then @@ -212,7 +212,7 @@ internal class DefaultCreateLocalRoomStateEventsTaskTest { every { createRoomBody.roomAliasName } returns aRoomAlias // When - val params = CreateLocalRoomStateEventsTask.Params(MY_USER_ID, createRoomBody) + val params = CreateLocalRoomStateEventsTask.Params(createRoomBody) val result = defaultCreateLocalRoomStateEventsTask.execute(params) // Then @@ -244,7 +244,7 @@ internal class DefaultCreateLocalRoomStateEventsTaskTest { every { createRoomBody.preset } returns case.preset // When - val params = CreateLocalRoomStateEventsTask.Params(MY_USER_ID, createRoomBody) + val params = CreateLocalRoomStateEventsTask.Params(createRoomBody) val result = defaultCreateLocalRoomStateEventsTask.execute(params) // Then @@ -284,7 +284,7 @@ internal class DefaultCreateLocalRoomStateEventsTaskTest { every { createRoomBody.initialStates } returns aListOfInitialStateEvents // When - val params = CreateLocalRoomStateEventsTask.Params(MY_USER_ID, createRoomBody) + val params = CreateLocalRoomStateEventsTask.Params(createRoomBody) val result = defaultCreateLocalRoomStateEventsTask.execute(params) // Then @@ -319,7 +319,7 @@ internal class DefaultCreateLocalRoomStateEventsTaskTest { every { createRoomBody.invite3pids } returns aListOf3pids // When - val params = CreateLocalRoomStateEventsTask.Params(MY_USER_ID, createRoomBody) + val params = CreateLocalRoomStateEventsTask.Params(createRoomBody) val result = defaultCreateLocalRoomStateEventsTask.execute(params) // Then @@ -358,7 +358,7 @@ internal class DefaultCreateLocalRoomStateEventsTaskTest { } // When - val params = CreateLocalRoomStateEventsTask.Params(MY_USER_ID, createRoomBody) + val params = CreateLocalRoomStateEventsTask.Params(createRoomBody) val result = defaultCreateLocalRoomStateEventsTask.execute(params) // Then @@ -447,7 +447,7 @@ internal class DefaultCreateLocalRoomStateEventsTaskTest { ) // When - val params = CreateLocalRoomStateEventsTask.Params(MY_USER_ID, createRoomBody) + val params = CreateLocalRoomStateEventsTask.Params(createRoomBody) val result = defaultCreateLocalRoomStateEventsTask.execute(params) // Then From cac4df7d664b1e081f9c9bb36d217afbb410b5c9 Mon Sep 17 00:00:00 2001 From: Florian Renaud Date: Thu, 25 Aug 2022 14:28:17 +0200 Subject: [PATCH 19/20] Improve createRoomThreePidEvents for clarity --- .../room/create/CreateLocalRoomStateEventsTask.kt | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/create/CreateLocalRoomStateEventsTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/create/CreateLocalRoomStateEventsTask.kt index 855e07eaf6..a9ff4970fe 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/create/CreateLocalRoomStateEventsTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/create/CreateLocalRoomStateEventsTask.kt @@ -156,7 +156,7 @@ internal class DefaultCreateLocalRoomStateEventsTask @Inject constructor( * Generate the local state events related to the given third party invites, if any. */ private fun MutableList.createRoomThreePidEvents() { - val threePidEvents = createRoomBody.invite3pids.orEmpty().map { body -> + createRoomBody.invite3pids.orEmpty().forEach { body -> val localThirdPartyInviteEvent = createLocalStateEvent( type = EventType.LOCAL_STATE_ROOM_THIRD_PARTY_INVITE, content = LocalRoomThirdPartyInviteContent( @@ -168,11 +168,16 @@ internal class DefaultCreateLocalRoomStateEventsTask @Inject constructor( ) val thirdPartyInviteEvent = createLocalStateEvent( type = EventType.STATE_ROOM_THIRD_PARTY_INVITE, - content = RoomThirdPartyInviteContent(body.address, null, null, null).toContent(), + content = RoomThirdPartyInviteContent( + displayName = body.address, + keyValidityUrl = null, + publicKey = null, + publicKeys = null + ).toContent(), ) - listOf(localThirdPartyInviteEvent, thirdPartyInviteEvent) - }.flatten() - addAll(threePidEvents) + add(localThirdPartyInviteEvent) + add(thirdPartyInviteEvent) + } } /** From ee7c0593ba7af436a819a88c1a9989a4ac368a22 Mon Sep 17 00:00:00 2001 From: Florian Renaud Date: Thu, 25 Aug 2022 14:37:06 +0200 Subject: [PATCH 20/20] Fix copyright --- .../session/room/create/LocalRoomThirdPartyInviteContent.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/create/LocalRoomThirdPartyInviteContent.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/create/LocalRoomThirdPartyInviteContent.kt index 31d161a16d..617ed35326 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/create/LocalRoomThirdPartyInviteContent.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/create/LocalRoomThirdPartyInviteContent.kt @@ -1,5 +1,5 @@ /* - * Copyright (c) 2022 New Vector Ltd + * 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.