Skip to content

Commit 5e4b8ed

Browse files
authored
Merge pull request #8700 from vector-im/feature/fga/handle_functional_members
Support Functional members #3736
2 parents bb9d1fc + bb86660 commit 5e4b8ed

File tree

10 files changed

+134
-10
lines changed

10 files changed

+134
-10
lines changed

changelog.d/3736.feature

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Support Functional members (https://github.com/vector-im/element-meta/blob/develop/spec/functional_members.md)

matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/common/TestRoomDisplayNameFallbackProvider.kt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@ import org.matrix.android.sdk.api.provider.RoomDisplayNameFallbackProvider
2020

2121
class TestRoomDisplayNameFallbackProvider : RoomDisplayNameFallbackProvider {
2222

23+
override fun excludedUserIds(roomId: String) = emptyList<String>()
24+
2325
override fun getNameForRoomInvite() =
2426
"Room invite"
2527

matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/provider/RoomDisplayNameFallbackProvider.kt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,10 @@ package org.matrix.android.sdk.api.provider
2525
* *Limitation*: if the locale of the device changes, the methods will not be called again.
2626
*/
2727
interface RoomDisplayNameFallbackProvider {
28+
/**
29+
* Return the list of user ids to ignore when computing the room display name.
30+
*/
31+
fun excludedUserIds(roomId: String): List<String>
2832
fun getNameForRoomInvite(): String
2933
fun getNameForEmptyRoom(isDirect: Boolean, leftMemberNames: List<String>): String
3034
fun getNameFor1member(name: String): String

matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/RoomAvatarResolver.kt

Lines changed: 17 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
package org.matrix.android.sdk.internal.session.room
1818

1919
import io.realm.Realm
20+
import org.matrix.android.sdk.api.MatrixConfiguration
2021
import org.matrix.android.sdk.api.extensions.orFalse
2122
import org.matrix.android.sdk.api.session.events.model.EventType
2223
import org.matrix.android.sdk.api.session.events.model.toModel
@@ -31,7 +32,12 @@ import org.matrix.android.sdk.internal.di.UserId
3132
import org.matrix.android.sdk.internal.session.room.membership.RoomMemberHelper
3233
import javax.inject.Inject
3334

34-
internal class RoomAvatarResolver @Inject constructor(@UserId private val userId: String) {
35+
internal class RoomAvatarResolver @Inject constructor(
36+
matrixConfiguration: MatrixConfiguration,
37+
@UserId private val userId: String
38+
) {
39+
40+
private val roomDisplayNameFallbackProvider = matrixConfiguration.roomDisplayNameFallbackProvider
3541

3642
/**
3743
* Compute the room avatar url.
@@ -40,21 +46,26 @@ internal class RoomAvatarResolver @Inject constructor(@UserId private val userId
4046
* @return the room avatar url, can be a fallback to a room member avatar or null
4147
*/
4248
fun resolve(realm: Realm, roomId: String): String? {
43-
val roomName = CurrentStateEventEntity.getOrNull(realm, roomId, type = EventType.STATE_ROOM_AVATAR, stateKey = "")
49+
val roomAvatarUrl = CurrentStateEventEntity.getOrNull(realm, roomId, type = EventType.STATE_ROOM_AVATAR, stateKey = "")
4450
?.root
4551
?.asDomain()
4652
?.content
4753
?.toModel<RoomAvatarContent>()
4854
?.avatarUrl
49-
if (!roomName.isNullOrEmpty()) {
50-
return roomName
55+
if (!roomAvatarUrl.isNullOrEmpty()) {
56+
return roomAvatarUrl
5157
}
52-
val roomMembers = RoomMemberHelper(realm, roomId)
53-
val members = roomMembers.queryActiveRoomMembersEvent().findAll()
5458
// detect if it is a room with no more than 2 members (i.e. an alone or a 1:1 chat)
5559
val isDirectRoom = RoomSummaryEntity.where(realm, roomId).findFirst()?.isDirect.orFalse()
5660

5761
if (isDirectRoom) {
62+
val excludedUserIds = roomDisplayNameFallbackProvider.excludedUserIds(roomId)
63+
val roomMembers = RoomMemberHelper(realm, roomId)
64+
val members = roomMembers
65+
.queryActiveRoomMembersEvent()
66+
.not().`in`(RoomMemberSummaryEntityFields.USER_ID, excludedUserIds.toTypedArray())
67+
.findAll()
68+
5869
if (members.size == 1) {
5970
// Use avatar of a left user
6071
val firstLeftAvatarUrl = roomMembers.queryLeftRoomMembersEvent()

matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/membership/RoomDisplayNameResolver.kt

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -92,18 +92,20 @@ internal class RoomDisplayNameResolver @Inject constructor(
9292
}
9393
?: roomDisplayNameFallbackProvider.getNameForRoomInvite()
9494
} else if (roomEntity?.membership == Membership.JOIN) {
95+
val excludedUserIds = roomDisplayNameFallbackProvider.excludedUserIds(roomId)
9596
val roomSummary = RoomSummaryEntity.where(realm, roomId).findFirst()
9697
val invitedCount = roomSummary?.invitedMembersCount ?: 0
9798
val joinedCount = roomSummary?.joinedMembersCount ?: 0
9899
val otherMembersSubset: List<RoomMemberSummaryEntity> = if (roomSummary?.heroes?.isNotEmpty() == true) {
99100
roomSummary.heroes.mapNotNull { userId ->
100101
roomMembers.getLastRoomMember(userId)?.takeIf {
101-
it.membership == Membership.INVITE || it.membership == Membership.JOIN
102+
(it.membership == Membership.INVITE || it.membership == Membership.JOIN) && !excludedUserIds.contains(it.userId)
102103
}
103104
}
104105
} else {
105106
activeMembers.where()
106107
.notEqualTo(RoomMemberSummaryEntityFields.USER_ID, userId)
108+
.not().`in`(RoomMemberSummaryEntityFields.USER_ID, excludedUserIds.toTypedArray())
107109
.limit(5)
108110
.findAll()
109111
.createSnapshot()
@@ -113,6 +115,7 @@ internal class RoomDisplayNameResolver @Inject constructor(
113115
0 -> {
114116
// Get left members if any
115117
val leftMembersNames = roomMembers.queryLeftRoomMembersEvent()
118+
.not().`in`(RoomMemberSummaryEntityFields.USER_ID, excludedUserIds.toTypedArray())
116119
.findAll()
117120
.map { displayNameResolver.getBestName(it.toMatrixItem()) }
118121
val directUserId = roomSummary?.directUserId

vector-app/src/androidTest/java/im/vector/app/core/utils/TestMatrixHelper.kt

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,15 +17,14 @@
1717
package im.vector.app.core.utils
1818

1919
import androidx.test.platform.app.InstrumentationRegistry
20-
import im.vector.app.features.room.VectorRoomDisplayNameFallbackProvider
2120
import org.matrix.android.sdk.api.Matrix
2221
import org.matrix.android.sdk.api.MatrixConfiguration
2322
import org.matrix.android.sdk.api.SyncConfig
2423

2524
fun getMatrixInstance(): Matrix {
2625
val context = InstrumentationRegistry.getInstrumentation().targetContext
2726
val configuration = MatrixConfiguration(
28-
roomDisplayNameFallbackProvider = VectorRoomDisplayNameFallbackProvider(context),
27+
roomDisplayNameFallbackProvider = TestRoomDisplayNameFallbackProvider(),
2928
syncConfig = SyncConfig(longPollTimeout = 5_000L),
3029
)
3130
return Matrix(context, configuration)
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
/*
2+
* Copyright (c) 2021 The Matrix.org Foundation C.I.C.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package im.vector.app.core.utils
18+
19+
import org.matrix.android.sdk.api.provider.RoomDisplayNameFallbackProvider
20+
21+
class TestRoomDisplayNameFallbackProvider : RoomDisplayNameFallbackProvider {
22+
23+
override fun excludedUserIds(roomId: String) = emptyList<String>()
24+
25+
override fun getNameForRoomInvite() =
26+
"Room invite"
27+
28+
override fun getNameForEmptyRoom(isDirect: Boolean, leftMemberNames: List<String>) =
29+
"Empty room"
30+
31+
override fun getNameFor1member(name: String) =
32+
name
33+
34+
override fun getNameFor2members(name1: String, name2: String) =
35+
"$name1 and $name2"
36+
37+
override fun getNameFor3members(name1: String, name2: String, name3: String) =
38+
"$name1, $name2 and $name3"
39+
40+
override fun getNameFor4members(name1: String, name2: String, name3: String, name4: String) =
41+
"$name1, $name2, $name3 and $name4"
42+
43+
override fun getNameFor4membersAndMore(name1: String, name2: String, name3: String, remainingCount: Int) =
44+
"$name1, $name2, $name3 and $remainingCount others"
45+
}

vector-config/src/main/java/im/vector/app/config/Config.kt

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,12 @@ object Config {
4242
const val ENABLE_LOCATION_SHARING = true
4343
const val LOCATION_MAP_TILER_KEY = "fU3vlMsMn4Jb6dnEIFsx"
4444

45+
/**
46+
* Whether to read the `io.element.functional_members` state event
47+
* and exclude any service members when computing a room's name and avatar.
48+
*/
49+
const val SUPPORT_FUNCTIONAL_MEMBERS = true
50+
4551
/**
4652
* The maximum length of voice messages in milliseconds.
4753
*/
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
/*
2+
* Copyright (c) 2023 New Vector Ltd
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package im.vector.app.features.room
18+
19+
import com.squareup.moshi.Json
20+
import com.squareup.moshi.JsonClass
21+
import org.matrix.android.sdk.api.query.QueryStringValue
22+
import org.matrix.android.sdk.api.session.events.model.toModel
23+
import org.matrix.android.sdk.api.session.room.state.StateService
24+
25+
private const val FUNCTIONAL_MEMBERS_STATE_EVENT_TYPE = "io.element.functional_members"
26+
27+
@JsonClass(generateAdapter = true)
28+
data class FunctionalMembersContent(
29+
@Json(name = "service_members") val userIds: List<String>? = null
30+
)
31+
32+
fun StateService.getFunctionalMembers(): List<String> {
33+
return getStateEvent(FUNCTIONAL_MEMBERS_STATE_EVENT_TYPE, QueryStringValue.IsEmpty)
34+
?.content
35+
?.toModel<FunctionalMembersContent>()
36+
?.userIds
37+
.orEmpty()
38+
}

vector/src/main/java/im/vector/app/features/room/VectorRoomDisplayNameFallbackProvider.kt

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,13 +18,28 @@ package im.vector.app.features.room
1818

1919
import android.content.Context
2020
import im.vector.app.R
21+
import im.vector.app.config.Config
22+
import im.vector.app.core.di.ActiveSessionHolder
2123
import org.matrix.android.sdk.api.provider.RoomDisplayNameFallbackProvider
24+
import org.matrix.android.sdk.api.session.getRoom
2225
import javax.inject.Inject
26+
import javax.inject.Provider
2327

2428
class VectorRoomDisplayNameFallbackProvider @Inject constructor(
25-
private val context: Context
29+
private val context: Context,
30+
private val activeSessionHolder: Provider<ActiveSessionHolder>,
2631
) : RoomDisplayNameFallbackProvider {
2732

33+
override fun excludedUserIds(roomId: String): List<String> {
34+
if (!Config.SUPPORT_FUNCTIONAL_MEMBERS) return emptyList()
35+
return activeSessionHolder.get()
36+
.getSafeActiveSession()
37+
?.getRoom(roomId)
38+
?.stateService()
39+
?.getFunctionalMembers()
40+
.orEmpty()
41+
}
42+
2843
override fun getNameForRoomInvite(): String {
2944
return context.getString(R.string.room_displayname_room_invite)
3045
}

0 commit comments

Comments
 (0)