diff --git a/dependencies.gradle b/dependencies.gradle
index 7fa42a666f0bb8fccbd69efe145c73cf14d5083d..47090d4732fc1f10b10c9235a1865938ab3c90c6 100644
--- a/dependencies.gradle
+++ b/dependencies.gradle
@@ -11,7 +11,7 @@ def gradle = "7.0.3"
 // Ref: https://kotlinlang.org/releases.html
 def kotlin = "1.5.31"
 def kotlinCoroutines = "1.5.2"
-def dagger = "2.39.1"
+def dagger = "2.40"
 def retrofit = "2.9.0"
 def arrow = "0.8.2"
 def markwon = "4.6.2"
@@ -34,7 +34,9 @@ def androidxTest = "1.4.0"
 ext.libs = [
         gradle      : [
                 'gradlePlugin'            : "com.android.tools.build:gradle:$gradle",
-                'kotlinPlugin'            : "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin"
+                'kotlinPlugin'            : "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin",
+                'hiltPlugin'              : "com.google.dagger:hilt-android-gradle-plugin:$dagger"
+
         ],
         jetbrains   : [
                 'coroutinesCore'          : "org.jetbrains.kotlinx:kotlinx-coroutines-core:$kotlinCoroutines",
@@ -72,7 +74,9 @@ ext.libs = [
         ],
         dagger      : [
                 'dagger'                  : "com.google.dagger:dagger:$dagger",
-                'daggerCompiler'          : "com.google.dagger:dagger-compiler:$dagger"
+                'daggerCompiler'          : "com.google.dagger:dagger-compiler:$dagger",
+                'hilt'                    : "com.google.dagger:hilt-android:$dagger",
+                'hiltCompiler'            : "com.google.dagger:hilt-compiler:$dagger"
         ],
         squareup    : [
                 'moshi'                  : "com.squareup.moshi:moshi-adapters:$moshi",
diff --git a/matrix-sdk-android/build.gradle b/matrix-sdk-android/build.gradle
index 12b37b00b23d826819ee1de20f337e502f94be1d..98724c5b7f596c1868433b35f7cc39c682420b45 100644
--- a/matrix-sdk-android/build.gradle
+++ b/matrix-sdk-android/build.gradle
@@ -159,7 +159,7 @@ dependencies {
     implementation libs.apache.commonsImaging
 
     // Phone number https://github.com/google/libphonenumber
-    implementation 'com.googlecode.libphonenumber:libphonenumber:8.12.35'
+    implementation 'com.googlecode.libphonenumber:libphonenumber:8.12.36'
 
     testImplementation libs.tests.junit
     testImplementation 'org.robolectric:robolectric:4.6.1'
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/MatrixConfiguration.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/MatrixConfiguration.kt
index 3359e253f6408f65e1ca96677046cd9988cfcd2d..306ed455003a78f3f1d22500677934568be658fc 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/MatrixConfiguration.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/MatrixConfiguration.kt
@@ -16,6 +16,7 @@
 
 package org.matrix.android.sdk.api
 
+import okhttp3.ConnectionSpec
 import org.matrix.android.sdk.api.crypto.MXCryptoConfig
 import java.net.Proxy
 
@@ -44,6 +45,10 @@ data class MatrixConfiguration(
          * You can create one using for instance Proxy(proxyType, InetSocketAddress.createUnresolved(hostname, port).
          */
         val proxy: Proxy? = null,
+        /**
+         * TLS versions and cipher suites limitation for unauthenticated requests
+         */
+        val connectionSpec: ConnectionSpec = ConnectionSpec.RESTRICTED_TLS,
         /**
          * True to advertise support for call transfers to other parties on Matrix calls.
          */
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/AuthenticationService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/AuthenticationService.kt
index 5e359172431564f5c880c20a9e480f2d50604674..9cb784c9c02321c8f0ba93e7e22d6b6b6500160a 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/AuthenticationService.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/AuthenticationService.kt
@@ -105,9 +105,15 @@ interface AuthenticationService {
     /**
      * Authenticate with a matrixId and a password
      * Usually call this after a successful call to getWellKnownData()
+     * @param homeServerConnectionConfig the information about the homeserver and other configuration
+     * @param matrixId the matrixId of the user
+     * @param password the password of the account
+     * @param initialDeviceName the initial device name
+     * @param deviceId the device id, optional. If not provided or null, the server will generate one.
      */
     suspend fun directAuthentication(homeServerConnectionConfig: HomeServerConnectionConfig,
                                      matrixId: String,
                                      password: String,
-                                     initialDeviceName: String): Session
+                                     initialDeviceName: String,
+                                     deviceId: String? = null): Session
 }
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/data/HomeServerConnectionConfig.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/data/HomeServerConnectionConfig.kt
index 215f0a0351a9442d3d52d43685273ed3aa4ee5b4..e87824d6a5f50bc4ce4b685aa4b4c1393e284754 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/data/HomeServerConnectionConfig.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/data/HomeServerConnectionConfig.kt
@@ -19,6 +19,7 @@ package org.matrix.android.sdk.api.auth.data
 import android.net.Uri
 import com.squareup.moshi.JsonClass
 import okhttp3.CipherSuite
+import okhttp3.ConnectionSpec
 import okhttp3.TlsVersion
 import org.matrix.android.sdk.api.auth.data.HomeServerConnectionConfig.Builder
 import org.matrix.android.sdk.internal.network.ssl.Fingerprint
@@ -191,32 +192,25 @@ data class HomeServerConnectionConfig(
         /**
          * Convenient method to limit the TLS versions and cipher suites for this Builder
          * Ref:
-         * - https://www.ssi.gouv.fr/uploads/2017/02/security-recommendations-for-tls_v1.1.pdf
+         * - https://www.ssi.gouv.fr/uploads/2017/07/anssi-guide-recommandations_de_securite_relatives_a_tls-v1.2.pdf
          * - https://developer.android.com/reference/javax/net/ssl/SSLEngine
          *
          * @param tlsLimitations         true to use Tls limitations
          * @param enableCompatibilityMode set to true for Android < 20
          * @return this builder
          */
+        @Deprecated("TLS versions and cipher suites are limited by default")
         fun withTlsLimitations(tlsLimitations: Boolean, enableCompatibilityMode: Boolean): Builder {
             if (tlsLimitations) {
                 withShouldAcceptTlsExtensions(false)
 
-                // Tls versions
-                addAcceptedTlsVersion(TlsVersion.TLS_1_2)
-                addAcceptedTlsVersion(TlsVersion.TLS_1_3)
+                // TlS versions
+                ConnectionSpec.RESTRICTED_TLS.tlsVersions?.let { this.tlsVersions.addAll(it) }
 
                 forceUsageOfTlsVersions(enableCompatibilityMode)
 
                 // Cipher suites
-                addAcceptedTlsCipherSuite(CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256)
-                addAcceptedTlsCipherSuite(CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256)
-                addAcceptedTlsCipherSuite(CipherSuite.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256)
-                addAcceptedTlsCipherSuite(CipherSuite.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256)
-                addAcceptedTlsCipherSuite(CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384)
-                addAcceptedTlsCipherSuite(CipherSuite.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384)
-                addAcceptedTlsCipherSuite(CipherSuite.TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256)
-                addAcceptedTlsCipherSuite(CipherSuite.TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256)
+                ConnectionSpec.RESTRICTED_TLS.cipherSuites?.let { this.tlsCipherSuites.addAll(it) }
 
                 if (enableCompatibilityMode) {
                     // Adopt some preceding cipher suites for Android < 20 to be able to negotiate
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/login/LoginWizard.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/login/LoginWizard.kt
index a2a93738370e8a79737e3a741f8b9eda48413b05..247d58ce7972bcd2157478726d2747cdcfbd8420 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/login/LoginWizard.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/login/LoginWizard.kt
@@ -17,6 +17,7 @@
 package org.matrix.android.sdk.api.auth.login
 
 import org.matrix.android.sdk.api.session.Session
+import org.matrix.android.sdk.api.util.JsonDict
 
 /**
  * Set of methods to be able to login to an existing account on a homeserver.
@@ -34,12 +35,14 @@ interface LoginWizard {
      *
      * @param login the login field. Can be a user name, or a msisdn (email or phone number) associated to the account
      * @param password the password of the account
-     * @param deviceName the initial device name
+     * @param initialDeviceName the initial device name
+     * @param deviceId the device id, optional. If not provided or null, the server will generate one.
      * @return a [Session] if the login is successful
      */
     suspend fun login(login: String,
                       password: String,
-                      deviceName: String): Session
+                      initialDeviceName: String,
+                      deviceId: String? = null): Session
 
     /**
      * Exchange a login token to an access token.
@@ -49,6 +52,12 @@ interface LoginWizard {
      */
     suspend fun loginWithToken(loginToken: String): Session
 
+    /**
+     * Login to the homeserver by sending a custom JsonDict.
+     * The data should contain at least one entry "type" with a String value.
+     */
+    suspend fun loginCustom(data: JsonDict): Session
+
     /**
      * Ask the homeserver to reset the user password. The password will not be reset until
      * [resetPasswordMailConfirmed] is successfully called.
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 8f83beface43f94596e8cb5196ada60ba4a2551a..31ec131c5ca9558710acf93335b196404776425e 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,17 +19,41 @@ 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 {
+        val string: String
+        val case: Case
+    }
+
+    object NoCondition : QueryStringValue
+    object IsNull : QueryStringValue
+    object IsNotNull : QueryStringValue
+    object IsEmpty : QueryStringValue
+    object IsNotEmpty : QueryStringValue
+
+    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 {
+        /**
+         * Match query sensitive to case
+         */
         SENSITIVE,
-        INSENSITIVE
+
+        /**
+         * 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
     }
 }
+
+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/api/session/Session.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/Session.kt
index d4bfd4ee8c13ebe02c81ff1157b1b27525acde5e..dfe43aed6f4bd3b4574f452856288ac4ef06127e 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/Session.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/Session.kt
@@ -120,9 +120,11 @@ interface Session :
     fun requireBackgroundSync()
 
     /**
-     * Launches infinite periodic background syncs
-     * This does not work in doze mode :/
-     * If battery optimization is on it can work in app standby but that's all :/
+     * Launches infinite self rescheduling background syncs via the WorkManager
+     *
+     * While dozing, syncs will only occur during maintenance windows
+     * For reliability it's recommended to also start a long running foreground service
+     * along with disabling battery optimizations
      */
     fun startAutomaticBackgroundSync(timeOutInSeconds: Long, repeatDelayInSeconds: Long)
 
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/events/model/Event.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/events/model/Event.kt
index 169f90dbca0a5b023a8f27b32e21af705cdd2051..aad5fce33ecc3ab849136dd9730b0ee12b23af2f 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/events/model/Event.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/events/model/Event.kt
@@ -22,6 +22,8 @@ import org.json.JSONObject
 import org.matrix.android.sdk.api.extensions.tryOrNull
 import org.matrix.android.sdk.api.failure.MatrixError
 import org.matrix.android.sdk.api.session.crypto.MXCryptoError
+import org.matrix.android.sdk.api.session.room.model.Membership
+import org.matrix.android.sdk.api.session.room.model.RoomMemberContent
 import org.matrix.android.sdk.api.session.room.model.message.MessageContent
 import org.matrix.android.sdk.api.session.room.model.message.MessageType
 import org.matrix.android.sdk.api.session.room.model.relation.RelationDefaultContent
@@ -310,3 +312,6 @@ fun Event.isEdition(): Boolean {
 fun Event.getPresenceContent(): PresenceContent? {
     return content.toModel<PresenceContent>()
 }
+
+fun Event.isInvitation(): Boolean = type == EventType.STATE_ROOM_MEMBER &&
+        content?.toModel<RoomMemberContent>()?.membership == Membership.INVITE
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 d80faa729c2c844ad4ec30644c7236df95c8e182..e4bd498990eeb35f61a200e48cdad28d876b82e5 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<RoomSummary>
+    fun getRoomSummaries(queryParams: RoomSummaryQueryParams,
+                         sortOrder: RoomSortOrder = RoomSortOrder.NONE): List<RoomSummary>
 
     /**
      * 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<List<RoomSummary>>
+    fun getRoomSummariesLive(queryParams: RoomSummaryQueryParams,
+                             sortOrder: RoomSortOrder = RoomSortOrder.ACTIVITY): LiveData<List<RoomSummary>>
 
     /**
      * 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 f40572518f0322b3c4ff783cd4511e8ec60aae38..357c0b941a4d60fc40b034f4d02fa05209d03632 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<List<RoomSummary>>
+    fun getSpaceSummariesLive(queryParams: SpaceSummaryQueryParams,
+                              sortOrder: RoomSortOrder = RoomSortOrder.NONE): LiveData<List<RoomSummary>>
 
-    fun getSpaceSummaries(spaceSummaryQueryParams: SpaceSummaryQueryParams): List<RoomSummary>
+    fun getSpaceSummaries(spaceSummaryQueryParams: SpaceSummaryQueryParams,
+                          sortOrder: RoomSortOrder = RoomSortOrder.NONE): List<RoomSummary>
 
     suspend fun joinSpace(spaceIdOrAlias: String,
                           reason: String? = null,
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/AuthAPI.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/AuthAPI.kt
index 50d9e5a06c88a875824eba715b445d04e24bf85a..554e21ce55981a76522302f4bde66b3562b37619 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/AuthAPI.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/AuthAPI.kt
@@ -121,6 +121,10 @@ internal interface AuthAPI {
     @POST(NetworkConstants.URI_API_PREFIX_PATH_R0 + "login")
     suspend fun login(@Body loginParams: TokenLoginParams): Credentials
 
+    @Headers("CONNECT_TIMEOUT:60000", "READ_TIMEOUT:60000", "WRITE_TIMEOUT:60000")
+    @POST(NetworkConstants.URI_API_PREFIX_PATH_R0 + "login")
+    suspend fun login(@Body loginParams: JsonDict): Credentials
+
     /**
      * Ask the homeserver to reset the password associated with the provided email.
      */
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/DefaultAuthenticationService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/DefaultAuthenticationService.kt
index 641a8f1bb6eb853816feef1560e08a07e9d5507a..8784d85c1073021869586ea00e1409f5fa38190b 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/DefaultAuthenticationService.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/DefaultAuthenticationService.kt
@@ -388,8 +388,15 @@ internal class DefaultAuthenticationService @Inject constructor(
     override suspend fun directAuthentication(homeServerConnectionConfig: HomeServerConnectionConfig,
                                               matrixId: String,
                                               password: String,
-                                              initialDeviceName: String): Session {
-        return directLoginTask.execute(DirectLoginTask.Params(homeServerConnectionConfig, matrixId, password, initialDeviceName))
+                                              initialDeviceName: String,
+                                              deviceId: String?): Session {
+        return directLoginTask.execute(DirectLoginTask.Params(
+                homeServerConnectionConfig = homeServerConnectionConfig,
+                userId = matrixId,
+                password = password,
+                deviceName = initialDeviceName,
+                deviceId = deviceId
+        ))
     }
 
     private fun buildAuthAPI(homeServerConnectionConfig: HomeServerConnectionConfig): AuthAPI {
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/data/PasswordLoginParams.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/data/PasswordLoginParams.kt
index d4b14f1ca96cc1d8807d36faab18a85a8c12f4a9..5be480f633b8096c8211f83051e7a34d9429d22e 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/data/PasswordLoginParams.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/data/PasswordLoginParams.kt
@@ -49,51 +49,54 @@ internal data class PasswordLoginParams(
 
         fun userIdentifier(user: String,
                            password: String,
-                           deviceDisplayName: String? = null,
-                           deviceId: String? = null): PasswordLoginParams {
+                           deviceDisplayName: String?,
+                           deviceId: String?): PasswordLoginParams {
             return PasswordLoginParams(
-                    mapOf(
+                    identifier = mapOf(
                             IDENTIFIER_KEY_TYPE to IDENTIFIER_KEY_TYPE_USER,
                             IDENTIFIER_KEY_USER to user
                     ),
-                    password,
-                    LoginFlowTypes.PASSWORD,
-                    deviceDisplayName,
-                    deviceId)
+                    password = password,
+                    type = LoginFlowTypes.PASSWORD,
+                    deviceDisplayName = deviceDisplayName,
+                    deviceId = deviceId
+            )
         }
 
         fun thirdPartyIdentifier(medium: String,
                                  address: String,
                                  password: String,
-                                 deviceDisplayName: String? = null,
-                                 deviceId: String? = null): PasswordLoginParams {
+                                 deviceDisplayName: String?,
+                                 deviceId: String?): PasswordLoginParams {
             return PasswordLoginParams(
-                    mapOf(
+                    identifier = mapOf(
                             IDENTIFIER_KEY_TYPE to IDENTIFIER_KEY_TYPE_THIRD_PARTY,
                             IDENTIFIER_KEY_MEDIUM to medium,
                             IDENTIFIER_KEY_ADDRESS to address
                     ),
-                    password,
-                    LoginFlowTypes.PASSWORD,
-                    deviceDisplayName,
-                    deviceId)
+                    password = password,
+                    type = LoginFlowTypes.PASSWORD,
+                    deviceDisplayName = deviceDisplayName,
+                    deviceId = deviceId
+            )
         }
 
         fun phoneIdentifier(country: String,
                             phone: String,
                             password: String,
-                            deviceDisplayName: String? = null,
-                            deviceId: String? = null): PasswordLoginParams {
+                            deviceDisplayName: String?,
+                            deviceId: String?): PasswordLoginParams {
             return PasswordLoginParams(
-                    mapOf(
+                    identifier = mapOf(
                             IDENTIFIER_KEY_TYPE to IDENTIFIER_KEY_TYPE_PHONE,
                             IDENTIFIER_KEY_COUNTRY to country,
                             IDENTIFIER_KEY_PHONE to phone
                     ),
-                    password,
-                    LoginFlowTypes.PASSWORD,
-                    deviceDisplayName,
-                    deviceId)
+                    password = password,
+                    type = LoginFlowTypes.PASSWORD,
+                    deviceDisplayName = deviceDisplayName,
+                    deviceId = deviceId
+            )
         }
     }
 }
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/login/DefaultLoginWizard.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/login/DefaultLoginWizard.kt
index 854caf8a622a90adf17c907dc4e36d658423da06..b72cff3cf1d8f100c86f3bcca3b40ec81f91629b 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/login/DefaultLoginWizard.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/login/DefaultLoginWizard.kt
@@ -21,6 +21,7 @@ import org.matrix.android.sdk.api.auth.login.LoginProfileInfo
 import org.matrix.android.sdk.api.auth.login.LoginWizard
 import org.matrix.android.sdk.api.auth.registration.RegisterThreePid
 import org.matrix.android.sdk.api.session.Session
+import org.matrix.android.sdk.api.util.JsonDict
 import org.matrix.android.sdk.internal.auth.AuthAPI
 import org.matrix.android.sdk.internal.auth.PendingSessionStore
 import org.matrix.android.sdk.internal.auth.SessionCreator
@@ -52,11 +53,23 @@ internal class DefaultLoginWizard(
 
     override suspend fun login(login: String,
                                password: String,
-                               deviceName: String): Session {
+                               initialDeviceName: String,
+                               deviceId: String?): Session {
         val loginParams = if (Patterns.EMAIL_ADDRESS.matcher(login).matches()) {
-            PasswordLoginParams.thirdPartyIdentifier(ThreePidMedium.EMAIL, login, password, deviceName)
+            PasswordLoginParams.thirdPartyIdentifier(
+                    medium = ThreePidMedium.EMAIL,
+                    address = login,
+                    password = password,
+                    deviceDisplayName = initialDeviceName,
+                    deviceId = deviceId
+            )
         } else {
-            PasswordLoginParams.userIdentifier(login, password, deviceName)
+            PasswordLoginParams.userIdentifier(
+                    user = login,
+                    password = password,
+                    deviceDisplayName = initialDeviceName,
+                    deviceId = deviceId
+            )
         }
         val credentials = executeRequest(null) {
             authAPI.login(loginParams)
@@ -79,6 +92,14 @@ internal class DefaultLoginWizard(
         return sessionCreator.createSession(credentials, pendingSessionData.homeServerConnectionConfig)
     }
 
+    override suspend fun loginCustom(data: JsonDict): Session {
+        val credentials = executeRequest(null) {
+            authAPI.login(data)
+        }
+
+        return sessionCreator.createSession(credentials, pendingSessionData.homeServerConnectionConfig)
+    }
+
     override suspend fun resetPassword(email: String, newPassword: String) {
         val param = RegisterAddThreePidTask.Params(
                 RegisterThreePid.Email(email),
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/login/DirectLoginTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/login/DirectLoginTask.kt
index 8f61afe3742df4ab081fb246bde7714b14d28692..28706c7e8081edab796743bbcd9fbf498680cd93 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/login/DirectLoginTask.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/login/DirectLoginTask.kt
@@ -37,7 +37,8 @@ internal interface DirectLoginTask : Task<DirectLoginTask.Params, Session> {
             val homeServerConnectionConfig: HomeServerConnectionConfig,
             val userId: String,
             val password: String,
-            val deviceName: String
+            val deviceName: String,
+            val deviceId: String?
     )
 }
 
@@ -55,7 +56,12 @@ internal class DefaultDirectLoginTask @Inject constructor(
         val authAPI = retrofitFactory.create(client, homeServerUrl)
                 .create(AuthAPI::class.java)
 
-        val loginParams = PasswordLoginParams.userIdentifier(params.userId, params.password, params.deviceName)
+        val loginParams = PasswordLoginParams.userIdentifier(
+                user = params.userId,
+                password = params.password,
+                deviceDisplayName = params.deviceName,
+                deviceId = params.deviceId
+        )
 
         val credentials = try {
             executeRequest(null) {
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/tasks/SendEventTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/tasks/SendEventTask.kt
index e1e297767b100902ad4105f9f26f8fe53878f7b2..e40db6af67c92d2ef5243f0fa8c3670c05baa9d1 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/tasks/SendEventTask.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/tasks/SendEventTask.kt
@@ -34,7 +34,7 @@ internal interface SendEventTask : Task<SendEventTask.Params, String> {
 
 internal class DefaultSendEventTask @Inject constructor(
         private val localEchoRepository: LocalEchoRepository,
-        private val encryptEventTask: DefaultEncryptEventTask,
+        private val encryptEventTask: EncryptEventTask,
         private val loadRoomMembersTask: LoadRoomMembersTask,
         private val roomAPI: RoomAPI,
         private val globalErrorReceiver: GlobalErrorReceiver) : SendEventTask {
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/tasks/SendVerificationMessageTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/tasks/SendVerificationMessageTask.kt
index 7fa48c3da1b56318f7d031cd7d1767eff24d4e70..c4a6ba27d6834db29cbd9e5e5193f62874889518 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/tasks/SendVerificationMessageTask.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/tasks/SendVerificationMessageTask.kt
@@ -34,7 +34,7 @@ internal interface SendVerificationMessageTask : Task<SendVerificationMessageTas
 
 internal class DefaultSendVerificationMessageTask @Inject constructor(
         private val localEchoRepository: LocalEchoRepository,
-        private val encryptEventTask: DefaultEncryptEventTask,
+        private val encryptEventTask: EncryptEventTask,
         private val roomAPI: RoomAPI,
         private val cryptoSessionInfoProvider: CryptoSessionInfoProvider,
         private val globalErrorReceiver: GlobalErrorReceiver) : SendVerificationMessageTask {
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 05137f810529f83b80799266739c3b66370ff376..2256d9310010c8da6ba7e82d05a3ec6fc2a826e4 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,24 @@ 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
+    }
+
+    /**
+     * 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")
@@ -72,6 +85,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 +378,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 1771c5b202f8363fdbf79c6e1820fb5f072b771d..04ca26a943da6092ad096e218fbd312f4534f8b9 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/mapper/RoomSummaryMapper.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/mapper/RoomSummaryMapper.kt
index 5900ef6b76dcf840d9f3f7974ac7525df07aa6a9..3a15e0acf01e6813394348f02faa69ad6704c845 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
@@ -21,13 +21,13 @@ import org.matrix.android.sdk.api.session.room.model.RoomSummary
 import org.matrix.android.sdk.api.session.room.model.SpaceChildInfo
 import org.matrix.android.sdk.api.session.room.model.SpaceParentInfo
 import org.matrix.android.sdk.api.session.room.model.tag.RoomTag
+import org.matrix.android.sdk.api.session.typing.TypingUsersTracker
 import org.matrix.android.sdk.internal.database.model.RoomSummaryEntity
 import org.matrix.android.sdk.internal.database.model.presence.toUserPresence
-import org.matrix.android.sdk.internal.session.typing.DefaultTypingUsersTracker
 import javax.inject.Inject
 
 internal class RoomSummaryMapper @Inject constructor(private val timelineEventMapper: TimelineEventMapper,
-                                                     private val typingUsersTracker: DefaultTypingUsersTracker) {
+                                                     private val typingUsersTracker: TypingUsersTracker) {
 
     fun map(roomSummaryEntity: RoomSummaryEntity): RoomSummary {
         val tags = roomSummaryEntity.tags().map {
@@ -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 88b8886936a2802ff9ff8527f47443beb3bad19d..67672f03add0d75342ce7f78d6377c7725f795e3 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,10 +37,24 @@ internal open class RoomSummaryEntity(
         var children: RealmList<SpaceChildSummaryEntity> = 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
+     */
+    private var normalizedDisplayName: String? = ""
+
     var avatarUrl: String? = ""
         set(value) {
             if (value != field) field = value
@@ -284,5 +299,6 @@ internal open class RoomSummaryEntity(
                 roomEncryptionTrustLevelStr = value?.name
             }
         }
+
     companion object
 }
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/di/NetworkModule.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/di/NetworkModule.kt
index fa59b94c1756767f3166d0bda60295eea5c7fc0b..ad34a4d8a6411082f1ba02607a5d3a7b0a12f95d 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/di/NetworkModule.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/di/NetworkModule.kt
@@ -20,6 +20,7 @@ import com.facebook.stetho.okhttp3.StethoInterceptor
 import com.squareup.moshi.Moshi
 import dagger.Module
 import dagger.Provides
+import okhttp3.ConnectionSpec
 import okhttp3.OkHttpClient
 import okhttp3.logging.HttpLoggingInterceptor
 import org.matrix.android.sdk.BuildConfig
@@ -29,6 +30,7 @@ import org.matrix.android.sdk.internal.network.TimeOutInterceptor
 import org.matrix.android.sdk.internal.network.UserAgentInterceptor
 import org.matrix.android.sdk.internal.network.interceptors.CurlLoggingInterceptor
 import org.matrix.android.sdk.internal.network.interceptors.FormattedJsonHttpLogger
+import java.util.Collections
 import java.util.concurrent.TimeUnit
 
 @Module
@@ -66,6 +68,8 @@ internal object NetworkModule {
                              httpLoggingInterceptor: HttpLoggingInterceptor,
                              curlLoggingInterceptor: CurlLoggingInterceptor,
                              apiInterceptor: ApiInterceptor): OkHttpClient {
+        val spec = ConnectionSpec.Builder(matrixConfiguration.connectionSpec).build()
+
         return OkHttpClient.Builder()
                 .connectTimeout(30, TimeUnit.SECONDS)
                 .readTimeout(60, TimeUnit.SECONDS)
@@ -87,6 +91,7 @@ internal object NetworkModule {
                         proxy(it)
                     }
                 }
+                .connectionSpecs(Collections.singletonList(spec))
                 .build()
     }
 
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/network/ssl/CertUtil.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/network/ssl/CertUtil.kt
index 92c7f3f2369968b95b7246e863d06b09079c60b8..d8bdc5fc2b2b29fa01a707d845af712997d42c1b 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/network/ssl/CertUtil.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/network/ssl/CertUtil.kt
@@ -177,15 +177,13 @@ internal object CertUtil {
 
             val trustPinned = arrayOf<TrustManager>(PinnedTrustManagerProvider.provide(hsConfig.allowedFingerprints, defaultTrustManager))
 
-            val sslSocketFactory: SSLSocketFactory
-
-            if (hsConfig.forceUsageTlsVersions && hsConfig.tlsVersions != null) {
+            val sslSocketFactory = if (hsConfig.forceUsageTlsVersions && !hsConfig.tlsVersions.isNullOrEmpty()) {
                 // Force usage of accepted Tls Versions for Android < 20
-                sslSocketFactory = TLSSocketFactory(trustPinned, hsConfig.tlsVersions)
+                TLSSocketFactory(trustPinned, hsConfig.tlsVersions)
             } else {
                 val sslContext = SSLContext.getInstance("TLS")
                 sslContext.init(null, trustPinned, java.security.SecureRandom())
-                sslSocketFactory = sslContext.socketFactory
+                sslContext.socketFactory
             }
 
             return PinnedSSLSocketFactory(sslSocketFactory, defaultTrustManager!!)
@@ -237,14 +235,14 @@ internal object CertUtil {
      * @return a list of accepted TLS specifications.
      */
     fun newConnectionSpecs(hsConfig: HomeServerConnectionConfig): List<ConnectionSpec> {
-        val builder = ConnectionSpec.Builder(ConnectionSpec.MODERN_TLS)
+        val builder = ConnectionSpec.Builder(ConnectionSpec.RESTRICTED_TLS)
         val tlsVersions = hsConfig.tlsVersions
-        if (null != tlsVersions && tlsVersions.isNotEmpty()) {
+        if (!tlsVersions.isNullOrEmpty()) {
             builder.tlsVersions(*tlsVersions.toTypedArray())
         }
 
         val tlsCipherSuites = hsConfig.tlsCipherSuites
-        if (null != tlsCipherSuites && tlsCipherSuites.isNotEmpty()) {
+        if (!tlsCipherSuites.isNullOrEmpty()) {
             builder.cipherSuites(*tlsCipherSuites.toTypedArray())
         }
 
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 fd3368223139837781fe7a2f97d64d6f48183d1e..b42bf2b8c789f835044326ed317ec0c4f658c5fb 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,24 +20,41 @@ import io.realm.Case
 import io.realm.RealmObject
 import io.realm.RealmQuery
 import org.matrix.android.sdk.api.query.QueryStringValue
-import timber.log.Timber
+import org.matrix.android.sdk.api.query.QueryStringValue.ContentQueryStringValue
+import org.matrix.android.sdk.internal.util.Normalizer
+import javax.inject.Inject
 
-fun <T : RealmObject> RealmQuery<T>.process(field: String, queryStringValue: QueryStringValue): RealmQuery<T> {
-    when (queryStringValue) {
-        is QueryStringValue.NoCondition -> Timber.v("No condition to process")
-        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())
+class QueryStringValueProcessor @Inject constructor(
+        private val normalizer: Normalizer
+) {
+
+    fun <T : RealmObject> RealmQuery<T>.process(field: String, queryStringValue: QueryStringValue): RealmQuery<T> {
+        return when (queryStringValue) {
+            is QueryStringValue.NoCondition -> 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.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
+        }
     }
-    return this
 }
 
 private fun QueryStringValue.Case.toRealmCase(): Case {
     return when (this) {
         QueryStringValue.Case.INSENSITIVE -> Case.INSENSITIVE
-        QueryStringValue.Case.SENSITIVE   -> Case.SENSITIVE
+        QueryStringValue.Case.SENSITIVE,
+        QueryStringValue.Case.NORMALIZED  -> Case.SENSITIVE
     }
 }
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/DefaultSession.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/DefaultSession.kt
index 6a6bce5ce28e2f5b14940fa1afef164ae0d49c10..c52462612aeb9a566fcb6a2fcdb1c4cee5eeb8af 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/DefaultSession.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/DefaultSession.kt
@@ -41,6 +41,7 @@ import org.matrix.android.sdk.api.session.file.ContentDownloadStateTracker
 import org.matrix.android.sdk.api.session.file.FileService
 import org.matrix.android.sdk.api.session.group.GroupService
 import org.matrix.android.sdk.api.session.homeserver.HomeServerCapabilitiesService
+import org.matrix.android.sdk.api.session.identity.IdentityService
 import org.matrix.android.sdk.api.session.initsync.SyncStatusService
 import org.matrix.android.sdk.api.session.integrationmanager.IntegrationManagerService
 import org.matrix.android.sdk.api.session.media.MediaService
@@ -72,7 +73,6 @@ import org.matrix.android.sdk.internal.di.SessionId
 import org.matrix.android.sdk.internal.di.UnauthenticatedWithCertificate
 import org.matrix.android.sdk.internal.di.WorkManagerProvider
 import org.matrix.android.sdk.internal.network.GlobalErrorHandler
-import org.matrix.android.sdk.internal.session.identity.DefaultIdentityService
 import org.matrix.android.sdk.internal.session.sync.SyncTokenStore
 import org.matrix.android.sdk.internal.session.sync.job.SyncThread
 import org.matrix.android.sdk.internal.session.sync.job.SyncWorker
@@ -124,7 +124,7 @@ internal class DefaultSession @Inject constructor(
         private val _sharedSecretStorageService: Lazy<SharedSecretStorageService>,
         private val accountService: Lazy<AccountService>,
         private val eventService: Lazy<EventService>,
-        private val defaultIdentityService: DefaultIdentityService,
+        private val identityService: IdentityService,
         private val integrationManagerService: IntegrationManagerService,
         private val thirdPartyService: Lazy<ThirdPartyService>,
         private val callSignalingService: Lazy<CallSignalingService>,
@@ -275,7 +275,7 @@ internal class DefaultSession @Inject constructor(
 
     override fun cryptoService(): CryptoService = cryptoService.get()
 
-    override fun identityService() = defaultIdentityService
+    override fun identityService() = identityService
 
     override fun fileService(): FileService = defaultFileService.get()
 
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/SessionModule.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/SessionModule.kt
index dc59277f64e97688f27e3d8acd61781825f820ca..ebc2176a1308744e4658b2b703ffb71331ee2442 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/SessionModule.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/SessionModule.kt
@@ -163,6 +163,7 @@ internal abstract class SessionModule {
         @JvmStatic
         @Provides
         @SessionFilesDirectory
+        @SessionScope
         fun providesFilesDir(@UserMd5 userMd5: String,
                              @SessionId sessionId: String,
                              context: Context): File {
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/content/FileUploader.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/content/FileUploader.kt
index bdebb0addf58fe7d76ac11900cf846b6bc6f4279..1b0ccbb48956688f75e80f32345a85b3e1a96a5c 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/content/FileUploader.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/content/FileUploader.kt
@@ -35,12 +35,12 @@ import org.matrix.android.sdk.api.failure.Failure
 import org.matrix.android.sdk.api.failure.MatrixError
 import org.matrix.android.sdk.api.session.content.ContentUrlResolver
 import org.matrix.android.sdk.api.session.homeserver.HomeServerCapabilities
+import org.matrix.android.sdk.api.session.homeserver.HomeServerCapabilitiesService
 import org.matrix.android.sdk.internal.di.Authenticated
 import org.matrix.android.sdk.internal.network.GlobalErrorReceiver
 import org.matrix.android.sdk.internal.network.ProgressRequestBody
 import org.matrix.android.sdk.internal.network.awaitResponse
 import org.matrix.android.sdk.internal.network.toFailure
-import org.matrix.android.sdk.internal.session.homeserver.DefaultHomeServerCapabilitiesService
 import org.matrix.android.sdk.internal.util.TemporaryFileCreator
 import java.io.File
 import java.io.FileNotFoundException
@@ -50,7 +50,7 @@ import javax.inject.Inject
 internal class FileUploader @Inject constructor(
         @Authenticated private val okHttpClient: OkHttpClient,
         private val globalErrorReceiver: GlobalErrorReceiver,
-        private val homeServerCapabilitiesService: DefaultHomeServerCapabilitiesService,
+        private val homeServerCapabilitiesService: HomeServerCapabilitiesService,
         private val context: Context,
         private val temporaryFileCreator: TemporaryFileCreator,
         contentUrlResolver: ContentUrlResolver,
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/content/UploadContentWorker.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/content/UploadContentWorker.kt
index 0c5a90ca60cb2df91fc878c11e9219c1d18039a7..b657d950bda760ddef363801802dd390967e692a 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/content/UploadContentWorker.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/content/UploadContentWorker.kt
@@ -214,8 +214,11 @@ internal class UploadContentWorker(val context: Context, params: WorkerParameter
                                                 .also { filesToDelete.add(it) }
                                     }
                                     VideoCompressionResult.CompressionNotNeeded,
-                                    VideoCompressionResult.CompressionCancelled,
+                                    VideoCompressionResult.CompressionCancelled -> {
+                                        workingFile
+                                    }
                                     is VideoCompressionResult.CompressionFailed -> {
+                                        Timber.e(videoCompressionResult.failure, "Video compression failed")
                                         workingFile
                                     }
                                 }
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/content/VideoCompressor.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/content/VideoCompressor.kt
index 05aaf4e9f16066686c43071f3195480b06fd5b1d..a43f8abf3347b7b5286945b7057632e0ee96c0ec 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/content/VideoCompressor.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/content/VideoCompressor.kt
@@ -18,6 +18,7 @@ package org.matrix.android.sdk.internal.session.content
 
 import com.otaliastudios.transcoder.Transcoder
 import com.otaliastudios.transcoder.TranscoderListener
+import com.otaliastudios.transcoder.source.FilePathDataSource
 import kotlinx.coroutines.Dispatchers
 import kotlinx.coroutines.Job
 import kotlinx.coroutines.withContext
@@ -43,7 +44,16 @@ internal class VideoCompressor @Inject constructor(
         var result: Int = -1
         var failure: Throwable? = null
         Transcoder.into(destinationFile.path)
-                .addDataSource(videoFile.path)
+                .addDataSource(object : FilePathDataSource(videoFile.path) {
+                    // https://github.com/natario1/Transcoder/issues/154
+                    @Suppress("SENSELESS_COMPARISON") // Source is annotated as @NonNull, but can actually be null...
+                    override fun isInitialized(): Boolean {
+                        if (source == null) {
+                            return false
+                        }
+                        return super.isInitialized()
+                    }
+                })
                 .setListener(object : TranscoderListener {
                     override fun onTranscodeProgress(progress: Double) {
                         Timber.d("Compressing: $progress%")
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 8dc5f3931d21c266d1d2d5fd4cb4ee27c21fdc9f..9334d09377228be0ff3bd5d0050a9fd4499bbd4c 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<GroupSummaryEntity> {
-        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/identity/DefaultIdentityService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/identity/DefaultIdentityService.kt
index 37d9a4e74f8d649576ca9ee7583721d672ac6e72..c8a9c0f09ad490b090f4007a5d8d8a588bb57e3f 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/identity/DefaultIdentityService.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/identity/DefaultIdentityService.kt
@@ -80,7 +80,7 @@ internal class DefaultIdentityService @Inject constructor(
         private val identityApiProvider: IdentityApiProvider,
         private val accountDataDataSource: UserAccountDataDataSource,
         private val homeServerCapabilitiesService: HomeServerCapabilitiesService,
-        private val sign3pidInvitationTask: DefaultSign3pidInvitationTask,
+        private val sign3pidInvitationTask: Sign3pidInvitationTask,
         private val sessionParams: SessionParams
 ) : IdentityService, SessionLifecycleObserver {
 
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/identity/IdentityModule.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/identity/IdentityModule.kt
index 19e602d7a716b08e28f6c3f011c42afc2adf81cb..65794e6b14a7a685fa2b00dcb2f2c3e2d6f6e84b 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/identity/IdentityModule.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/identity/IdentityModule.kt
@@ -21,6 +21,7 @@ import dagger.Module
 import dagger.Provides
 import io.realm.RealmConfiguration
 import okhttp3.OkHttpClient
+import org.matrix.android.sdk.api.session.identity.IdentityService
 import org.matrix.android.sdk.internal.database.RealmKeysUtils
 import org.matrix.android.sdk.internal.di.AuthenticatedIdentity
 import org.matrix.android.sdk.internal.di.IdentityDatabase
@@ -75,6 +76,9 @@ internal abstract class IdentityModule {
         }
     }
 
+    @Binds
+    abstract fun bindIdentityService(service: DefaultIdentityService): IdentityService
+
     @Binds
     @AuthenticatedIdentity
     abstract fun bindAccessTokenProvider(provider: IdentityAccessTokenProvider): AccessTokenProvider
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/notification/ProcessEventForPushTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/notification/ProcessEventForPushTask.kt
index 1321f8dd624b7c883a39c1f3feaec617e9280af1..3c74888eda1e50f852d1f4f12a8b7dfa06bdce45 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/notification/ProcessEventForPushTask.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/notification/ProcessEventForPushTask.kt
@@ -18,6 +18,7 @@ package org.matrix.android.sdk.internal.session.notification
 
 import org.matrix.android.sdk.api.pushrules.rest.PushRule
 import org.matrix.android.sdk.api.session.events.model.EventType
+import org.matrix.android.sdk.api.session.events.model.isInvitation
 import org.matrix.android.sdk.api.session.sync.model.RoomsSyncResponse
 import org.matrix.android.sdk.internal.di.UserId
 import org.matrix.android.sdk.internal.task.Task
@@ -48,14 +49,18 @@ internal class DefaultProcessEventForPushTask @Inject constructor(
         }
         val newJoinEvents = params.syncResponse.join
                 .mapNotNull { (key, value) ->
-                    value.timeline?.events?.map { it.copy(roomId = key) }
+                    value.timeline?.events?.mapNotNull {
+                        it.takeIf { !it.isInvitation() }?.copy(roomId = key)
+                    }
                 }
                 .flatten()
+
         val inviteEvents = params.syncResponse.invite
                 .mapNotNull { (key, value) ->
                     value.inviteState?.events?.map { it.copy(roomId = key) }
                 }
                 .flatten()
+
         val allEvents = (newJoinEvents + inviteEvents).filter { event ->
             when (event.type) {
                 EventType.MESSAGE,
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 5b2499c1300d8ce7b076a9d8df79710fbe48f544..7ca64aa66a7efcbc6b77b33643bd4ee294328a00 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<RoomSummary> {
-        return roomSummaryDataSource.getRoomSummaries(queryParams)
+    override fun getRoomSummaries(queryParams: RoomSummaryQueryParams,
+                                  sortOrder: RoomSortOrder): List<RoomSummary> {
+        return roomSummaryDataSource.getRoomSummaries(queryParams, sortOrder)
     }
 
-    override fun getRoomSummariesLive(queryParams: RoomSummaryQueryParams): LiveData<List<RoomSummary>> {
-        return roomSummaryDataSource.getRoomSummariesLive(queryParams)
+    override fun getRoomSummariesLive(queryParams: RoomSummaryQueryParams,
+                                      sortOrder: RoomSortOrder): LiveData<List<RoomSummary>> {
+        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/membership/DefaultMembershipService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/membership/DefaultMembershipService.kt
index 204deb72b4b42d38e6f313592720cba2ef3a9a37..6cf82dde44d8a20d1d973d1e59e870f5674e54d3 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<RoomMemberSummaryEntity> {
-        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/membership/RoomDisplayNameResolver.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/membership/RoomDisplayNameResolver.kt
index 5e77dd157abf1f8f42830af049491923978aaed7..bd9f2ecc36791a67242de1a5d9d010ee953498e1 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<RoomNameContent>()?.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<RoomCanonicalAliasContent>()?.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/state/StateEventDataSource.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/state/StateEventDataSource.kt
index a25a362bfa5bbd08ec13ba9fb9bb2deb0637c129..2114b9c5901c5cf5963a716e5442f2aa683cd327 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<String>,
                                      stateKey: QueryStringValue
     ): RealmQuery<CurrentStateEventEntity> {
-        return realm.where<CurrentStateEventEntity>()
-                .equalTo(CurrentStateEventEntityFields.ROOM_ID, roomId)
-                .apply {
-                    if (eventTypes.isNotEmpty()) {
-                        `in`(CurrentStateEventEntityFields.TYPE, eventTypes.toTypedArray())
+        return with(queryStringValueProcessor) {
+            realm.where<CurrentStateEventEntity>()
+                    .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 0b8c6df8066c6274acaf8c257619a030b1f45ed3..c9fc3c9575be324d9efcb63c07147fbc51ae6835 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,10 +25,10 @@ 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
+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
@@ -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
@@ -80,25 +84,27 @@ internal class RoomSummaryDataSource @Inject constructor(@SessionDatabase privat
         }
     }
 
-    fun getRoomSummaries(queryParams: RoomSummaryQueryParams): List<RoomSummary> {
+    fun getRoomSummaries(queryParams: RoomSummaryQueryParams,
+                         sortOrder: RoomSortOrder = RoomSortOrder.NONE): List<RoomSummary> {
         return monarchy.fetchAllMappedSync(
-                { roomSummariesQuery(it, queryParams) },
+                { roomSummariesQuery(it, queryParams).process(sortOrder) },
                 { roomSummaryMapper.map(it) }
         )
     }
 
-    fun getRoomSummariesLive(queryParams: RoomSummaryQueryParams): LiveData<List<RoomSummary>> {
+    fun getRoomSummariesLive(queryParams: RoomSummaryQueryParams,
+                             sortOrder: RoomSortOrder = RoomSortOrder.NONE): LiveData<List<RoomSummary>> {
         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<List<RoomSummary>> {
-        return getRoomSummariesLive(queryParams)
+    fun getSpaceSummariesLive(queryParams: SpaceSummaryQueryParams,
+                              sortOrder: RoomSortOrder = RoomSortOrder.NONE): LiveData<List<RoomSummary>> {
+        return getRoomSummariesLive(queryParams, sortOrder)
     }
 
     fun getSpaceSummary(roomIdOrAlias: String): RoomSummary? {
@@ -122,8 +128,9 @@ internal class RoomSummaryDataSource @Inject constructor(@SessionDatabase privat
         }
     }
 
-    fun getSpaceSummaries(spaceSummaryQueryParams: SpaceSummaryQueryParams): List<RoomSummary> {
-        return getRoomSummaries(spaceSummaryQueryParams)
+    fun getSpaceSummaries(spaceSummaryQueryParams: SpaceSummaryQueryParams,
+                          sortOrder: RoomSortOrder = RoomSortOrder.NONE): List<RoomSummary> {
+        return getRoomSummaries(spaceSummaryQueryParams, sortOrder)
     }
 
     fun getRootSpaceSummaries(): List<RoomSummary> {
@@ -238,12 +245,20 @@ internal class RoomSummaryDataSource @Inject constructor(@SessionDatabase privat
     }
 
     private fun roomSummariesQuery(realm: Realm, queryParams: RoomSummaryQueryParams): RealmQuery<RoomSummaryEntity> {
-        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) {
+            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 {
             when (it) {
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 30014f4539f2d367a0639c39b0282aceda83c3f7..3556cabb3354eda1fdb42c1157afe179b782f9c0 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,7 @@ 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)
+        roomSummaryEntity.setDisplayName(roomDisplayNameResolver.resolve(realm, roomId))
         roomSummaryEntity.avatarUrl = roomAvatarResolver.resolve(realm, roomId)
         roomSummaryEntity.name = ContentMapper.map(lastNameEvent?.content).toModel<RoomNameContent>()?.name
         roomSummaryEntity.topic = ContentMapper.map(lastTopicEvent?.content).toModel<RoomTopicContent>()?.topic
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/signout/SignInAgainTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/signout/SignInAgainTask.kt
index 563e85aefc5edbd2e23664a4103a057731314620..42fdf305012f775a3cb79ee14507abe953d0ad89 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/signout/SignInAgainTask.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/signout/SignInAgainTask.kt
@@ -42,11 +42,12 @@ internal class DefaultSignInAgainTask @Inject constructor(
             signOutAPI.loginAgain(
                     PasswordLoginParams.userIdentifier(
                             // Reuse the same userId
-                            sessionParams.userId,
-                            params.password,
+                            user = sessionParams.userId,
+                            password = params.password,
                             // The spec says the initial device name will be ignored
                             // https://matrix.org/docs/spec/client_server/latest#post-matrix-client-r0-login
                             // but https://github.com/matrix-org/synapse/issues/6525
+                            deviceDisplayName = null,
                             // Reuse the same deviceId
                             deviceId = sessionParams.deviceId
                     )
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 ac20c7905878305441ec4a6f9d9bb08c95a4c391..ebd5f2578ef539331333f3c941b6d429b7e99e46 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<List<RoomSummary>> {
-        return roomSummaryDataSource.getSpaceSummariesLive(queryParams)
+    override fun getSpaceSummariesLive(queryParams: SpaceSummaryQueryParams,
+                                       sortOrder: RoomSortOrder): LiveData<List<RoomSummary>> {
+        return roomSummaryDataSource.getSpaceSummariesLive(queryParams, sortOrder)
     }
 
-    override fun getSpaceSummaries(spaceSummaryQueryParams: SpaceSummaryQueryParams): List<RoomSummary> {
-        return roomSummaryDataSource.getSpaceSummaries(spaceSummaryQueryParams)
+    override fun getSpaceSummaries(spaceSummaryQueryParams: SpaceSummaryQueryParams,
+                                   sortOrder: RoomSortOrder): List<RoomSummary> {
+        return roomSummaryDataSource.getSpaceSummaries(spaceSummaryQueryParams, sortOrder)
     }
 
     override fun getRootSpaceSummaries(): List<RoomSummary> {
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/RoomSyncEphemeralTemporaryStore.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/RoomSyncEphemeralTemporaryStore.kt
index e8f74bbd4871a1f1122bcf408b7ebd8f372148e7..8c68e224dcde88756d958369c4af0aa5bd0d58a1 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/RoomSyncEphemeralTemporaryStore.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/RoomSyncEphemeralTemporaryStore.kt
@@ -39,8 +39,11 @@ internal class RoomSyncEphemeralTemporaryStoreFile @Inject constructor(
         moshi: Moshi
 ) : RoomSyncEphemeralTemporaryStore {
 
-    private val workingDir = File(fileDirectory, "rr")
-            .also { it.mkdirs() }
+    private val workingDir: File by lazy {
+        File(fileDirectory, "rr").also {
+            it.mkdirs()
+        }
+    }
 
     private val roomSyncEphemeralAdapter = moshi.adapter(RoomSyncEphemeral::class.java)
 
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 3e38cd78391a9c1849353eb33d8c7e93d790496f..7f80486c70dee176651556b260236d72a1ba8516 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))
                 }
     }
 
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/job/SyncService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/job/SyncService.kt
index 9480cc73f142ebdc50405cc3ec52e5e66adb7cd6..c17b31b910eef9f6c5f453538f89ea7b149e92c8 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/job/SyncService.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/job/SyncService.kt
@@ -105,9 +105,8 @@ abstract class SyncService : Service() {
                 }
             }
         }
-
-        // It's ok to be not sticky because we will explicitly start it again on the next alarm?
-        return START_NOT_STICKY
+        // Attempt to continue scheduling syncs after killed service is restarted
+        return START_REDELIVER_INTENT
     }
 
     override fun onDestroy() {
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/job/SyncWorker.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/job/SyncWorker.kt
index b81804feb53983556b1a5bd834dfe06c25519e36..41bb1a44a63589e19e9edfc72788cdfd1fae30b9 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/job/SyncWorker.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/job/SyncWorker.kt
@@ -121,9 +121,9 @@ internal class SyncWorker(context: Context,
                     .setBackoffCriteria(BackoffPolicy.LINEAR, WorkManagerProvider.BACKOFF_DELAY_MILLIS, TimeUnit.MILLISECONDS)
                     .setInitialDelay(delayInSeconds, TimeUnit.SECONDS)
                     .build()
-
+            // Avoid risking multiple chains of syncs by replacing the existing chain
             workManagerProvider.workManager
-                    .enqueueUniqueWork(BG_SYNC_WORK_NAME, ExistingWorkPolicy.APPEND_OR_REPLACE, workRequest)
+                    .enqueueUniqueWork(BG_SYNC_WORK_NAME, ExistingWorkPolicy.REPLACE, workRequest)
         }
 
         fun stopAnyBackgroundSync(workManagerProvider: WorkManagerProvider) {
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/util/BackgroundDetectionObserver.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/util/BackgroundDetectionObserver.kt
index 7cc00d023f4b93817f541d957802db953b803d29..a12587ac56563515a9393a80d76d9fd722a3400a 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/util/BackgroundDetectionObserver.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/util/BackgroundDetectionObserver.kt
@@ -29,7 +29,7 @@ import javax.inject.Inject
 @MatrixScope
 internal class BackgroundDetectionObserver @Inject constructor() : LifecycleObserver {
 
-    var isInBackground: Boolean = false
+    var isInBackground: Boolean = true
         private set
 
     private
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 0000000000000000000000000000000000000000..0e9c885394a1b43db82bb56bf72b64841b6f7896
--- /dev/null
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/util/Normalizer.kt
@@ -0,0 +1,27 @@
+/*
+ * 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
+ *
+ * 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)
+    }
+}
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 0000000000000000000000000000000000000000..3a0574e95a67326254d41c02e8112e2e10b069b0
--- /dev/null
+++ b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/database/RealmSessionStoreMigrationTest.kt
@@ -0,0 +1,29 @@
+/*
+ * 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.
+ * 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())
+    }
+}