CreateRoomParams has been replaced by CreateRoomParamsBuilder, to be able to invite 3pids

This commit is contained in:
Benoit Marty 2020-07-09 14:07:00 +02:00
parent 6c0bb2a949
commit 4b3a6a883d
24 changed files with 443 additions and 413 deletions

View File

@ -28,7 +28,7 @@ Translations 🗣:
-
SDK API changes ⚠️:
-
- CreateRoomParams has been replaced by CreateRoomParamsBuilder
Build 🧱:
- Upgrade some dependencies

View File

@ -19,6 +19,7 @@ package im.vector.matrix.rx
import android.net.Uri
import im.vector.matrix.android.api.query.QueryStringValue
import im.vector.matrix.android.api.session.events.model.Event
import im.vector.matrix.android.api.session.identity.ThreePid
import im.vector.matrix.android.api.session.room.Room
import im.vector.matrix.android.api.session.room.members.RoomMemberQueryParams
import im.vector.matrix.android.api.session.room.model.EventAnnotationsSummary
@ -104,6 +105,10 @@ class RxRoom(private val room: Room) {
room.invite(userId, reason, it)
}
fun invite3pid(threePid: ThreePid): Completable = completableBuilder<Unit> {
room.invite3pid(threePid, it)
}
fun updateTopic(topic: String): Completable = completableBuilder<Unit> {
room.updateTopic(topic, it)
}

View File

