From 72508c61d92886720017b4b0a979754ee9d9f65b Mon Sep 17 00:00:00 2001 From: Adam Brown Date: Wed, 27 Oct 2021 12:19:49 +0100 Subject: [PATCH 01/20] porting QueryStringValue to sealed interface with a sub category for the content based values - allows for handling those cases separately for normalisation --- .../android/sdk/api/query/QueryStringValue.kt | 19 +++++++++++-------- .../query/QueryStringValueProcessor.kt | 15 ++++++++++----- 2 files changed, 21 insertions(+), 13 deletions(-) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/query/QueryStringValue.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/query/QueryStringValue.kt index 8f83beface..1490bff6a8 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/query/QueryStringValue.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/query/QueryStringValue.kt @@ -19,14 +19,17 @@ package org.matrix.android.sdk.api.query /** * Basic query language. All these cases are mutually exclusive. */ -sealed class QueryStringValue { - object NoCondition : QueryStringValue() - object IsNull : QueryStringValue() - object IsNotNull : QueryStringValue() - object IsEmpty : QueryStringValue() - object IsNotEmpty : QueryStringValue() - data class Equals(val string: String, val case: Case = Case.SENSITIVE) : QueryStringValue() - data class Contains(val string: String, val case: Case = Case.SENSITIVE) : QueryStringValue() +sealed interface QueryStringValue { + sealed interface ContentQueryStringValue : QueryStringValue + + object NoCondition : QueryStringValue + object IsNull : QueryStringValue + object IsNotNull : QueryStringValue + object IsEmpty : QueryStringValue + object IsNotEmpty : QueryStringValue + + data class Equals(val string: String, val case: Case = Case.SENSITIVE) : ContentQueryStringValue + data class Contains(val string: String, val case: Case = Case.SENSITIVE) : ContentQueryStringValue enum class Case { SENSITIVE, diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/query/QueryStringValueProcessor.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/query/QueryStringValueProcessor.kt index fd33682231..b3f207e7f3 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/query/QueryStringValueProcessor.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/query/QueryStringValueProcessor.kt @@ -20,19 +20,24 @@ import io.realm.Case import io.realm.RealmObject import io.realm.RealmQuery import org.matrix.android.sdk.api.query.QueryStringValue +import org.matrix.android.sdk.api.query.QueryStringValue.ContentQueryStringValue import timber.log.Timber fun RealmQuery.process(field: String, queryStringValue: QueryStringValue): RealmQuery { - when (queryStringValue) { - is QueryStringValue.NoCondition -> Timber.v("No condition to process") + return when (queryStringValue) { + is QueryStringValue.NoCondition -> { + Timber.v("No condition to process") + this + } is QueryStringValue.IsNotNull -> isNotNull(field) is QueryStringValue.IsNull -> isNull(field) is QueryStringValue.IsEmpty -> isEmpty(field) is QueryStringValue.IsNotEmpty -> isNotEmpty(field) - is QueryStringValue.Equals -> equalTo(field, queryStringValue.string, queryStringValue.case.toRealmCase()) - is QueryStringValue.Contains -> contains(field, queryStringValue.string, queryStringValue.case.toRealmCase()) + is ContentQueryStringValue -> when (queryStringValue) { + is QueryStringValue.Equals -> equalTo(field, queryStringValue.string, queryStringValue.case.toRealmCase()) + is QueryStringValue.Contains -> contains(field, queryStringValue.string, queryStringValue.case.toRealmCase()) + } } - return this } private fun QueryStringValue.Case.toRealmCase(): Case { From 2681601d35aae66f9fe8ff0f2dba412506d1bd86 Mon Sep 17 00:00:00 2001 From: Adam Brown Date: Wed, 27 Oct 2021 12:30:23 +0100 Subject: [PATCH 02/20] making value processing an injectable class, it will need to have its own dependencies to support normalisation --- .../query/QueryStringValueProcessor.kt | 30 +++++++++++-------- .../session/group/DefaultGroupService.kt | 16 ++++++---- .../membership/DefaultMembershipService.kt | 22 ++++++++------ .../room/state/StateEventDataSource.kt | 24 +++++++++------ .../room/summary/RoomSummaryDataSource.kt | 22 +++++++++----- 5 files changed, 70 insertions(+), 44 deletions(-) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/query/QueryStringValueProcessor.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/query/QueryStringValueProcessor.kt index b3f207e7f3..d323943ad4 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/query/QueryStringValueProcessor.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/query/QueryStringValueProcessor.kt @@ -22,20 +22,24 @@ import io.realm.RealmQuery import org.matrix.android.sdk.api.query.QueryStringValue import org.matrix.android.sdk.api.query.QueryStringValue.ContentQueryStringValue import timber.log.Timber +import javax.inject.Inject -fun RealmQuery.process(field: String, queryStringValue: QueryStringValue): RealmQuery { - return when (queryStringValue) { - is QueryStringValue.NoCondition -> { - Timber.v("No condition to process") - this - } - is QueryStringValue.IsNotNull -> isNotNull(field) - is QueryStringValue.IsNull -> isNull(field) - is QueryStringValue.IsEmpty -> isEmpty(field) - is QueryStringValue.IsNotEmpty -> isNotEmpty(field) - is ContentQueryStringValue -> when (queryStringValue) { - is QueryStringValue.Equals -> equalTo(field, queryStringValue.string, queryStringValue.case.toRealmCase()) - is QueryStringValue.Contains -> contains(field, queryStringValue.string, queryStringValue.case.toRealmCase()) +class QueryStringValueProcessor @Inject constructor() { + + fun RealmQuery.process(field: String, queryStringValue: QueryStringValue): RealmQuery { + return when (queryStringValue) { + is QueryStringValue.NoCondition -> { + Timber.v("No condition to process") + this + } + is QueryStringValue.IsNotNull -> isNotNull(field) + is QueryStringValue.IsNull -> isNull(field) + is QueryStringValue.IsEmpty -> isEmpty(field) + is QueryStringValue.IsNotEmpty -> isNotEmpty(field) + is ContentQueryStringValue -> when (queryStringValue) { + is QueryStringValue.Equals -> equalTo(field, queryStringValue.string, queryStringValue.case.toRealmCase()) + is QueryStringValue.Contains -> contains(field, queryStringValue.string, queryStringValue.case.toRealmCase()) + } } } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/group/DefaultGroupService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/group/DefaultGroupService.kt index 8dc5f3931d..9334d09377 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/group/DefaultGroupService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/group/DefaultGroupService.kt @@ -30,12 +30,16 @@ import org.matrix.android.sdk.internal.database.model.GroupSummaryEntity import org.matrix.android.sdk.internal.database.model.GroupSummaryEntityFields import org.matrix.android.sdk.internal.database.query.where import org.matrix.android.sdk.internal.di.SessionDatabase +import org.matrix.android.sdk.internal.query.QueryStringValueProcessor import org.matrix.android.sdk.internal.query.process import org.matrix.android.sdk.internal.util.fetchCopyMap import javax.inject.Inject -internal class DefaultGroupService @Inject constructor(@SessionDatabase private val monarchy: Monarchy, - private val groupFactory: GroupFactory) : GroupService { +internal class DefaultGroupService @Inject constructor( + @SessionDatabase private val monarchy: Monarchy, + private val groupFactory: GroupFactory, + private val queryStringValueProcessor: QueryStringValueProcessor, +) : GroupService { override fun getGroup(groupId: String): Group? { return Realm.getInstance(monarchy.realmConfiguration).use { realm -> @@ -67,8 +71,10 @@ internal class DefaultGroupService @Inject constructor(@SessionDatabase private } private fun groupSummariesQuery(realm: Realm, queryParams: GroupSummaryQueryParams): RealmQuery { - return GroupSummaryEntity.where(realm) - .process(GroupSummaryEntityFields.DISPLAY_NAME, queryParams.displayName) - .process(GroupSummaryEntityFields.MEMBERSHIP_STR, queryParams.memberships) + return with(queryStringValueProcessor) { + GroupSummaryEntity.where(realm) + .process(GroupSummaryEntityFields.DISPLAY_NAME, queryParams.displayName) + .process(GroupSummaryEntityFields.MEMBERSHIP_STR, queryParams.memberships) + } } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/membership/DefaultMembershipService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/membership/DefaultMembershipService.kt index 204deb72b4..6cf82dde44 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/membership/DefaultMembershipService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/membership/DefaultMembershipService.kt @@ -33,6 +33,7 @@ import org.matrix.android.sdk.internal.database.model.RoomMemberSummaryEntity import org.matrix.android.sdk.internal.database.model.RoomMemberSummaryEntityFields import org.matrix.android.sdk.internal.di.SessionDatabase import org.matrix.android.sdk.internal.di.UserId +import org.matrix.android.sdk.internal.query.QueryStringValueProcessor import org.matrix.android.sdk.internal.query.process import org.matrix.android.sdk.internal.session.room.membership.admin.MembershipAdminTask import org.matrix.android.sdk.internal.session.room.membership.joining.InviteTask @@ -51,7 +52,8 @@ internal class DefaultMembershipService @AssistedInject constructor( private val leaveRoomTask: LeaveRoomTask, private val membershipAdminTask: MembershipAdminTask, @UserId - private val userId: String + private val userId: String, + private val queryStringValueProcessor: QueryStringValueProcessor ) : MembershipService { @AssistedFactory @@ -94,15 +96,17 @@ internal class DefaultMembershipService @AssistedInject constructor( } private fun roomMembersQuery(realm: Realm, queryParams: RoomMemberQueryParams): RealmQuery { - return RoomMemberHelper(realm, roomId).queryRoomMembersEvent() - .process(RoomMemberSummaryEntityFields.USER_ID, queryParams.userId) - .process(RoomMemberSummaryEntityFields.MEMBERSHIP_STR, queryParams.memberships) - .process(RoomMemberSummaryEntityFields.DISPLAY_NAME, queryParams.displayName) - .apply { - if (queryParams.excludeSelf) { - notEqualTo(RoomMemberSummaryEntityFields.USER_ID, userId) + return with(queryStringValueProcessor) { + RoomMemberHelper(realm, roomId).queryRoomMembersEvent() + .process(RoomMemberSummaryEntityFields.USER_ID, queryParams.userId) + .process(RoomMemberSummaryEntityFields.MEMBERSHIP_STR, queryParams.memberships) + .process(RoomMemberSummaryEntityFields.DISPLAY_NAME, queryParams.displayName) + .apply { + if (queryParams.excludeSelf) { + notEqualTo(RoomMemberSummaryEntityFields.USER_ID, userId) + } } - } + } } override fun getNumberOfJoinedMembers(): Int { diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/state/StateEventDataSource.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/state/StateEventDataSource.kt index a25a362bfa..2114b9c590 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/state/StateEventDataSource.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/state/StateEventDataSource.kt @@ -31,11 +31,15 @@ import org.matrix.android.sdk.internal.database.mapper.asDomain import org.matrix.android.sdk.internal.database.model.CurrentStateEventEntity import org.matrix.android.sdk.internal.database.model.CurrentStateEventEntityFields import org.matrix.android.sdk.internal.di.SessionDatabase +import org.matrix.android.sdk.internal.query.QueryStringValueProcessor import org.matrix.android.sdk.internal.query.process import javax.inject.Inject -internal class StateEventDataSource @Inject constructor(@SessionDatabase private val monarchy: Monarchy, - private val realmSessionProvider: RealmSessionProvider) { +internal class StateEventDataSource @Inject constructor( + @SessionDatabase private val monarchy: Monarchy, + private val realmSessionProvider: RealmSessionProvider, + private val queryStringValueProcessor: QueryStringValueProcessor +) { fun getStateEvent(roomId: String, eventType: String, stateKey: QueryStringValue): Event? { return realmSessionProvider.withRealm { realm -> @@ -78,13 +82,15 @@ internal class StateEventDataSource @Inject constructor(@SessionDatabase private eventTypes: Set, stateKey: QueryStringValue ): RealmQuery { - return realm.where() - .equalTo(CurrentStateEventEntityFields.ROOM_ID, roomId) - .apply { - if (eventTypes.isNotEmpty()) { - `in`(CurrentStateEventEntityFields.TYPE, eventTypes.toTypedArray()) + return with(queryStringValueProcessor) { + realm.where() + .equalTo(CurrentStateEventEntityFields.ROOM_ID, roomId) + .apply { + if (eventTypes.isNotEmpty()) { + `in`(CurrentStateEventEntityFields.TYPE, eventTypes.toTypedArray()) + } } - } - .process(CurrentStateEventEntityFields.STATE_KEY, stateKey) + .process(CurrentStateEventEntityFields.STATE_KEY, stateKey) + } } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/summary/RoomSummaryDataSource.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/summary/RoomSummaryDataSource.kt index 0b8c6df806..8e6d97be80 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/summary/RoomSummaryDataSource.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/summary/RoomSummaryDataSource.kt @@ -48,12 +48,16 @@ import org.matrix.android.sdk.internal.database.model.RoomSummaryEntityFields import org.matrix.android.sdk.internal.database.query.findByAlias import org.matrix.android.sdk.internal.database.query.where import org.matrix.android.sdk.internal.di.SessionDatabase +import org.matrix.android.sdk.internal.query.QueryStringValueProcessor import org.matrix.android.sdk.internal.query.process import org.matrix.android.sdk.internal.util.fetchCopyMap import javax.inject.Inject -internal class RoomSummaryDataSource @Inject constructor(@SessionDatabase private val monarchy: Monarchy, - private val roomSummaryMapper: RoomSummaryMapper) { +internal class RoomSummaryDataSource @Inject constructor( + @SessionDatabase private val monarchy: Monarchy, + private val roomSummaryMapper: RoomSummaryMapper, + private val queryStringValueProcessor: QueryStringValueProcessor +) { fun getRoomSummary(roomIdOrAlias: String): RoomSummary? { return monarchy @@ -238,12 +242,14 @@ internal class RoomSummaryDataSource @Inject constructor(@SessionDatabase privat } private fun roomSummariesQuery(realm: Realm, queryParams: RoomSummaryQueryParams): RealmQuery { - val query = RoomSummaryEntity.where(realm) - query.process(RoomSummaryEntityFields.ROOM_ID, queryParams.roomId) - query.process(RoomSummaryEntityFields.DISPLAY_NAME, queryParams.displayName) - query.process(RoomSummaryEntityFields.CANONICAL_ALIAS, queryParams.canonicalAlias) - query.process(RoomSummaryEntityFields.MEMBERSHIP_STR, queryParams.memberships) - query.equalTo(RoomSummaryEntityFields.IS_HIDDEN_FROM_USER, false) + val query = with(queryStringValueProcessor) { + val query = RoomSummaryEntity.where(realm) + query.process(RoomSummaryEntityFields.ROOM_ID, queryParams.roomId) + query.process(RoomSummaryEntityFields.DISPLAY_NAME, queryParams.displayName) + query.process(RoomSummaryEntityFields.CANONICAL_ALIAS, queryParams.canonicalAlias) + query.process(RoomSummaryEntityFields.MEMBERSHIP_STR, queryParams.memberships) + query.equalTo(RoomSummaryEntityFields.IS_HIDDEN_FROM_USER, false) + } queryParams.roomCategoryFilter?.let { when (it) { From d5ed95988d80d4a723ca8d95dd0fe4b52a1c4e30 Mon Sep 17 00:00:00 2001 From: Adam Brown Date: Wed, 27 Oct 2021 12:46:17 +0100 Subject: [PATCH 03/20] adding normalisation to the query string cases --- .../android/sdk/api/query/QueryStringValue.kt | 12 ++++++--- .../query/QueryStringValueProcessor.kt | 20 ++++++++++---- .../android/sdk/internal/util/Normalizer.kt | 27 +++++++++++++++++++ 3 files changed, 50 insertions(+), 9 deletions(-) create mode 100644 matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/util/Normalizer.kt diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/query/QueryStringValue.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/query/QueryStringValue.kt index 1490bff6a8..5141751888 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/query/QueryStringValue.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/query/QueryStringValue.kt @@ -20,7 +20,10 @@ package org.matrix.android.sdk.api.query * Basic query language. All these cases are mutually exclusive. */ sealed interface QueryStringValue { - sealed interface ContentQueryStringValue : QueryStringValue + sealed interface ContentQueryStringValue : QueryStringValue { + val string: String + val case: Case + } object NoCondition : QueryStringValue object IsNull : QueryStringValue @@ -28,11 +31,12 @@ sealed interface QueryStringValue { object IsEmpty : QueryStringValue object IsNotEmpty : QueryStringValue - data class Equals(val string: String, val case: Case = Case.SENSITIVE) : ContentQueryStringValue - data class Contains(val string: String, val case: Case = Case.SENSITIVE) : ContentQueryStringValue + data class Equals(override val string: String, override val case: Case = Case.SENSITIVE) : ContentQueryStringValue + data class Contains(override val string: String, override val case: Case = Case.SENSITIVE) : ContentQueryStringValue enum class Case { SENSITIVE, - INSENSITIVE + INSENSITIVE, + NORMALIZED } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/query/QueryStringValueProcessor.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/query/QueryStringValueProcessor.kt index d323943ad4..33564eb610 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/query/QueryStringValueProcessor.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/query/QueryStringValueProcessor.kt @@ -21,10 +21,13 @@ import io.realm.RealmObject import io.realm.RealmQuery import org.matrix.android.sdk.api.query.QueryStringValue import org.matrix.android.sdk.api.query.QueryStringValue.ContentQueryStringValue +import org.matrix.android.sdk.internal.util.Normalizer import timber.log.Timber import javax.inject.Inject -class QueryStringValueProcessor @Inject constructor() { +class QueryStringValueProcessor @Inject constructor( + private val normalizer: Normalizer +) { fun RealmQuery.process(field: String, queryStringValue: QueryStringValue): RealmQuery { return when (queryStringValue) { @@ -37,16 +40,23 @@ class QueryStringValueProcessor @Inject constructor() { is QueryStringValue.IsEmpty -> isEmpty(field) is QueryStringValue.IsNotEmpty -> isNotEmpty(field) is ContentQueryStringValue -> when (queryStringValue) { - is QueryStringValue.Equals -> equalTo(field, queryStringValue.string, queryStringValue.case.toRealmCase()) - is QueryStringValue.Contains -> contains(field, queryStringValue.string, queryStringValue.case.toRealmCase()) + is QueryStringValue.Equals -> equalTo(field, queryStringValue.toRealmValue(), queryStringValue.case.toRealmCase()) + is QueryStringValue.Contains -> contains(field, queryStringValue.toRealmValue(), queryStringValue.case.toRealmCase()) } } } + + private fun ContentQueryStringValue.toRealmValue(): String { + return when (case) { + QueryStringValue.Case.NORMALIZED -> normalizer.normalize(string) + QueryStringValue.Case.SENSITIVE, QueryStringValue.Case.INSENSITIVE -> string + } + } } private fun QueryStringValue.Case.toRealmCase(): Case { return when (this) { - QueryStringValue.Case.INSENSITIVE -> Case.INSENSITIVE - QueryStringValue.Case.SENSITIVE -> Case.SENSITIVE + QueryStringValue.Case.INSENSITIVE -> Case.INSENSITIVE + QueryStringValue.Case.SENSITIVE, QueryStringValue.Case.NORMALIZED -> Case.SENSITIVE } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/util/Normalizer.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/util/Normalizer.kt new file mode 100644 index 0000000000..2df1faa784 --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/util/Normalizer.kt @@ -0,0 +1,27 @@ +/* + * Copyright (c) 2021 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.matrix.android.sdk.internal.util + +import java.text.Normalizer +import javax.inject.Inject + +class Normalizer @Inject constructor() { + + fun normalize(input: String): String { + return Normalizer.normalize(input.lowercase(), Normalizer.Form.NFD) + } +} From 4ae04fc29717f1bbbb16e43731de585c4babe110 Mon Sep 17 00:00:00 2001 From: Adam Brown Date: Wed, 27 Oct 2021 13:06:55 +0100 Subject: [PATCH 04/20] adding normalised room display name field and making use of it when filtering rooms by name - fixes non latin-1 character set room names from being ignored when searching with inexact casing --- .../android/sdk/api/query/QueryStringValue.kt | 2 ++ .../database/RealmSessionStoreMigration.kt | 23 +++++++++++++++++-- .../SessionRealmConfigurationFactory.kt | 3 ++- .../database/model/RoomSummaryEntity.kt | 12 ++++++++++ .../room/summary/RoomSummaryDataSource.kt | 6 ++++- .../room/summary/RoomSummaryUpdater.kt | 8 +++++-- .../android/sdk/internal/util/Normalizer.kt | 4 ++-- .../home/room/list/RoomListViewModel.kt | 2 +- 8 files changed, 51 insertions(+), 9 deletions(-) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/query/QueryStringValue.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/query/QueryStringValue.kt index 5141751888..da32d745d6 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/query/QueryStringValue.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/query/QueryStringValue.kt @@ -39,4 +39,6 @@ sealed interface QueryStringValue { INSENSITIVE, NORMALIZED } + + fun isNormalized() = this is ContentQueryStringValue && case == Case.NORMALIZED } 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 05137f8105..d6e86fd22b 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 @@ -45,11 +45,17 @@ import org.matrix.android.sdk.internal.database.model.TimelineEventEntityFields import org.matrix.android.sdk.internal.database.model.presence.UserPresenceEntityFields import org.matrix.android.sdk.internal.di.MoshiProvider import org.matrix.android.sdk.internal.query.process +import org.matrix.android.sdk.internal.util.Normalizer import timber.log.Timber +import javax.inject.Inject -internal object RealmSessionStoreMigration : RealmMigration { +internal class RealmSessionStoreMigration @Inject constructor( + private val normalizer: Normalizer +) : RealmMigration { - const val SESSION_STORE_SCHEMA_VERSION = 18L + companion object { + const val SESSION_STORE_SCHEMA_VERSION = 19L + } override fun migrate(realm: DynamicRealm, oldVersion: Long, newVersion: Long) { Timber.v("Migrating Realm Session from $oldVersion to $newVersion") @@ -72,6 +78,7 @@ internal object RealmSessionStoreMigration : RealmMigration { if (oldVersion <= 15) migrateTo16(realm) if (oldVersion <= 16) migrateTo17(realm) if (oldVersion <= 17) migrateTo18(realm) + if (oldVersion <= 18) migrateTo19(realm) } private fun migrateTo1(realm: DynamicRealm) { @@ -364,4 +371,16 @@ internal object RealmSessionStoreMigration : RealmMigration { realm.schema.get("RoomMemberSummaryEntity") ?.addRealmObjectField(RoomMemberSummaryEntityFields.USER_PRESENCE_ENTITY.`$`, userPresenceEntity) } + + private fun migrateTo19(realm: DynamicRealm) { + Timber.d("Step 18 -> 19") + realm.schema.get("RoomSummaryEntity") + ?.addField(RoomSummaryEntityFields.NORMALIZED_DISPLAY_NAME, String::class.java) + ?.transform { + it.getString(RoomSummaryEntityFields.DISPLAY_NAME)?.let { displayName -> + val normalised = normalizer.normalize(displayName) + it.set(RoomSummaryEntityFields.NORMALIZED_DISPLAY_NAME, normalised) + } + } + } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/SessionRealmConfigurationFactory.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/SessionRealmConfigurationFactory.kt index 1771c5b202..04ca26a943 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/SessionRealmConfigurationFactory.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/SessionRealmConfigurationFactory.kt @@ -40,6 +40,7 @@ private const val REALM_NAME = "disk_store.realm" */ internal class SessionRealmConfigurationFactory @Inject constructor( private val realmKeysUtils: RealmKeysUtils, + private val realmSessionStoreMigration: RealmSessionStoreMigration, @SessionFilesDirectory val directory: File, @SessionId val sessionId: String, @UserMd5 val userMd5: String, @@ -71,7 +72,7 @@ internal class SessionRealmConfigurationFactory @Inject constructor( .allowWritesOnUiThread(true) .modules(SessionRealmModule()) .schemaVersion(RealmSessionStoreMigration.SESSION_STORE_SCHEMA_VERSION) - .migration(RealmSessionStoreMigration) + .migration(realmSessionStoreMigration) .build() // Try creating a realm instance and if it succeeds we can clear the flag diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/RoomSummaryEntity.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/RoomSummaryEntity.kt index 88b8886936..23df6d6234 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/RoomSummaryEntity.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/RoomSummaryEntity.kt @@ -40,6 +40,17 @@ internal open class RoomSummaryEntity( set(value) { if (value != field) field = value } + + /** + * Workaround for Realm only supporting Latin-1 character sets when sorting + * or filtering by case + * See https://github.com/realm/realm-core/issues/777 + */ + var normalizedDisplayName: String? = "" + set(value) { + if (value != field) field = value + } + var avatarUrl: String? = "" set(value) { if (value != field) field = value @@ -284,5 +295,6 @@ internal open class RoomSummaryEntity( roomEncryptionTrustLevelStr = value?.name } } + companion object } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/summary/RoomSummaryDataSource.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/summary/RoomSummaryDataSource.kt index 8e6d97be80..e86bd27ed2 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/summary/RoomSummaryDataSource.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/summary/RoomSummaryDataSource.kt @@ -245,7 +245,11 @@ internal class RoomSummaryDataSource @Inject constructor( val query = with(queryStringValueProcessor) { val query = RoomSummaryEntity.where(realm) query.process(RoomSummaryEntityFields.ROOM_ID, queryParams.roomId) - query.process(RoomSummaryEntityFields.DISPLAY_NAME, queryParams.displayName) + if (queryParams.displayName.isNormalized()) { + query.process(RoomSummaryEntityFields.NORMALIZED_DISPLAY_NAME, queryParams.displayName) + } else { + query.process(RoomSummaryEntityFields.DISPLAY_NAME, queryParams.displayName) + } query.process(RoomSummaryEntityFields.CANONICAL_ALIAS, queryParams.canonicalAlias) query.process(RoomSummaryEntityFields.MEMBERSHIP_STR, queryParams.memberships) query.equalTo(RoomSummaryEntityFields.IS_HIDDEN_FROM_USER, false) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/summary/RoomSummaryUpdater.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/summary/RoomSummaryUpdater.kt index 30014f4539..03640d0555 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/summary/RoomSummaryUpdater.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/summary/RoomSummaryUpdater.kt @@ -65,6 +65,7 @@ import org.matrix.android.sdk.internal.session.room.accountdata.RoomAccountDataD import org.matrix.android.sdk.internal.session.room.membership.RoomDisplayNameResolver import org.matrix.android.sdk.internal.session.room.membership.RoomMemberHelper import org.matrix.android.sdk.internal.session.room.relationship.RoomChildRelationInfo +import org.matrix.android.sdk.internal.util.Normalizer import timber.log.Timber import javax.inject.Inject import kotlin.system.measureTimeMillis @@ -75,7 +76,8 @@ internal class RoomSummaryUpdater @Inject constructor( private val roomAvatarResolver: RoomAvatarResolver, private val eventDecryptor: EventDecryptor, private val crossSigningService: DefaultCrossSigningService, - private val roomAccountDataDataSource: RoomAccountDataDataSource) { + private val roomAccountDataDataSource: RoomAccountDataDataSource, + private val normalizer: Normalizer) { fun update(realm: Realm, roomId: String, @@ -136,7 +138,9 @@ internal class RoomSummaryUpdater @Inject constructor( // avoid this call if we are sure there are unread events !isEventRead(realm.configuration, userId, roomId, latestPreviewableEvent?.eventId) - roomSummaryEntity.displayName = roomDisplayNameResolver.resolve(realm, roomId) + val roomDisplayName = roomDisplayNameResolver.resolve(realm, roomId) + roomSummaryEntity.displayName = roomDisplayName + roomSummaryEntity.normalizedDisplayName = normalizer.normalize(roomDisplayName) roomSummaryEntity.avatarUrl = roomAvatarResolver.resolve(realm, roomId) roomSummaryEntity.name = ContentMapper.map(lastNameEvent?.content).toModel()?.name roomSummaryEntity.topic = ContentMapper.map(lastTopicEvent?.content).toModel()?.topic diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/util/Normalizer.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/util/Normalizer.kt index 2df1faa784..0e9c885394 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/util/Normalizer.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/util/Normalizer.kt @@ -1,11 +1,11 @@ /* - * Copyright (c) 2021 New Vector Ltd + * Copyright 2021 The Matrix.org Foundation C.I.C. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * 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, diff --git a/vector/src/main/java/im/vector/app/features/home/room/list/RoomListViewModel.kt b/vector/src/main/java/im/vector/app/features/home/room/list/RoomListViewModel.kt index 89f5aec8fb..b38f2565b6 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/list/RoomListViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/list/RoomListViewModel.kt @@ -191,7 +191,7 @@ class RoomListViewModel @AssistedInject constructor( } updatableQuery?.updateQuery { it.copy( - displayName = QueryStringValue.Contains(action.filter, QueryStringValue.Case.INSENSITIVE) + displayName = QueryStringValue.Contains(action.filter, QueryStringValue.Case.NORMALIZED) ) } } From 9b75da5d4d5fc910649bde82c42aa3f6d108e291 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Fri, 22 Oct 2021 17:39:06 +0200 Subject: [PATCH 05/20] Clean code --- .../im/vector/app/features/home/ShortcutsHandler.kt | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/vector/src/main/java/im/vector/app/features/home/ShortcutsHandler.kt b/vector/src/main/java/im/vector/app/features/home/ShortcutsHandler.kt index 7514d455aa..04bbd8f191 100644 --- a/vector/src/main/java/im/vector/app/features/home/ShortcutsHandler.kt +++ b/vector/src/main/java/im/vector/app/features/home/ShortcutsHandler.kt @@ -42,15 +42,16 @@ class ShortcutsHandler @Inject constructor( return Disposables.empty() } - return activeSessionHolder.getSafeActiveSession() - ?.getPagedRoomSummariesLive( + val session = activeSessionHolder.getSafeActiveSession() ?: return Disposables.empty() + return session + .getPagedRoomSummariesLive( roomSummaryQueryParams { memberships = listOf(Membership.JOIN) }, sortOrder = RoomSortOrder.PRIORITY_AND_ACTIVITY ) - ?.asObservable() - ?.subscribe { rooms -> + .asObservable() + .subscribe { rooms -> // Remove dead shortcuts (i.e. deleted rooms) val roomIds = rooms.map { it.roomId } val deadShortcutIds = ShortcutManagerCompat.getShortcuts(context, ShortcutManagerCompat.FLAG_MATCH_DYNAMIC) @@ -66,7 +67,6 @@ class ShortcutsHandler @Inject constructor( ShortcutManagerCompat.pushDynamicShortcut(context, shortcut) } } - ?: Disposables.empty() } fun clearShortcuts() { From 76314b9d87cd2a6951e9f55148873aa7867d548e Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Fri, 22 Oct 2021 18:01:40 +0200 Subject: [PATCH 06/20] Add `sortOrder: RoomSortOrder` parameter, with no API break --- .../sdk/api/session/room/RoomService.kt | 6 +++-- .../sdk/api/session/space/SpaceService.kt | 7 ++++-- .../session/room/DefaultRoomService.kt | 10 +++++---- .../room/summary/RoomSummaryDataSource.kt | 22 ++++++++++--------- .../session/space/DefaultSpaceService.kt | 11 ++++++---- 5 files changed, 34 insertions(+), 22 deletions(-) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/RoomService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/RoomService.kt index d80faa729c..e4bd498990 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/RoomService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/RoomService.kt @@ -94,13 +94,15 @@ interface RoomService { * Get a snapshot list of room summaries. * @return the immutable list of [RoomSummary] */ - fun getRoomSummaries(queryParams: RoomSummaryQueryParams): List + fun getRoomSummaries(queryParams: RoomSummaryQueryParams, + sortOrder: RoomSortOrder = RoomSortOrder.NONE): List /** * Get a live list of room summaries. This list is refreshed as soon as the data changes. * @return the [LiveData] of List[RoomSummary] */ - fun getRoomSummariesLive(queryParams: RoomSummaryQueryParams): LiveData> + fun getRoomSummariesLive(queryParams: RoomSummaryQueryParams, + sortOrder: RoomSortOrder = RoomSortOrder.ACTIVITY): LiveData> /** * Get a snapshot list of Breadcrumbs diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/space/SpaceService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/space/SpaceService.kt index f40572518f..357c0b941a 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/space/SpaceService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/space/SpaceService.kt @@ -19,6 +19,7 @@ package org.matrix.android.sdk.api.session.space import android.net.Uri import androidx.lifecycle.LiveData import org.matrix.android.sdk.api.session.events.model.Event +import org.matrix.android.sdk.api.session.room.RoomSortOrder import org.matrix.android.sdk.api.session.room.RoomSummaryQueryParams import org.matrix.android.sdk.api.session.room.model.RoomSummary import org.matrix.android.sdk.internal.session.space.peeking.SpacePeekResult @@ -74,9 +75,11 @@ interface SpaceService { * Get a live list of space summaries. This list is refreshed as soon as the data changes. * @return the [LiveData] of List[SpaceSummary] */ - fun getSpaceSummariesLive(queryParams: SpaceSummaryQueryParams): LiveData> + fun getSpaceSummariesLive(queryParams: SpaceSummaryQueryParams, + sortOrder: RoomSortOrder = RoomSortOrder.NONE): LiveData> - fun getSpaceSummaries(spaceSummaryQueryParams: SpaceSummaryQueryParams): List + fun getSpaceSummaries(spaceSummaryQueryParams: SpaceSummaryQueryParams, + sortOrder: RoomSortOrder = RoomSortOrder.NONE): List suspend fun joinSpace(spaceIdOrAlias: String, reason: String? = null, diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/DefaultRoomService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/DefaultRoomService.kt index 5b2499c130..7ca64aa66a 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/DefaultRoomService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/DefaultRoomService.kt @@ -85,12 +85,14 @@ internal class DefaultRoomService @Inject constructor( return roomSummaryDataSource.getRoomSummary(roomIdOrAlias) } - override fun getRoomSummaries(queryParams: RoomSummaryQueryParams): List { - return roomSummaryDataSource.getRoomSummaries(queryParams) + override fun getRoomSummaries(queryParams: RoomSummaryQueryParams, + sortOrder: RoomSortOrder): List { + return roomSummaryDataSource.getRoomSummaries(queryParams, sortOrder) } - override fun getRoomSummariesLive(queryParams: RoomSummaryQueryParams): LiveData> { - return roomSummaryDataSource.getRoomSummariesLive(queryParams) + override fun getRoomSummariesLive(queryParams: RoomSummaryQueryParams, + sortOrder: RoomSortOrder): LiveData> { + return roomSummaryDataSource.getRoomSummariesLive(queryParams, sortOrder) } override fun getPagedRoomSummariesLive(queryParams: RoomSummaryQueryParams, diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/summary/RoomSummaryDataSource.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/summary/RoomSummaryDataSource.kt index 0b8c6df806..dd14b32463 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/summary/RoomSummaryDataSource.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/summary/RoomSummaryDataSource.kt @@ -25,7 +25,6 @@ import androidx.paging.PagedList import com.zhuinden.monarchy.Monarchy import io.realm.Realm import io.realm.RealmQuery -import io.realm.Sort import io.realm.kotlin.where import org.matrix.android.sdk.api.query.ActiveSpaceFilter import org.matrix.android.sdk.api.query.RoomCategoryFilter @@ -80,25 +79,27 @@ internal class RoomSummaryDataSource @Inject constructor(@SessionDatabase privat } } - fun getRoomSummaries(queryParams: RoomSummaryQueryParams): List { + fun getRoomSummaries(queryParams: RoomSummaryQueryParams, + sortOrder: RoomSortOrder = RoomSortOrder.NONE): List { return monarchy.fetchAllMappedSync( - { roomSummariesQuery(it, queryParams) }, + { roomSummariesQuery(it, queryParams).process(sortOrder) }, { roomSummaryMapper.map(it) } ) } - fun getRoomSummariesLive(queryParams: RoomSummaryQueryParams): LiveData> { + fun getRoomSummariesLive(queryParams: RoomSummaryQueryParams, + sortOrder: RoomSortOrder = RoomSortOrder.NONE): LiveData> { return monarchy.findAllMappedWithChanges( { - roomSummariesQuery(it, queryParams) - .sort(RoomSummaryEntityFields.LAST_ACTIVITY_TIME, Sort.DESCENDING) + roomSummariesQuery(it, queryParams).process(sortOrder) }, { roomSummaryMapper.map(it) } ) } - fun getSpaceSummariesLive(queryParams: SpaceSummaryQueryParams): LiveData> { - return getRoomSummariesLive(queryParams) + fun getSpaceSummariesLive(queryParams: SpaceSummaryQueryParams, + sortOrder: RoomSortOrder = RoomSortOrder.NONE): LiveData> { + return getRoomSummariesLive(queryParams, sortOrder) } fun getSpaceSummary(roomIdOrAlias: String): RoomSummary? { @@ -122,8 +123,9 @@ internal class RoomSummaryDataSource @Inject constructor(@SessionDatabase privat } } - fun getSpaceSummaries(spaceSummaryQueryParams: SpaceSummaryQueryParams): List { - return getRoomSummaries(spaceSummaryQueryParams) + fun getSpaceSummaries(spaceSummaryQueryParams: SpaceSummaryQueryParams, + sortOrder: RoomSortOrder = RoomSortOrder.NONE): List { + return getRoomSummaries(spaceSummaryQueryParams, sortOrder) } fun getRootSpaceSummaries(): List { diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/space/DefaultSpaceService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/space/DefaultSpaceService.kt index ac20c79058..ebd5f2578e 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/space/DefaultSpaceService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/space/DefaultSpaceService.kt @@ -23,6 +23,7 @@ import org.matrix.android.sdk.api.session.events.model.Event import org.matrix.android.sdk.api.session.events.model.EventType import org.matrix.android.sdk.api.session.events.model.toContent import org.matrix.android.sdk.api.session.events.model.toModel +import org.matrix.android.sdk.api.session.room.RoomSortOrder 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 @@ -94,12 +95,14 @@ internal class DefaultSpaceService @Inject constructor( return spaceGetter.get(spaceId) } - override fun getSpaceSummariesLive(queryParams: SpaceSummaryQueryParams): LiveData> { - return roomSummaryDataSource.getSpaceSummariesLive(queryParams) + override fun getSpaceSummariesLive(queryParams: SpaceSummaryQueryParams, + sortOrder: RoomSortOrder): LiveData> { + return roomSummaryDataSource.getSpaceSummariesLive(queryParams, sortOrder) } - override fun getSpaceSummaries(spaceSummaryQueryParams: SpaceSummaryQueryParams): List { - return roomSummaryDataSource.getSpaceSummaries(spaceSummaryQueryParams) + override fun getSpaceSummaries(spaceSummaryQueryParams: SpaceSummaryQueryParams, + sortOrder: RoomSortOrder): List { + return roomSummaryDataSource.getSpaceSummaries(spaceSummaryQueryParams, sortOrder) } override fun getRootSpaceSummaries(): List { From f166348a68aa863a2d50f222057328e3fe43b82f Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Fri, 22 Oct 2021 18:10:32 +0200 Subject: [PATCH 07/20] Ensure ShortcutsHandler get all the joined rooms #4168 --- .../im/vector/app/features/home/ShortcutsHandler.kt | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/vector/src/main/java/im/vector/app/features/home/ShortcutsHandler.kt b/vector/src/main/java/im/vector/app/features/home/ShortcutsHandler.kt index 04bbd8f191..89e431cc59 100644 --- a/vector/src/main/java/im/vector/app/features/home/ShortcutsHandler.kt +++ b/vector/src/main/java/im/vector/app/features/home/ShortcutsHandler.kt @@ -43,13 +43,12 @@ class ShortcutsHandler @Inject constructor( } val session = activeSessionHolder.getSafeActiveSession() ?: return Disposables.empty() - return session - .getPagedRoomSummariesLive( - roomSummaryQueryParams { - memberships = listOf(Membership.JOIN) - }, - sortOrder = RoomSortOrder.PRIORITY_AND_ACTIVITY - ) + return session.getRoomSummariesLive( + roomSummaryQueryParams { + memberships = listOf(Membership.JOIN) + }, + sortOrder = RoomSortOrder.PRIORITY_AND_ACTIVITY + ) .asObservable() .subscribe { rooms -> // Remove dead shortcuts (i.e. deleted rooms) From 3a81c1006212da896ee9f8f890e2493c4ffccd37 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Wed, 27 Oct 2021 15:13:07 +0200 Subject: [PATCH 08/20] Remove (disable) shortcut if a room is left --- .../app/features/home/ShortcutsHandler.kt | 44 ++++++++++++++----- 1 file changed, 32 insertions(+), 12 deletions(-) diff --git a/vector/src/main/java/im/vector/app/features/home/ShortcutsHandler.kt b/vector/src/main/java/im/vector/app/features/home/ShortcutsHandler.kt index 89e431cc59..d0f805c75a 100644 --- a/vector/src/main/java/im/vector/app/features/home/ShortcutsHandler.kt +++ b/vector/src/main/java/im/vector/app/features/home/ShortcutsHandler.kt @@ -26,8 +26,10 @@ import io.reactivex.disposables.Disposable import io.reactivex.disposables.Disposables import org.matrix.android.sdk.api.session.room.RoomSortOrder import org.matrix.android.sdk.api.session.room.model.Membership +import org.matrix.android.sdk.api.session.room.model.RoomSummary import org.matrix.android.sdk.api.session.room.roomSummaryQueryParams import org.matrix.android.sdk.rx.asObservable +import timber.log.Timber import javax.inject.Inject class ShortcutsHandler @Inject constructor( @@ -52,22 +54,40 @@ class ShortcutsHandler @Inject constructor( .asObservable() .subscribe { rooms -> // Remove dead shortcuts (i.e. deleted rooms) - val roomIds = rooms.map { it.roomId } - val deadShortcutIds = ShortcutManagerCompat.getShortcuts(context, ShortcutManagerCompat.FLAG_MATCH_DYNAMIC) - .map { it.id } - .filter { !roomIds.contains(it) } - ShortcutManagerCompat.removeLongLivedShortcuts(context, deadShortcutIds) + removeDeadShortcut(rooms.map { it.roomId }) - val shortcuts = rooms.mapIndexed { index, room -> - shortcutCreator.create(room, index) - } - - shortcuts.forEach { shortcut -> - ShortcutManagerCompat.pushDynamicShortcut(context, shortcut) - } + // Create shortcuts + createShortcuts(rooms) } } + + private fun removeDeadShortcut(roomIds: List) { + val deadShortcutIds = ShortcutManagerCompat.getShortcuts(context, ShortcutManagerCompat.FLAG_MATCH_DYNAMIC) + .map { it.id } + .filter { !roomIds.contains(it) } + + if (deadShortcutIds.isNotEmpty()) { + Timber.d("Removing shortcut(s) $deadShortcutIds") + ShortcutManagerCompat.removeLongLivedShortcuts(context, deadShortcutIds) + if (ShortcutManagerCompat.isRequestPinShortcutSupported(context)) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N_MR1) { + context.getSystemService()?.disableShortcuts(deadShortcutIds) + } + } + } + } + + private fun createShortcuts(rooms: List) { + val shortcuts = rooms.mapIndexed { index, room -> + shortcutCreator.create(room, index) + } + + shortcuts.forEach { shortcut -> + ShortcutManagerCompat.pushDynamicShortcut(context, shortcut) + } + } + fun clearShortcuts() { if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N_MR1) { // No op From 6f577d823281ee7e4edc9ab28df69727cc6e53bf Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Wed, 27 Oct 2021 15:24:24 +0200 Subject: [PATCH 09/20] Do not show shortcuts if a PIN code is set --- changelog.d/4170.bugfix | 1 + .../app/features/home/ShortcutsHandler.kt | 20 ++++++++++++------- 2 files changed, 14 insertions(+), 7 deletions(-) create mode 100644 changelog.d/4170.bugfix diff --git a/changelog.d/4170.bugfix b/changelog.d/4170.bugfix new file mode 100644 index 0000000000..3c1cc4361f --- /dev/null +++ b/changelog.d/4170.bugfix @@ -0,0 +1 @@ +Do not show shortcuts if a PIN code is set \ No newline at end of file diff --git a/vector/src/main/java/im/vector/app/features/home/ShortcutsHandler.kt b/vector/src/main/java/im/vector/app/features/home/ShortcutsHandler.kt index d0f805c75a..005ca30845 100644 --- a/vector/src/main/java/im/vector/app/features/home/ShortcutsHandler.kt +++ b/vector/src/main/java/im/vector/app/features/home/ShortcutsHandler.kt @@ -22,6 +22,7 @@ import android.os.Build import androidx.core.content.getSystemService import androidx.core.content.pm.ShortcutManagerCompat import im.vector.app.core.di.ActiveSessionHolder +import im.vector.app.features.pin.PinCodeStore import io.reactivex.disposables.Disposable import io.reactivex.disposables.Disposables import org.matrix.android.sdk.api.session.room.RoomSortOrder @@ -35,7 +36,8 @@ import javax.inject.Inject class ShortcutsHandler @Inject constructor( private val context: Context, private val shortcutCreator: ShortcutCreator, - private val activeSessionHolder: ActiveSessionHolder + private val activeSessionHolder: ActiveSessionHolder, + private val pinCodeStore: PinCodeStore ) { fun observeRoomsAndBuildShortcuts(): Disposable { @@ -61,7 +63,6 @@ class ShortcutsHandler @Inject constructor( } } - private fun removeDeadShortcut(roomIds: List) { val deadShortcutIds = ShortcutManagerCompat.getShortcuts(context, ShortcutManagerCompat.FLAG_MATCH_DYNAMIC) .map { it.id } @@ -79,12 +80,17 @@ class ShortcutsHandler @Inject constructor( } private fun createShortcuts(rooms: List) { - val shortcuts = rooms.mapIndexed { index, room -> - shortcutCreator.create(room, index) - } + if (pinCodeStore.getEncodedPin() != null) { + // No shortcut in this case (privacy) + ShortcutManagerCompat.removeAllDynamicShortcuts(context) + } else { + val shortcuts = rooms.mapIndexed { index, room -> + shortcutCreator.create(room, index) + } - shortcuts.forEach { shortcut -> - ShortcutManagerCompat.pushDynamicShortcut(context, shortcut) + shortcuts.forEach { shortcut -> + ShortcutManagerCompat.pushDynamicShortcut(context, shortcut) + } } } From 3a48e33c812662ef188ee7872b86f134c5c59630 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Wed, 27 Oct 2021 15:26:31 +0200 Subject: [PATCH 10/20] Cache immutable value --- .../java/im/vector/app/features/home/ShortcutsHandler.kt | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/vector/src/main/java/im/vector/app/features/home/ShortcutsHandler.kt b/vector/src/main/java/im/vector/app/features/home/ShortcutsHandler.kt index 005ca30845..d7e1122246 100644 --- a/vector/src/main/java/im/vector/app/features/home/ShortcutsHandler.kt +++ b/vector/src/main/java/im/vector/app/features/home/ShortcutsHandler.kt @@ -39,6 +39,7 @@ class ShortcutsHandler @Inject constructor( private val activeSessionHolder: ActiveSessionHolder, private val pinCodeStore: PinCodeStore ) { + private val isRequestPinShortcutSupported = ShortcutManagerCompat.isRequestPinShortcutSupported(context) fun observeRoomsAndBuildShortcuts(): Disposable { if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N_MR1) { @@ -71,7 +72,7 @@ class ShortcutsHandler @Inject constructor( if (deadShortcutIds.isNotEmpty()) { Timber.d("Removing shortcut(s) $deadShortcutIds") ShortcutManagerCompat.removeLongLivedShortcuts(context, deadShortcutIds) - if (ShortcutManagerCompat.isRequestPinShortcutSupported(context)) { + if (isRequestPinShortcutSupported) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N_MR1) { context.getSystemService()?.disableShortcuts(deadShortcutIds) } @@ -107,7 +108,7 @@ class ShortcutsHandler @Inject constructor( ShortcutManagerCompat.removeLongLivedShortcuts(context, shortcuts) // We can only disabled pinned shortcuts with the API, but at least it will prevent the crash - if (ShortcutManagerCompat.isRequestPinShortcutSupported(context)) { + if (isRequestPinShortcutSupported) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N_MR1) { context.getSystemService() ?.let { From dbb4a87784eb1df3f380cc9167eabb091095d50e Mon Sep 17 00:00:00 2001 From: Adam Brown Date: Wed, 27 Oct 2021 14:26:51 +0100 Subject: [PATCH 11/20] making the isNormalized function an extension and internal to the sdk --- .../java/org/matrix/android/sdk/api/query/QueryStringValue.kt | 4 ++-- .../internal/session/room/summary/RoomSummaryDataSource.kt | 1 + 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/query/QueryStringValue.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/query/QueryStringValue.kt index da32d745d6..2461492ba7 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/query/QueryStringValue.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/query/QueryStringValue.kt @@ -39,6 +39,6 @@ sealed interface QueryStringValue { INSENSITIVE, NORMALIZED } - - fun isNormalized() = this is ContentQueryStringValue && case == Case.NORMALIZED } + +internal fun QueryStringValue.isNormalized() = this is QueryStringValue.ContentQueryStringValue && case == QueryStringValue.Case.NORMALIZED diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/summary/RoomSummaryDataSource.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/summary/RoomSummaryDataSource.kt index e86bd27ed2..f9f78b9181 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/summary/RoomSummaryDataSource.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/summary/RoomSummaryDataSource.kt @@ -29,6 +29,7 @@ import io.realm.Sort import io.realm.kotlin.where import org.matrix.android.sdk.api.query.ActiveSpaceFilter import org.matrix.android.sdk.api.query.RoomCategoryFilter +import org.matrix.android.sdk.api.query.isNormalized import org.matrix.android.sdk.api.session.room.ResultBoundaries import org.matrix.android.sdk.api.session.room.RoomSortOrder import org.matrix.android.sdk.api.session.room.RoomSummaryQueryParams From e7a0a4d4aeb32aecb044fd79b5b6d3ee75b5408b Mon Sep 17 00:00:00 2001 From: Adam Brown Date: Wed, 27 Oct 2021 14:32:51 +0100 Subject: [PATCH 12/20] documenting the different query cases --- .../android/sdk/api/query/QueryStringValue.kt | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/query/QueryStringValue.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/query/QueryStringValue.kt index 2461492ba7..31ec131c5c 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/query/QueryStringValue.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/query/QueryStringValue.kt @@ -35,8 +35,23 @@ sealed interface QueryStringValue { data class Contains(override val string: String, override val case: Case = Case.SENSITIVE) : ContentQueryStringValue enum class Case { + /** + * Match query sensitive to case + */ SENSITIVE, + + /** + * Match query insensitive to case, this only works for Latin-1 character sets + */ INSENSITIVE, + + /** + * Match query with input normalized (case insensitive) + * Works around Realms inability to sort or filter by case for non Latin-1 character sets + * Expects the target field to contain normalized data + * + * @see org.matrix.android.sdk.internal.util.Normalizer.normalize + */ NORMALIZED } } From 7b356484ae6e21d4efa236d397894de20d989782 Mon Sep 17 00:00:00 2001 From: Adam Brown Date: Wed, 27 Oct 2021 14:34:40 +0100 Subject: [PATCH 13/20] removing noisy log which duplicates a type clause and fixing when casing formatting to have a case per line --- .../internal/query/QueryStringValueProcessor.kt | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/query/QueryStringValueProcessor.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/query/QueryStringValueProcessor.kt index 33564eb610..b42bf2b8c7 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/query/QueryStringValueProcessor.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/query/QueryStringValueProcessor.kt @@ -22,7 +22,6 @@ import io.realm.RealmQuery import org.matrix.android.sdk.api.query.QueryStringValue import org.matrix.android.sdk.api.query.QueryStringValue.ContentQueryStringValue import org.matrix.android.sdk.internal.util.Normalizer -import timber.log.Timber import javax.inject.Inject class QueryStringValueProcessor @Inject constructor( @@ -31,10 +30,7 @@ class QueryStringValueProcessor @Inject constructor( fun RealmQuery.process(field: String, queryStringValue: QueryStringValue): RealmQuery { return when (queryStringValue) { - is QueryStringValue.NoCondition -> { - Timber.v("No condition to process") - this - } + is QueryStringValue.NoCondition -> this is QueryStringValue.IsNotNull -> isNotNull(field) is QueryStringValue.IsNull -> isNull(field) is QueryStringValue.IsEmpty -> isEmpty(field) @@ -48,15 +44,17 @@ class QueryStringValueProcessor @Inject constructor( private fun ContentQueryStringValue.toRealmValue(): String { return when (case) { - QueryStringValue.Case.NORMALIZED -> normalizer.normalize(string) - QueryStringValue.Case.SENSITIVE, QueryStringValue.Case.INSENSITIVE -> string + QueryStringValue.Case.NORMALIZED -> normalizer.normalize(string) + QueryStringValue.Case.SENSITIVE, + QueryStringValue.Case.INSENSITIVE -> string } } } private fun QueryStringValue.Case.toRealmCase(): Case { return when (this) { - QueryStringValue.Case.INSENSITIVE -> Case.INSENSITIVE - QueryStringValue.Case.SENSITIVE, QueryStringValue.Case.NORMALIZED -> Case.SENSITIVE + QueryStringValue.Case.INSENSITIVE -> Case.INSENSITIVE + QueryStringValue.Case.SENSITIVE, + QueryStringValue.Case.NORMALIZED -> Case.SENSITIVE } } From 540036f83cb0aaa54b14f74a608cca4a6ffc4181 Mon Sep 17 00:00:00 2001 From: Adam Brown Date: Wed, 27 Oct 2021 14:36:26 +0100 Subject: [PATCH 14/20] removing extra query definition by chaining the query creation with modifiers --- .../room/summary/RoomSummaryDataSource.kt | 22 ++++++++++--------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/summary/RoomSummaryDataSource.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/summary/RoomSummaryDataSource.kt index f9f78b9181..9fdc27f224 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/summary/RoomSummaryDataSource.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/summary/RoomSummaryDataSource.kt @@ -244,16 +244,18 @@ internal class RoomSummaryDataSource @Inject constructor( private fun roomSummariesQuery(realm: Realm, queryParams: RoomSummaryQueryParams): RealmQuery { val query = with(queryStringValueProcessor) { - val query = RoomSummaryEntity.where(realm) - query.process(RoomSummaryEntityFields.ROOM_ID, queryParams.roomId) - if (queryParams.displayName.isNormalized()) { - query.process(RoomSummaryEntityFields.NORMALIZED_DISPLAY_NAME, queryParams.displayName) - } else { - query.process(RoomSummaryEntityFields.DISPLAY_NAME, queryParams.displayName) - } - query.process(RoomSummaryEntityFields.CANONICAL_ALIAS, queryParams.canonicalAlias) - query.process(RoomSummaryEntityFields.MEMBERSHIP_STR, queryParams.memberships) - query.equalTo(RoomSummaryEntityFields.IS_HIDDEN_FROM_USER, false) + RoomSummaryEntity.where(realm) + .process(RoomSummaryEntityFields.ROOM_ID, queryParams.roomId) + .let { + if (queryParams.displayName.isNormalized()) { + it.process(RoomSummaryEntityFields.NORMALIZED_DISPLAY_NAME, queryParams.displayName) + } else { + it.process(RoomSummaryEntityFields.DISPLAY_NAME, queryParams.displayName) + } + } + .process(RoomSummaryEntityFields.CANONICAL_ALIAS, queryParams.canonicalAlias) + .process(RoomSummaryEntityFields.MEMBERSHIP_STR, queryParams.memberships) + .equalTo(RoomSummaryEntityFields.IS_HIDDEN_FROM_USER, false) } queryParams.roomCategoryFilter?.let { From 9949779b62acd7f5ab5f176dc581b65b842588cd Mon Sep 17 00:00:00 2001 From: Adam Brown Date: Wed, 27 Oct 2021 15:01:26 +0100 Subject: [PATCH 15/20] ensuring the store migration class is always equal to other store migration instances - is needed as realm will throw if multiple migration instances are created and they don't match --- .../database/RealmSessionStoreMigration.kt | 7 +++++ .../RealmSessionStoreMigrationTest.kt | 29 +++++++++++++++++++ 2 files changed, 36 insertions(+) create mode 100644 matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/database/RealmSessionStoreMigrationTest.kt diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/RealmSessionStoreMigration.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/RealmSessionStoreMigration.kt index d6e86fd22b..2256d93100 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 @@ -57,6 +57,13 @@ internal class RealmSessionStoreMigration @Inject constructor( const val SESSION_STORE_SCHEMA_VERSION = 19L } + /** + * Forces all RealmSessionStoreMigration instances to be equal + * Avoids Realm throwing when multiple instances of the migration are set + */ + override fun equals(other: Any?) = other is RealmSessionStoreMigration + override fun hashCode() = 1000 + override fun migrate(realm: DynamicRealm, oldVersion: Long, newVersion: Long) { Timber.v("Migrating Realm Session from $oldVersion to $newVersion") diff --git a/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/database/RealmSessionStoreMigrationTest.kt b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/database/RealmSessionStoreMigrationTest.kt new file mode 100644 index 0000000000..25db609db7 --- /dev/null +++ b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/database/RealmSessionStoreMigrationTest.kt @@ -0,0 +1,29 @@ +/* + * Copyright (c) 2021 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.matrix.android.sdk.internal.database + +import io.mockk.mockk +import org.amshove.kluent.shouldBeEqualTo +import org.junit.Test + +class RealmSessionStoreMigrationTest { + + @Test + fun `when creating multiple migration instances then they are equal`() { + RealmSessionStoreMigration(normalizer = mockk()) shouldBeEqualTo RealmSessionStoreMigration(normalizer = mockk()) + } +} From 6691edb59da9b90e149fe78c601b8e7e47ddb4fa Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Wed, 27 Oct 2021 16:17:53 +0200 Subject: [PATCH 16/20] Remove shortcut as soon as a PIN code is set --- .../app/features/home/ShortcutsHandler.kt | 24 ++++++++- .../vector/app/features/pin/PinCodeStore.kt | 49 ++++++++++++++----- 2 files changed, 58 insertions(+), 15 deletions(-) diff --git a/vector/src/main/java/im/vector/app/features/home/ShortcutsHandler.kt b/vector/src/main/java/im/vector/app/features/home/ShortcutsHandler.kt index d7e1122246..f27cd30494 100644 --- a/vector/src/main/java/im/vector/app/features/home/ShortcutsHandler.kt +++ b/vector/src/main/java/im/vector/app/features/home/ShortcutsHandler.kt @@ -23,6 +23,7 @@ import androidx.core.content.getSystemService import androidx.core.content.pm.ShortcutManagerCompat import im.vector.app.core.di.ActiveSessionHolder import im.vector.app.features.pin.PinCodeStore +import im.vector.app.features.pin.PinCodeStoreListener import io.reactivex.disposables.Disposable import io.reactivex.disposables.Disposables import org.matrix.android.sdk.api.session.room.RoomSortOrder @@ -38,16 +39,22 @@ class ShortcutsHandler @Inject constructor( private val shortcutCreator: ShortcutCreator, private val activeSessionHolder: ActiveSessionHolder, private val pinCodeStore: PinCodeStore -) { +) : PinCodeStoreListener { private val isRequestPinShortcutSupported = ShortcutManagerCompat.isRequestPinShortcutSupported(context) + // Value will be set correctly if necessary + private var hasPinCode = true + fun observeRoomsAndBuildShortcuts(): Disposable { if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N_MR1) { // No op return Disposables.empty() } + hasPinCode = pinCodeStore.getEncodedPin() != null + val session = activeSessionHolder.getSafeActiveSession() ?: return Disposables.empty() + pinCodeStore.addListener(this) return session.getRoomSummariesLive( roomSummaryQueryParams { memberships = listOf(Membership.JOIN) @@ -55,6 +62,9 @@ class ShortcutsHandler @Inject constructor( sortOrder = RoomSortOrder.PRIORITY_AND_ACTIVITY ) .asObservable() + .doOnDispose { + pinCodeStore.removeListener(this) + } .subscribe { rooms -> // Remove dead shortcuts (i.e. deleted rooms) removeDeadShortcut(rooms.map { it.roomId }) @@ -81,7 +91,7 @@ class ShortcutsHandler @Inject constructor( } private fun createShortcuts(rooms: List) { - if (pinCodeStore.getEncodedPin() != null) { + if (hasPinCode) { // No shortcut in this case (privacy) ShortcutManagerCompat.removeAllDynamicShortcuts(context) } else { @@ -117,4 +127,14 @@ class ShortcutsHandler @Inject constructor( } } } + + override fun onPinSetUpChange(isConfigured: Boolean) { + hasPinCode = isConfigured + if (isConfigured) { + // Remove shortcuts immediately + ShortcutManagerCompat.removeAllDynamicShortcuts(context) + } + // Else shortcut will be created next time any room summary is updated, or + // next time the app is started which is acceptable + } } diff --git a/vector/src/main/java/im/vector/app/features/pin/PinCodeStore.kt b/vector/src/main/java/im/vector/app/features/pin/PinCodeStore.kt index fb7c6897e2..d3632edbca 100644 --- a/vector/src/main/java/im/vector/app/features/pin/PinCodeStore.kt +++ b/vector/src/main/java/im/vector/app/features/pin/PinCodeStore.kt @@ -25,6 +25,7 @@ import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.withContext import org.matrix.android.sdk.api.extensions.orFalse import javax.inject.Inject +import javax.inject.Singleton import kotlin.coroutines.resume import kotlin.coroutines.suspendCoroutine @@ -56,26 +57,40 @@ interface PinCodeStore { * Will reset the counters */ fun resetCounters() + + fun addListener(listener: PinCodeStoreListener) + fun removeListener(listener: PinCodeStoreListener) } -class SharedPrefPinCodeStore @Inject constructor(private val sharedPreferences: SharedPreferences) : PinCodeStore { +interface PinCodeStoreListener { + fun onPinSetUpChange(isConfigured: Boolean) +} - override suspend fun storeEncodedPin(encodePin: String) = withContext(Dispatchers.IO) { - sharedPreferences.edit { - putString(ENCODED_PIN_CODE_KEY, encodePin) +@Singleton +class SharedPrefPinCodeStore @Inject constructor(private val sharedPreferences: SharedPreferences) : PinCodeStore { + private val listeners = mutableSetOf() + + override suspend fun storeEncodedPin(encodePin: String) { + withContext(Dispatchers.IO) { + sharedPreferences.edit { + putString(ENCODED_PIN_CODE_KEY, encodePin) + } } + listeners.forEach { it.onPinSetUpChange(isConfigured = true) } } - override suspend fun deleteEncodedPin() = withContext(Dispatchers.IO) { - // Also reset the counters - resetCounters() - sharedPreferences.edit { - remove(ENCODED_PIN_CODE_KEY) + override suspend fun deleteEncodedPin() { + withContext(Dispatchers.IO) { + // Also reset the counters + resetCounters() + sharedPreferences.edit { + remove(ENCODED_PIN_CODE_KEY) + } + awaitPinCodeCallback { + PFSecurityManager.getInstance().pinCodeHelper.delete(it) + } } - awaitPinCodeCallback { - PFSecurityManager.getInstance().pinCodeHelper.delete(it) - } - return@withContext + listeners.forEach { it.onPinSetUpChange(isConfigured = false) } } override fun getEncodedPin(): String? { @@ -124,6 +139,14 @@ class SharedPrefPinCodeStore @Inject constructor(private val sharedPreferences: } } + override fun addListener(listener: PinCodeStoreListener) { + listeners.add(listener) + } + + override fun removeListener(listener: PinCodeStoreListener) { + listeners.remove(listener) + } + private suspend inline fun awaitPinCodeCallback(crossinline callback: (PFPinCodeHelperCallback) -> Unit) = suspendCoroutine> { cont -> callback(PFPinCodeHelperCallback { result -> cont.resume(result) }) } From 611bf29ebe26cb8b340ba9a7be5bab855bea7e97 Mon Sep 17 00:00:00 2001 From: Adam Brown Date: Wed, 27 Oct 2021 15:25:05 +0100 Subject: [PATCH 17/20] extending the room name resolved to create a dedicated room name data class which contains a normalized version of the room name --- .../database/mapper/RoomSummaryMapper.kt | 2 +- .../database/model/RoomSummaryEntity.kt | 18 +++++++++++------- .../room/membership/RoomDisplayNameResolver.kt | 14 ++++++++++---- .../session/room/summary/RoomSummaryUpdater.kt | 4 +--- .../sync/handler/UserAccountDataSyncHandler.kt | 4 ++-- 5 files changed, 25 insertions(+), 17 deletions(-) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/mapper/RoomSummaryMapper.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/mapper/RoomSummaryMapper.kt index 6c81550012..3a15e0acf0 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/mapper/RoomSummaryMapper.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/mapper/RoomSummaryMapper.kt @@ -42,7 +42,7 @@ internal class RoomSummaryMapper @Inject constructor(private val timelineEventMa return RoomSummary( roomId = roomSummaryEntity.roomId, - displayName = roomSummaryEntity.displayName ?: "", + displayName = roomSummaryEntity.displayName() ?: "", name = roomSummaryEntity.name ?: "", topic = roomSummaryEntity.topic ?: "", avatarUrl = roomSummaryEntity.avatarUrl ?: "", diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/RoomSummaryEntity.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/RoomSummaryEntity.kt index 23df6d6234..67672f03ad 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/RoomSummaryEntity.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/RoomSummaryEntity.kt @@ -28,6 +28,7 @@ import org.matrix.android.sdk.api.session.room.model.RoomSummary import org.matrix.android.sdk.api.session.room.model.VersioningState import org.matrix.android.sdk.api.session.room.model.tag.RoomTag import org.matrix.android.sdk.internal.database.model.presence.UserPresenceEntity +import org.matrix.android.sdk.internal.session.room.membership.RoomName internal open class RoomSummaryEntity( @PrimaryKey var roomId: String = "", @@ -36,20 +37,23 @@ internal open class RoomSummaryEntity( var children: RealmList = RealmList() ) : RealmObject() { - var displayName: String? = "" - set(value) { - if (value != field) field = value + private var displayName: String? = "" + + fun displayName() = displayName + + fun setDisplayName(roomName: RoomName) { + if (roomName.name != displayName) { + displayName = roomName.name + normalizedDisplayName = roomName.normalizedName } + } /** * Workaround for Realm only supporting Latin-1 character sets when sorting * or filtering by case * See https://github.com/realm/realm-core/issues/777 */ - var normalizedDisplayName: String? = "" - set(value) { - if (value != field) field = value - } + private var normalizedDisplayName: String? = "" var avatarUrl: String? = "" set(value) { diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/membership/RoomDisplayNameResolver.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/membership/RoomDisplayNameResolver.kt index 5e77dd157a..bd9f2ecc36 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/membership/RoomDisplayNameResolver.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/membership/RoomDisplayNameResolver.kt @@ -34,6 +34,7 @@ import org.matrix.android.sdk.internal.database.query.getOrNull import org.matrix.android.sdk.internal.database.query.where import org.matrix.android.sdk.internal.di.UserId import org.matrix.android.sdk.internal.session.displayname.DisplayNameResolver +import org.matrix.android.sdk.internal.util.Normalizer import javax.inject.Inject /** @@ -42,6 +43,7 @@ import javax.inject.Inject internal class RoomDisplayNameResolver @Inject constructor( matrixConfiguration: MatrixConfiguration, private val displayNameResolver: DisplayNameResolver, + private val normalizer: Normalizer, @UserId private val userId: String ) { @@ -54,7 +56,7 @@ internal class RoomDisplayNameResolver @Inject constructor( * @param roomId: the roomId to resolve the name of. * @return the room display name */ - fun resolve(realm: Realm, roomId: String): String { + fun resolve(realm: Realm, roomId: String): RoomName { // this algorithm is the one defined in // https://github.com/matrix-org/matrix-js-sdk/blob/develop/lib/models/room.js#L617 // calculateRoomName(room, userId) @@ -66,12 +68,12 @@ internal class RoomDisplayNameResolver @Inject constructor( val roomName = CurrentStateEventEntity.getOrNull(realm, roomId, type = EventType.STATE_ROOM_NAME, stateKey = "")?.root name = ContentMapper.map(roomName?.content).toModel()?.name if (!name.isNullOrEmpty()) { - return name + return name.toRoomName() } val canonicalAlias = CurrentStateEventEntity.getOrNull(realm, roomId, type = EventType.STATE_ROOM_CANONICAL_ALIAS, stateKey = "")?.root name = ContentMapper.map(canonicalAlias?.content).toModel()?.canonicalAlias if (!name.isNullOrEmpty()) { - return name + return name.toRoomName() } val roomMembers = RoomMemberHelper(realm, roomId) @@ -152,7 +154,7 @@ internal class RoomDisplayNameResolver @Inject constructor( } } } - return name ?: roomId + return (name ?: roomId).toRoomName() } /** See [org.matrix.android.sdk.api.session.room.sender.SenderInfo.disambiguatedDisplayName] */ @@ -165,4 +167,8 @@ internal class RoomDisplayNameResolver @Inject constructor( "${roomMemberSummary.displayName} (${roomMemberSummary.userId})" } } + + private fun String.toRoomName() = RoomName(this, normalizedName = normalizer.normalize(this)) } + +internal data class RoomName(val name: String, val normalizedName: String) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/summary/RoomSummaryUpdater.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/summary/RoomSummaryUpdater.kt index 03640d0555..3556cabb33 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/summary/RoomSummaryUpdater.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/summary/RoomSummaryUpdater.kt @@ -138,9 +138,7 @@ internal class RoomSummaryUpdater @Inject constructor( // avoid this call if we are sure there are unread events !isEventRead(realm.configuration, userId, roomId, latestPreviewableEvent?.eventId) - val roomDisplayName = roomDisplayNameResolver.resolve(realm, roomId) - roomSummaryEntity.displayName = roomDisplayName - roomSummaryEntity.normalizedDisplayName = normalizer.normalize(roomDisplayName) + roomSummaryEntity.setDisplayName(roomDisplayNameResolver.resolve(realm, roomId)) roomSummaryEntity.avatarUrl = roomAvatarResolver.resolve(realm, roomId) roomSummaryEntity.name = ContentMapper.map(lastNameEvent?.content).toModel()?.name roomSummaryEntity.topic = ContentMapper.map(lastTopicEvent?.content).toModel()?.topic diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/handler/UserAccountDataSyncHandler.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/handler/UserAccountDataSyncHandler.kt index 3e38cd7839..7f80486c70 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/handler/UserAccountDataSyncHandler.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/handler/UserAccountDataSyncHandler.kt @@ -166,7 +166,7 @@ internal class UserAccountDataSyncHandler @Inject constructor( roomSummaryEntity.directUserId = userId // Also update the avatar and displayname, there is a specific treatment for DMs roomSummaryEntity.avatarUrl = roomAvatarResolver.resolve(realm, roomId) - roomSummaryEntity.displayName = roomDisplayNameResolver.resolve(realm, roomId) + roomSummaryEntity.setDisplayName(roomDisplayNameResolver.resolve(realm, roomId)) } } } @@ -178,7 +178,7 @@ internal class UserAccountDataSyncHandler @Inject constructor( it.directUserId = null // Also update the avatar and displayname, there was a specific treatment for DMs it.avatarUrl = roomAvatarResolver.resolve(realm, it.roomId) - it.displayName = roomDisplayNameResolver.resolve(realm, it.roomId) + it.setDisplayName(roomDisplayNameResolver.resolve(realm, it.roomId)) } } From 63e9e07d5ee82170e5d62ce55818870ae51b540d Mon Sep 17 00:00:00 2001 From: Adam Brown Date: Wed, 27 Oct 2021 16:42:35 +0100 Subject: [PATCH 18/20] using correct license for matrix-sdk test --- .../sdk/internal/database/RealmSessionStoreMigrationTest.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/database/RealmSessionStoreMigrationTest.kt b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/database/RealmSessionStoreMigrationTest.kt index 25db609db7..3a0574e95a 100644 --- a/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/database/RealmSessionStoreMigrationTest.kt +++ b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/database/RealmSessionStoreMigrationTest.kt @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021 New Vector Ltd + * Copyright (c) 2021 The Matrix.org Foundation C.I.C. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. From d344be508852082ed2543a05a9e3efe691f61750 Mon Sep 17 00:00:00 2001 From: Adam Brown Date: Wed, 27 Oct 2021 16:44:39 +0100 Subject: [PATCH 19/20] adding changelog entry --- changelog.d/3968.bugfix | 1 + 1 file changed, 1 insertion(+) create mode 100644 changelog.d/3968.bugfix diff --git a/changelog.d/3968.bugfix b/changelog.d/3968.bugfix new file mode 100644 index 0000000000..dec0eaf2df --- /dev/null +++ b/changelog.d/3968.bugfix @@ -0,0 +1 @@ +Fixing room search needing exact casing for non latin-1 character named rooms \ No newline at end of file From 34e8cf84dc7427fe18e22a9d9ab211e2c7bb7b41 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Thu, 28 Oct 2021 09:41:27 +0200 Subject: [PATCH 20/20] Improve Rx sequence regarding listener --- .../java/im/vector/app/features/home/ShortcutsHandler.kt | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/vector/src/main/java/im/vector/app/features/home/ShortcutsHandler.kt b/vector/src/main/java/im/vector/app/features/home/ShortcutsHandler.kt index f27cd30494..612e2dcf87 100644 --- a/vector/src/main/java/im/vector/app/features/home/ShortcutsHandler.kt +++ b/vector/src/main/java/im/vector/app/features/home/ShortcutsHandler.kt @@ -54,7 +54,6 @@ class ShortcutsHandler @Inject constructor( hasPinCode = pinCodeStore.getEncodedPin() != null val session = activeSessionHolder.getSafeActiveSession() ?: return Disposables.empty() - pinCodeStore.addListener(this) return session.getRoomSummariesLive( roomSummaryQueryParams { memberships = listOf(Membership.JOIN) @@ -62,9 +61,8 @@ class ShortcutsHandler @Inject constructor( sortOrder = RoomSortOrder.PRIORITY_AND_ACTIVITY ) .asObservable() - .doOnDispose { - pinCodeStore.removeListener(this) - } + .doOnSubscribe { pinCodeStore.addListener(this) } + .doFinally { pinCodeStore.removeListener(this) } .subscribe { rooms -> // Remove dead shortcuts (i.e. deleted rooms) removeDeadShortcut(rooms.map { it.roomId })