@ -32,7 +32,7 @@ import im.vector.matrix.android.api.session.pushers.Pusher
import im.vector.matrix.android.api.session.room.RoomSummaryQueryParams
import im.vector.matrix.android.api.session.room.members.ChangeMembershipState
import im.vector.matrix.android.api.session.room.model.RoomSummary
import im.vector.matrix.android.api.session.room.model.create.CreateRoomParams
import im.vector.matrix.android.api.session.room.model.create.CreateRoomParamsBuilder
import im.vector.matrix.android.api.session.sync.SyncState
import im.vector.matrix.android.api.session.user.model.User
import im.vector.matrix.android.api.session.widgets.model.Widget
@ -110,7 +110,7 @@ class RxSession(private val session: Session) {
.startWithCallable { session.getThreePids() }
}
fun createRoom(roomParams: CreateRoomParams): Single<String> = singleBuilder {
fun createRoom(roomParams: CreateRoomParamsBuilder): Single<String> = singleBuilder {
session.createRoom(roomParams, it)
}

View File

@ -30,7 +30,7 @@ import im.vector.matrix.android.api.session.events.model.toContent
import im.vector.matrix.android.api.session.room.Room
import im.vector.matrix.android.api.session.room.model.Membership
import im.vector.matrix.android.api.session.room.model.RoomSummary
import im.vector.matrix.android.api.session.room.model.create.CreateRoomParams
import im.vector.matrix.android.api.session.room.model.create.CreateRoomParamsBuilder
import im.vector.matrix.android.api.session.room.roomSummaryQueryParams
import im.vector.matrix.android.api.session.room.timeline.Timeline
import im.vector.matrix.android.api.session.room.timeline.TimelineEvent
@ -65,7 +65,7 @@ class CryptoTestHelper(private val mTestHelper: CommonTestHelper) {
val aliceSession = mTestHelper.createAccount(TestConstants.USER_ALICE, defaultSessionParams)
val roomId = mTestHelper.doSync<String> {
aliceSession.createRoom(CreateRoomParams(name = "MyRoom"), it)
aliceSession.createRoom(CreateRoomParamsBuilder().apply { name = "MyRoom" }, it)
}
if (encryptedRoom) {
@ -286,9 +286,11 @@ class CryptoTestHelper(private val mTestHelper: CommonTestHelper) {
fun createDM(alice: Session, bob: Session): String {
val roomId = mTestHelper.doSync<String> {
alice.createRoom(
CreateRoomParams(invitedUserIds = listOf(bob.myUserId))
.setDirectMessage()
.enableEncryptionIfInvitedUsersSupportIt(),
CreateRoomParamsBuilder().apply {
invitedUserIds.add(bob.myUserId)
setDirectMessage()
enableEncryptionIfInvitedUsersSupportIt = true
},
it
)
}

View File

@ -27,7 +27,7 @@ import im.vector.matrix.android.api.session.crypto.verification.VerificationTran
import im.vector.matrix.android.api.session.crypto.verification.VerificationTxState
import im.vector.matrix.android.api.session.events.model.toModel
import im.vector.matrix.android.api.session.room.model.RoomDirectoryVisibility
import im.vector.matrix.android.api.session.room.model.create.CreateRoomParams
import im.vector.matrix.android.api.session.room.model.create.CreateRoomParamsBuilder
import im.vector.matrix.android.common.CommonTestHelper
import im.vector.matrix.android.common.CryptoTestHelper
import im.vector.matrix.android.common.SessionTestParams
@ -66,7 +66,10 @@ class KeyShareTests : InstrumentedTest {
// Create an encrypted room and add a message
val roomId = mTestHelper.doSync<String> {
aliceSession.createRoom(
CreateRoomParams(RoomDirectoryVisibility.PRIVATE).enableEncryptionWithAlgorithm(true),
CreateRoomParamsBuilder().apply {
visibility = RoomDirectoryVisibility.PRIVATE
enableEncryption()
},
it
)
}
@ -285,7 +288,7 @@ class KeyShareTests : InstrumentedTest {
mTestHelper.waitWithLatch(60_000) { latch ->
val keysBackupService = aliceSession2.cryptoService().keysBackupService()
mTestHelper.retryPeriodicallyWithLatch(latch) {
Log.d("#TEST", "Recovery :${ keysBackupService.getKeyBackupRecoveryKeyInfo()?.recoveryKey}")
Log.d("#TEST", "Recovery :${keysBackupService.getKeyBackupRecoveryKeyInfo()?.recoveryKey}")
keysBackupService.getKeyBackupRecoveryKeyInfo()?.recoveryKey == creationInfo.recoveryKey
}
}

View File

@ -20,7 +20,7 @@ import androidx.lifecycle.LiveData
import im.vector.matrix.android.api.MatrixCallback
import im.vector.matrix.android.api.session.room.members.ChangeMembershipState
import im.vector.matrix.android.api.session.room.model.RoomSummary
import im.vector.matrix.android.api.session.room.model.create.CreateRoomParams
import im.vector.matrix.android.api.session.room.model.create.CreateRoomParamsBuilder
import im.vector.matrix.android.api.util.Cancelable
import im.vector.matrix.android.api.util.Optional
@ -32,7 +32,7 @@ interface RoomService {
/**
* Create a room asynchronously
*/
fun createRoom(createRoomParams: CreateRoomParams,
fun createRoom(createRoomParams: CreateRoomParamsBuilder,
callback: MatrixCallback<String>): Cancelable
/**
@ -113,5 +113,5 @@ interface RoomService {
*/
fun getChangeMembershipsLive(): LiveData<Map<String, ChangeMembershipState>>
fun getExistingDirectRoomWithUser(otherUserId: String) : Room?
fun getExistingDirectRoomWithUser(otherUserId: String): Room?
}

View File

@ -1,268 +0,0 @@
/*
* Copyright 2019 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package im.vector.matrix.android.api.session.room.model.create
import android.util.Patterns
import androidx.annotation.CheckResult
import com.squareup.moshi.Json
import com.squareup.moshi.JsonClass
import im.vector.matrix.android.api.MatrixPatterns.isUserId
import im.vector.matrix.android.api.auth.data.HomeServerConnectionConfig
import im.vector.matrix.android.api.session.events.model.Event
import im.vector.matrix.android.api.session.events.model.EventType
import im.vector.matrix.android.api.session.events.model.toContent
import im.vector.matrix.android.api.session.room.model.PowerLevelsContent
import im.vector.matrix.android.api.session.room.model.RoomDirectoryVisibility
import im.vector.matrix.android.api.session.room.model.RoomHistoryVisibility
import im.vector.matrix.android.internal.auth.data.ThreePidMedium
import im.vector.matrix.android.internal.crypto.MXCRYPTO_ALGORITHM_MEGOLM
import timber.log.Timber
/**
* Parameter to create a room, with facilities functions to configure it
*/
@JsonClass(generateAdapter = true)
data class CreateRoomParams(
/**
* A public visibility indicates that the room will be shown in the published room list.
* A private visibility will hide the room from the published room list.
* Rooms default to private visibility if this key is not included.
* NB: This should not be confused with join_rules which also uses the word public. One of: ["public", "private"]
*/
@Json(name = "visibility")
val visibility: RoomDirectoryVisibility? = null,
/**
* The desired room alias local part. If this is included, a room alias will be created and mapped to the newly created room.
* The alias will belong on the same homeserver which created the room.
* For example, if this was set to "foo" and sent to the homeserver "example.com" the complete room alias would be #foo:example.com.
*/
@Json(name = "room_alias_name")
val roomAliasName: String? = null,
/**
* If this is included, an m.room.name event will be sent into the room to indicate the name of the room.
* See Room Events for more information on m.room.name.
*/
@Json(name = "name")
val name: String? = null,
/**
* If this is included, an m.room.topic event will be sent into the room to indicate the topic for the room.
* See Room Events for more information on m.room.topic.
*/
@Json(name = "topic")
val topic: String? = null,
/**
* 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.
*/
@Json(name = "invite")
val invitedUserIds: List<String>? = null,
/**
* A list of objects representing third party IDs to invite into the room.
*/
@Json(name = "invite_3pid")
val invite3pids: List<Invite3Pid>? = null,
/**
* Extra keys to be added to the content of the m.room.create.
* The server will clobber the following keys: creator.
* Future versions of the specification may allow the server to clobber other keys.
*/
@Json(name = "creation_content")
val creationContent: Any? = null,
/**
* 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 presets, but gets overridden by name and topic keys.
*/
@Json(name = "initial_state")
val initialStates: List<Event>? = null,
/**
* Convenience parameter for setting various default state events based on a preset. Must be either:
* private_chat => join_rules is set to invite. history_visibility is set to shared.
* trusted_private_chat => join_rules is set to invite. history_visibility is set to shared. All invitees are given the same power level as the
* room creator.
* public_chat: => join_rules is set to public. history_visibility is set to shared.
*/
@Json(name = "preset")
val preset: CreateRoomPreset? = null,
/**
* This flag makes the server set the is_direct flag on the m.room.member events sent to the users in invite and invite_3pid.
* See Direct Messaging for more information.
*/
@Json(name = "is_direct")
val isDirect: Boolean? = null,
/**
* The power level content to override in the default power level event
*/
@Json(name = "power_level_content_override")
val powerLevelContentOverride: PowerLevelsContent? = null
) {
@Transient
internal var enableEncryptionIfInvitedUsersSupportIt: Boolean = false
private set
/**
* After calling this method, when the room will be created, if cross-signing is enabled and we can get keys for every invited users,
* the encryption will be enabled on the created room
* @param value true to activate this behavior.
* @return this, to allow chaining methods
*/
fun enableEncryptionIfInvitedUsersSupportIt(value: Boolean = true): CreateRoomParams {
enableEncryptionIfInvitedUsersSupportIt = value
return this
}
/**
* Add the crypto algorithm to the room creation parameters.
*
* @param enable true to enable encryption.
* @param algorithm the algorithm, default to [MXCRYPTO_ALGORITHM_MEGOLM], which is actually the only supported algorithm for the moment
* @return a modified copy of the CreateRoomParams object, or this if there is no modification
*/
@CheckResult
fun enableEncryptionWithAlgorithm(enable: Boolean = true,
algorithm: String = MXCRYPTO_ALGORITHM_MEGOLM): CreateRoomParams {
// Remove the existing value if any.
val newInitialStates = initialStates
?.filter { it.type != EventType.STATE_ROOM_ENCRYPTION }
return if (algorithm == MXCRYPTO_ALGORITHM_MEGOLM) {
if (enable) {
val contentMap = mapOf("algorithm" to algorithm)
val algoEvent = Event(
type = EventType.STATE_ROOM_ENCRYPTION,
stateKey = "",
content = contentMap.toContent()
)
copy(
initialStates = newInitialStates.orEmpty() + algoEvent
)
} else {
return copy(
initialStates = newInitialStates
)
}
} else {
Timber.e("Unsupported algorithm: $algorithm")
this
}
}
/**
* Force the history visibility in the room creation parameters.
*
* @param historyVisibility the expected history visibility, set null to remove any existing value.
* @return a modified copy of the CreateRoomParams object
*/
@CheckResult
fun setHistoryVisibility(historyVisibility: RoomHistoryVisibility?): CreateRoomParams {
// Remove the existing value if any.
val newInitialStates = initialStates
?.filter { it.type != EventType.STATE_ROOM_HISTORY_VISIBILITY }
if (historyVisibility != null) {
val contentMap = mapOf("history_visibility" to historyVisibility)
val historyVisibilityEvent = Event(
type = EventType.STATE_ROOM_HISTORY_VISIBILITY,
stateKey = "",
content = contentMap.toContent())
return copy(
initialStates = newInitialStates.orEmpty() + historyVisibilityEvent
)
} else {
return copy(
initialStates = newInitialStates
)
}
}
/**
* Mark as a direct message room.
* @return a modified copy of the CreateRoomParams object
*/
@CheckResult
fun setDirectMessage(): CreateRoomParams {
return copy(
preset = CreateRoomPreset.PRESET_TRUSTED_PRIVATE_CHAT,
isDirect = true
)
}
/**
* Tells if the created room can be a direct chat one.
*
* @return true if it is a direct chat
*/
fun isDirect(): Boolean {
return preset == CreateRoomPreset.PRESET_TRUSTED_PRIVATE_CHAT
&& isDirect == true
}
/**
* @return the first invited user id
*/
fun getFirstInvitedUserId(): String? {
return invitedUserIds?.firstOrNull() ?: invite3pids?.firstOrNull()?.address
}
/**
* Add some ids to the room creation
* ids might be a matrix id or an email address.
*
* @param ids the participant ids to add.
* @return a modified copy of the CreateRoomParams object
*/
@CheckResult
fun addParticipantIds(hsConfig: HomeServerConnectionConfig,
userId: String,
ids: List<String>): CreateRoomParams {
return copy(
invite3pids = (invite3pids.orEmpty() + ids
.takeIf { hsConfig.identityServerUri != null }
?.filter { id -> Patterns.EMAIL_ADDRESS.matcher(id).matches() }
?.map { id ->
Invite3Pid(
idServer = hsConfig.identityServerUri!!.host!!,
medium = ThreePidMedium.EMAIL,
address = id
)
}
.orEmpty())
.distinct(),
invitedUserIds = (invitedUserIds.orEmpty() + ids
.filter { id -> isUserId(id) }
// do not invite oneself
.filter { id -> id != userId })
.distinct()
)
// TODO add phonenumbers when it will be available
}
}

View File

@ -0,0 +1,86 @@
/*
* Copyright (c) 2020 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package im.vector.matrix.android.api.session.room.model.create
import im.vector.matrix.android.api.session.identity.ThreePid
import im.vector.matrix.android.api.session.room.model.RoomDirectoryVisibility
import im.vector.matrix.android.api.session.room.model.RoomHistoryVisibility
import im.vector.matrix.android.internal.crypto.MXCRYPTO_ALGORITHM_MEGOLM
class CreateRoomParamsBuilder {
var visibility: RoomDirectoryVisibility? = null
var roomAliasName: String? = null
var name: String? = null
var topic: String? = null
/**
* UserIds to invite
*/
val invitedUserIds = mutableListOf<String>()
/**
* ThreePids to invite
*/
val invite3pids = mutableListOf<ThreePid>()
/**
* If set to true, when the room will be created, if cross-signing is enabled and we can get keys for every invited users,
* the encryption will be enabled on the created room
*/
var enableEncryptionIfInvitedUsersSupportIt: Boolean = false
var preset: CreateRoomPreset? = null
var isDirect: Boolean? = null
/**
* Mark as a direct message room.
*/
fun setDirectMessage() {
preset = CreateRoomPreset.PRESET_TRUSTED_PRIVATE_CHAT
isDirect = true
}
/**
* Supported value: MXCRYPTO_ALGORITHM_MEGOLM
*/
var algorithm: String? = null
private set
var historyVisibility: RoomHistoryVisibility? = null
fun enableEncryption() {
algorithm = MXCRYPTO_ALGORITHM_MEGOLM
}
/**
* Tells if the created room can be a direct chat one.
*
* @return true if it is a direct chat
*/
fun isDirect(): Boolean {
return preset == CreateRoomPreset.PRESET_TRUSTED_PRIVATE_CHAT
&& isDirect == true
}
/**
* @return the first invited user id
*/
fun getFirstInvitedUserId(): String? {
return invitedUserIds.firstOrNull() ?: invite3pids.firstOrNull()?.value
}
}

View File

@ -1,50 +0,0 @@
/*
* Copyright 2019 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package im.vector.matrix.android.api.session.room.model.create
import com.squareup.moshi.Json
import com.squareup.moshi.JsonClass
@JsonClass(generateAdapter = true)
data class Invite3Pid(
/**
* Required.
* The hostname+port of the identity server which should be used for third party identifier lookups.
*/
@Json(name = "id_server")
val idServer: String,
/**
* Required.
* An access token previously registered with the identity server. Servers can treat this as optional to
* distinguish between r0.5-compatible clients and this specification version.
*/
@Json(name = "id_access_token")
val idAccessToken: String,
/**
* Required.
* The kind of address being passed in the address field, for example email.
*/
val medium: String,
/**
* Required.
* The invitee's third party identifier.
*/
val address: String
)

View File

@ -23,7 +23,7 @@ import im.vector.matrix.android.api.session.room.RoomService
import im.vector.matrix.android.api.session.room.RoomSummaryQueryParams
import im.vector.matrix.android.api.session.room.members.ChangeMembershipState
import im.vector.matrix.android.api.session.room.model.RoomSummary
import im.vector.matrix.android.api.session.room.model.create.CreateRoomParams
import im.vector.matrix.android.api.session.room.model.create.CreateRoomParamsBuilder
import im.vector.matrix.android.api.util.Cancelable
import im.vector.matrix.android.api.util.Optional
import im.vector.matrix.android.internal.session.room.alias.GetRoomIdByAliasTask
@ -49,7 +49,7 @@ internal class DefaultRoomService @Inject constructor(
private val taskExecutor: TaskExecutor
) : RoomService {
override fun createRoom(createRoomParams: CreateRoomParams, callback: MatrixCallback<String>): Cancelable {
override fun createRoom(createRoomParams: CreateRoomParamsBuilder, callback: MatrixCallback<String>): Cancelable {
return createRoomTask
.configureWith(createRoomParams) {
this.callback = callback

View File

@ -18,9 +18,6 @@ package im.vector.matrix.android.internal.session.room
import im.vector.matrix.android.api.session.events.model.Content
import im.vector.matrix.android.api.session.events.model.Event
import im.vector.matrix.android.api.session.room.model.create.CreateRoomParams
import im.vector.matrix.android.api.session.room.model.create.CreateRoomResponse
import im.vector.matrix.android.api.session.room.model.create.JoinRoomResponse
import im.vector.matrix.android.api.session.room.model.roomdirectory.PublicRoomsParams
import im.vector.matrix.android.api.session.room.model.roomdirectory.PublicRoomsResponse
import im.vector.matrix.android.api.session.room.model.thirdparty.ThirdPartyProtocol
@ -28,6 +25,9 @@ import im.vector.matrix.android.api.util.JsonDict
import im.vector.matrix.android.internal.network.NetworkConstants
import im.vector.matrix.android.internal.session.room.alias.AddRoomAliasBody
import im.vector.matrix.android.internal.session.room.alias.RoomAliasDescription
import im.vector.matrix.android.internal.session.room.create.CreateRoomParams
import im.vector.matrix.android.internal.session.room.create.CreateRoomResponse
import im.vector.matrix.android.internal.session.room.create.JoinRoomResponse
import im.vector.matrix.android.internal.session.room.membership.RoomMembersResponse
import im.vector.matrix.android.internal.session.room.membership.admin.UserIdAndReason
import im.vector.matrix.android.internal.session.room.membership.joining.InviteBody

View File

@ -0,0 +1,115 @@
/*
* Copyright (c) 2020 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package im.vector.matrix.android.internal.session.room.create
import com.squareup.moshi.Json
import com.squareup.moshi.JsonClass
import im.vector.matrix.android.api.session.events.model.Event
import im.vector.matrix.android.api.session.room.model.PowerLevelsContent
import im.vector.matrix.android.api.session.room.model.RoomDirectoryVisibility
import im.vector.matrix.android.api.session.room.model.create.CreateRoomPreset
import im.vector.matrix.android.internal.session.room.membership.threepid.ThreePidInviteBody
/**
* Parameter to create a room
*/
@JsonClass(generateAdapter = true)
internal data class CreateRoomParams(
/**
* A public visibility indicates that the room will be shown in the published room list.
* A private visibility will hide the room from the published room list.
* Rooms default to private visibility if this key is not included.
* NB: This should not be confused with join_rules which also uses the word public. One of: ["public", "private"]
*/
@Json(name = "visibility")
val visibility: RoomDirectoryVisibility?,
/**
* The desired room alias local part. If this is included, a room alias will be created and mapped to the newly created room.
* The alias will belong on the same homeserver which created the room.
* For example, if this was set to "foo" and sent to the homeserver "example.com" the complete room alias would be #foo:example.com.
*/
@Json(name = "room_alias_name")
val roomAliasName: String?,
/**
* If this is included, an m.room.name event will be sent into the room to indicate the name of the room.
* See Room Events for more information on m.room.name.
*/
@Json(name = "name")
val name: String?,
/**
* If this is included, an m.room.topic event will be sent into the room to indicate the topic for the room.
* See Room Events for more information on m.room.topic.
*/
@Json(name = "topic")
val topic: String?,
/**
* 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.
*/
@Json(name = "invite")
val invitedUserIds: List<String>?,
/**
* A list of objects representing third party IDs to invite into the room.
*/
@Json(name = "invite_3pid")
val invite3pids: List<ThreePidInviteBody>?,
/**
* Extra keys to be added to the content of the m.room.create.
* The server will clobber the following keys: creator.
* Future versions of the specification may allow the server to clobber other keys.
*/
@Json(name = "creation_content")
val creationContent: Any?,
/**
* 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 presets, but gets overridden by name and topic keys.
*/
@Json(name = "initial_state")
val initialStates: List<Event>?,
/**
* Convenience parameter for setting various default state events based on a preset. Must be either:
* private_chat => join_rules is set to invite. history_visibility is set to shared.
* trusted_private_chat => join_rules is set to invite. history_visibility is set to shared. All invitees are given the same power level as the
* room creator.
* public_chat: => join_rules is set to public. history_visibility is set to shared.
*/
@Json(name = "preset")
val preset: CreateRoomPreset?,
/**
* This flag makes the server set the is_direct flag on the m.room.member events sent to the users in invite and invite_3pid.
* See Direct Messaging for more information.
*/
@Json(name = "is_direct")
val isDirect: Boolean?,
/**
* The power level content to override in the default power level event
*/
@Json(name = "power_level_content_override")
val powerLevelContentOverride: PowerLevelsContent?
)

View File

@ -0,0 +1,147 @@
/*
* Copyright (c) 2020 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package im.vector.matrix.android.internal.session.room.create
import im.vector.matrix.android.api.session.crypto.crosssigning.CrossSigningService
import im.vector.matrix.android.api.session.events.model.Event
import im.vector.matrix.android.api.session.events.model.EventType
import im.vector.matrix.android.api.session.events.model.toContent
import im.vector.matrix.android.api.session.identity.IdentityServiceError
import im.vector.matrix.android.api.session.identity.toMedium
import im.vector.matrix.android.api.session.room.model.create.CreateRoomParamsBuilder
import im.vector.matrix.android.internal.crypto.DeviceListManager
import im.vector.matrix.android.internal.crypto.MXCRYPTO_ALGORITHM_MEGOLM
import im.vector.matrix.android.internal.di.AuthenticatedIdentity
import im.vector.matrix.android.internal.network.token.AccessTokenProvider
import im.vector.matrix.android.internal.session.identity.EnsureIdentityTokenTask
import im.vector.matrix.android.internal.session.identity.data.IdentityStore
import im.vector.matrix.android.internal.session.identity.data.getIdentityServerUrlWithoutProtocol
import im.vector.matrix.android.internal.session.room.membership.threepid.ThreePidInviteBody
import java.security.InvalidParameterException
import javax.inject.Inject
internal class CreateRoomParamsInternalBuilder @Inject constructor(
private val ensureIdentityTokenTask: EnsureIdentityTokenTask,
private val crossSigningService: CrossSigningService,
private val deviceListManager: DeviceListManager,
private val identityStore: IdentityStore,
@AuthenticatedIdentity
private val accessTokenProvider: AccessTokenProvider
) {
suspend fun build(builder: CreateRoomParamsBuilder): CreateRoomParams {
val invite3pids = builder.invite3pids
.takeIf { it.isNotEmpty() }
.let {
// This can throw Exception if Identity server is not configured
ensureIdentityTokenTask.execute(Unit)
val identityServerUrlWithoutProtocol = identityStore.getIdentityServerUrlWithoutProtocol()
?: throw IdentityServiceError.NoIdentityServerConfigured
val identityServerAccessToken = accessTokenProvider.getToken() ?: throw IdentityServiceError.NoIdentityServerConfigured
builder.invite3pids.map {
ThreePidInviteBody(
id_server = identityServerUrlWithoutProtocol,
id_access_token = identityServerAccessToken,
medium = it.toMedium(),
address = it.value
)
}
}
val initialStates = listOfNotNull(
buildEncryptionWithAlgorithmEvent(builder),
buildHistoryVisibilityEvent(builder)
)
.takeIf { it.isNotEmpty() }
return CreateRoomParams(
visibility = builder.visibility,
roomAliasName = builder.roomAliasName,
name = builder.name,
topic = builder.topic,
invitedUserIds = builder.invitedUserIds,
invite3pids = invite3pids,
// TODO Support this
creationContent = null,
initialStates = initialStates,
preset = builder.preset,
isDirect = builder.isDirect,
// TODO Support this
powerLevelContentOverride = null
)
}
private fun buildHistoryVisibilityEvent(builder: CreateRoomParamsBuilder): Event? {
return builder.historyVisibility
?.let {
val contentMap = mapOf("history_visibility" to it)
Event(
type = EventType.STATE_ROOM_HISTORY_VISIBILITY,
stateKey = "",
content = contentMap.toContent())
}
}
/**
* Add the crypto algorithm to the room creation parameters.
*/
private suspend fun buildEncryptionWithAlgorithmEvent(builder: CreateRoomParamsBuilder): Event? {
if (builder.algorithm == null
&& canEnableEncryption(builder)) {
// Enable the encryption
builder.enableEncryption()
}
return builder.algorithm
?.let {
if (it != MXCRYPTO_ALGORITHM_MEGOLM) {
throw InvalidParameterException("Unsupported algorithm: $it")
}
val contentMap = mapOf("algorithm" to it)
Event(
type = EventType.STATE_ROOM_ENCRYPTION,
stateKey = "",
content = contentMap.toContent()
)
}
}
private suspend fun canEnableEncryption(builder: CreateRoomParamsBuilder): Boolean {
return (builder.enableEncryptionIfInvitedUsersSupportIt
&& crossSigningService.isCrossSigningVerified()
&& builder.invite3pids.isEmpty())
&& builder.invitedUserIds.isNotEmpty()
&& builder.invitedUserIds.let { userIds ->
val keys = deviceListManager.downloadKeys(userIds, forceDownload = false)
userIds.all { userId ->
keys.map[userId].let { deviceMap ->
if (deviceMap.isNullOrEmpty()) {
// A user has no device, so do not enable encryption
false
} else {
// Check that every user's device have at least one key
deviceMap.values.all { !it.keys.isNullOrEmpty() }
}
}
}
}
}
}

View File

@ -1,5 +1,5 @@
/*
* Copyright 2019 New Vector Ltd
* Copyright (c) 2020 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.
@ -14,7 +14,7 @@
* limitations under the License.
*/
package im.vector.matrix.android.api.session.room.model.create
package im.vector.matrix.android.internal.session.room.create
import com.squareup.moshi.Json
import com.squareup.moshi.JsonClass

View File

@ -17,11 +17,8 @@
package im.vector.matrix.android.internal.session.room.create
import com.zhuinden.monarchy.Monarchy
import im.vector.matrix.android.api.session.crypto.crosssigning.CrossSigningService
import im.vector.matrix.android.api.session.room.failure.CreateRoomFailure
import im.vector.matrix.android.api.session.room.model.create.CreateRoomParams
import im.vector.matrix.android.api.session.room.model.create.CreateRoomResponse
import im.vector.matrix.android.internal.crypto.DeviceListManager
import im.vector.matrix.android.api.session.room.model.create.CreateRoomParamsBuilder
import im.vector.matrix.android.internal.database.awaitNotEmptyResult
import im.vector.matrix.android.internal.database.model.RoomEntity
import im.vector.matrix.android.internal.database.model.RoomEntityFields
@ -41,7 +38,7 @@ import org.greenrobot.eventbus.EventBus
import java.util.concurrent.TimeUnit
import javax.inject.Inject
internal interface CreateRoomTask : Task<CreateRoomParams, String>
internal interface CreateRoomTask : Task<CreateRoomParamsBuilder, String>
internal class DefaultCreateRoomTask @Inject constructor(
private val roomAPI: RoomAPI,
@ -51,17 +48,12 @@ internal class DefaultCreateRoomTask @Inject constructor(
private val readMarkersTask: SetReadMarkersTask,
@SessionDatabase
private val realmConfiguration: RealmConfiguration,
private val crossSigningService: CrossSigningService,
private val deviceListManager: DeviceListManager,
private val createRoomParamsInternalBuilder: CreateRoomParamsInternalBuilder,
private val eventBus: EventBus
) : CreateRoomTask {
override suspend fun execute(params: CreateRoomParams): String {
val createRoomParams = if (canEnableEncryption(params)) {
params.enableEncryptionWithAlgorithm()
} else {
params
}
override suspend fun execute(params: CreateRoomParamsBuilder): String {
val createRoomParams = createRoomParamsInternalBuilder.build(params)
val createRoomResponse = executeRequest<CreateRoomResponse>(eventBus) {
apiCall = roomAPI.createRoom(createRoomParams)
@ -76,36 +68,14 @@ internal class DefaultCreateRoomTask @Inject constructor(
} catch (exception: TimeoutCancellationException) {
throw CreateRoomFailure.CreatedWithTimeout
}
if (createRoomParams.isDirect()) {
handleDirectChatCreation(createRoomParams, roomId)
if (params.isDirect()) {
handleDirectChatCreation(params, roomId)
}
setReadMarkers(roomId)
return roomId
}
private suspend fun canEnableEncryption(params: CreateRoomParams): Boolean {
return params.enableEncryptionIfInvitedUsersSupportIt
&& crossSigningService.isCrossSigningVerified()
&& params.invite3pids.isNullOrEmpty()
&& params.invitedUserIds?.isNotEmpty() == true
&& params.invitedUserIds.let { userIds ->
val keys = deviceListManager.downloadKeys(userIds, forceDownload = false)
userIds.all { userId ->
keys.map[userId].let { deviceMap ->
if (deviceMap.isNullOrEmpty()) {
// A user has no device, so do not enable encryption
false
} else {
// Check that every user's device have at least one key
deviceMap.values.all { !it.keys.isNullOrEmpty() }
}
}
}
}
}
private suspend fun handleDirectChatCreation(params: CreateRoomParams, roomId: String) {
private suspend fun handleDirectChatCreation(params: CreateRoomParamsBuilder, roomId: String) {
val otherUserId = params.getFirstInvitedUserId()
?: throw IllegalStateException("You can't create a direct room without an invitedUser")

View File

@ -18,13 +18,13 @@ package im.vector.matrix.android.internal.session.room.membership.joining
import im.vector.matrix.android.api.session.room.failure.JoinRoomFailure
import im.vector.matrix.android.api.session.room.members.ChangeMembershipState
import im.vector.matrix.android.api.session.room.model.create.JoinRoomResponse
import im.vector.matrix.android.internal.database.awaitNotEmptyResult
import im.vector.matrix.android.internal.database.model.RoomEntity
import im.vector.matrix.android.internal.database.model.RoomEntityFields
import im.vector.matrix.android.internal.di.SessionDatabase
import im.vector.matrix.android.internal.network.executeRequest
import im.vector.matrix.android.internal.session.room.RoomAPI
import im.vector.matrix.android.internal.session.room.create.JoinRoomResponse
import im.vector.matrix.android.internal.session.room.membership.RoomChangeMembershipStateDataSource
import im.vector.matrix.android.internal.session.room.read.SetReadMarkersTask
import im.vector.matrix.android.internal.task.Task

View File

@ -22,8 +22,9 @@ import com.airbnb.mvrx.ViewModelContext
import com.squareup.inject.assisted.Assisted
import com.squareup.inject.assisted.AssistedInject
import im.vector.matrix.android.api.session.Session
import im.vector.matrix.android.api.session.room.model.create.CreateRoomParams
import im.vector.matrix.android.api.session.room.model.create.CreateRoomParamsBuilder
import im.vector.matrix.rx.rx
import im.vector.riotx.core.extensions.exhaustive
import im.vector.riotx.core.platform.VectorViewModel
import im.vector.riotx.features.userdirectory.PendingInvitee
@ -53,11 +54,17 @@ class CreateDirectRoomViewModel @AssistedInject constructor(@Assisted
}
private fun createRoomAndInviteSelectedUsers(selectedUsers: Set<PendingInvitee>) {
val roomParams = CreateRoomParams(
invitedUserIds = selectedUsers.map { it.userId }
)
.setDirectMessage()
.enableEncryptionIfInvitedUsersSupportIt()
val roomParams = CreateRoomParamsBuilder()
.apply {
selectedUsers.forEach {
when (it) {
is PendingInvitee.UserPendingInvitee -> invitedUserIds.add(it.user.userId)
is PendingInvitee.ThreePidPendingInvitee -> invite3pids.add(it.threePid)
}.exhaustive
}
setDirectMessage()
enableEncryptionIfInvitedUsersSupportIt = true
}
session.rx()
.createRoom(roomParams)

View File

@ -43,7 +43,7 @@ import im.vector.matrix.android.api.session.crypto.verification.VerificationServ
import im.vector.matrix.android.api.session.crypto.verification.VerificationTransaction
import im.vector.matrix.android.api.session.crypto.verification.VerificationTxState
import im.vector.matrix.android.api.session.events.model.LocalEcho
import im.vector.matrix.android.api.session.room.model.create.CreateRoomParams
import im.vector.matrix.android.api.session.room.model.create.CreateRoomParamsBuilder
import im.vector.matrix.android.api.util.MatrixItem
import im.vector.matrix.android.api.util.toMatrixItem
import im.vector.matrix.android.internal.crypto.crosssigning.fromBase64
@ -235,11 +235,12 @@ class VerificationBottomSheetViewModel @AssistedInject constructor(
pendingRequest = Loading()
)
}
val roomParams = CreateRoomParams(
invitedUserIds = listOf(otherUserId)
)
.setDirectMessage()
.enableEncryptionIfInvitedUsersSupportIt()
val roomParams = CreateRoomParamsBuilder()
.apply {
invitedUserIds.add(otherUserId)
setDirectMessage()
enableEncryptionIfInvitedUsersSupportIt = true
}
session.createRoom(roomParams, object : MatrixCallback<String> {
override fun onSuccess(data: String) {

View File

@ -16,9 +16,9 @@
package im.vector.riotx.features.invite
import im.vector.matrix.android.api.session.user.model.User
import im.vector.riotx.core.platform.VectorViewModelAction
import im.vector.riotx.features.userdirectory.PendingInvitee
sealed class InviteUsersToRoomAction : VectorViewModelAction {
data class InviteSelectedUsers(val selectedUsers: Set<User>) : InviteUsersToRoomAction()
data class InviteSelectedUsers(val selectedUsers: Set<PendingInvitee>) : InviteUsersToRoomAction()
}

View File

@ -22,11 +22,11 @@ import com.airbnb.mvrx.ViewModelContext
import com.squareup.inject.assisted.Assisted
import com.squareup.inject.assisted.AssistedInject
import im.vector.matrix.android.api.session.Session
import im.vector.matrix.android.api.session.user.model.User
import im.vector.matrix.rx.rx
import im.vector.riotx.R
import im.vector.riotx.core.platform.VectorViewModel
import im.vector.riotx.core.resources.StringProvider
import im.vector.riotx.features.userdirectory.PendingInvitee
import io.reactivex.Observable
class InviteUsersToRoomViewModel @AssistedInject constructor(@Assisted
@ -57,11 +57,14 @@ class InviteUsersToRoomViewModel @AssistedInject constructor(@Assisted
}
}
private fun inviteUsersToRoom(selectedUsers: Set<User>) {
private fun inviteUsersToRoom(selectedUsers: Set<PendingInvitee>) {
_viewEvents.post(InviteUsersToRoomViewEvents.Loading)
Observable.fromIterable(selectedUsers).flatMapCompletable { user ->
room.rx().invite(user.userId, null)
when (user) {
is PendingInvitee.UserPendingInvitee -> room.rx().invite(user.user.userId, null)
is PendingInvitee.ThreePidPendingInvitee -> room.rx().invite3pid(user.threePid)
}
}.subscribe(
{
val successMessage = when (selectedUsers.size) {

View File

@ -28,7 +28,7 @@ import com.squareup.inject.assisted.AssistedInject
import im.vector.matrix.android.api.MatrixCallback
import im.vector.matrix.android.api.session.Session
import im.vector.matrix.android.api.session.room.model.RoomDirectoryVisibility
import im.vector.matrix.android.api.session.room.model.create.CreateRoomParams
import im.vector.matrix.android.api.session.room.model.create.CreateRoomParamsBuilder
import im.vector.matrix.android.api.session.room.model.create.CreateRoomPreset
import im.vector.riotx.core.platform.EmptyViewEvents
import im.vector.riotx.core.platform.VectorViewModel
@ -84,15 +84,19 @@ class CreateRoomViewModel @AssistedInject constructor(@Assisted initialState: Cr
copy(asyncCreateRoomRequest = Loading())
}
val createRoomParams = CreateRoomParams(
name = state.roomName.takeIf { it.isNotBlank() },
// Directory visibility
visibility = if (state.isInRoomDirectory) RoomDirectoryVisibility.PUBLIC else RoomDirectoryVisibility.PRIVATE,
// Public room
preset = if (state.isPublic) CreateRoomPreset.PRESET_PUBLIC_CHAT else CreateRoomPreset.PRESET_PRIVATE_CHAT
)
// Encryption
.enableEncryptionWithAlgorithm(state.isEncrypted)
val createRoomParams = CreateRoomParamsBuilder()
.apply {
name = state.roomName.takeIf { it.isNotBlank() }
// Directory visibility
visibility = if (state.isInRoomDirectory) RoomDirectoryVisibility.PUBLIC else RoomDirectoryVisibility.PRIVATE
// Public room
preset = if (state.isPublic) CreateRoomPreset.PRESET_PUBLIC_CHAT else CreateRoomPreset.PRESET_PRIVATE_CHAT
// Encryption
if (state.isEncrypted) {
enableEncryption()
}
}
session.createRoom(createRoomParams, object : MatrixCallback<String> {
override fun onSuccess(data: String) {

View File

@ -160,10 +160,7 @@ class KnownUsersFragment @Inject constructor(
val chip = Chip(requireContext())
chip.setChipBackgroundColorResource(android.R.color.transparent)
chip.chipStrokeWidth = dimensionConverter.dpToPx(1).toFloat()
chip.text = when (pendingInvitee) {
is PendingInvitee.UserPendingInvitee -> pendingInvitee.user.getBestName()
is PendingInvitee.ThreePidPendingInvitee -> pendingInvitee.threePid.value
}
chip.text = pendingInvitee.getBestName()
chip.isClickable = true
chip.isCheckable = false
chip.isCloseIconVisible = true

View File

@ -20,6 +20,13 @@ import im.vector.matrix.android.api.session.identity.ThreePid
import im.vector.matrix.android.api.session.user.model.User
sealed class PendingInvitee {
data class UserPendingInvitee(val user: User): PendingInvitee()
data class ThreePidPendingInvitee(val threePid: ThreePid): PendingInvitee()
data class UserPendingInvitee(val user: User) : PendingInvitee()
data class ThreePidPendingInvitee(val threePid: ThreePid) : PendingInvitee()
fun getBestName(): String {
return when (this) {
is UserPendingInvitee -> user.getBestName()
is ThreePidPendingInvitee -> threePid.value
}
}
}

View File

@ -21,6 +21,7 @@ import android.view.View
import com.airbnb.mvrx.activityViewModel
import com.airbnb.mvrx.withState
import com.jakewharton.rxbinding3.widget.textChanges
import im.vector.matrix.android.api.session.user.model.User
import im.vector.riotx.R
import im.vector.riotx.core.extensions.cleanup
import im.vector.riotx.core.extensions.configureWith
@ -81,9 +82,9 @@ class UserDirectoryFragment @Inject constructor(
directRoomController.setData(it)
}
override fun onItemClick(pendingInvitee: PendingInvitee) {
override fun onItemClick(user: User) {
view?.hideKeyboard()
viewModel.handle(UserDirectoryAction.SelectPendingInvitee(pendingInvitee))
viewModel.handle(UserDirectoryAction.SelectPendingInvitee(PendingInvitee.UserPendingInvitee(user)))
sharedActionViewModel.post(UserDirectorySharedAction.GoBack)
}