diff --git a/.github/workflows/sanity_test.yml b/.github/workflows/sanity_test.yml
index 213c43b716..93e4686fe7 100644
--- a/.github/workflows/sanity_test.yml
+++ b/.github/workflows/sanity_test.yml
@@ -8,7 +8,7 @@ on:
# Enrich gradle.properties for CI/CD
env:
CI_GRADLE_ARG_PROPERTIES: >
- -Porg.gradle.jvmargs=-Xmx2g
+ -Porg.gradle.jvmargs=-Xmx4g
-Porg.gradle.parallel=false
jobs:
@@ -18,7 +18,7 @@ jobs:
strategy:
fail-fast: false
matrix:
- api-level: [ 29 ]
+ api-level: [ 28 ]
steps:
- uses: actions/checkout@v2
with:
@@ -57,9 +57,11 @@ jobs:
- name: Run sanity tests on API ${{ matrix.api-level }}
uses: reactivecircus/android-emulator-runner@v2
with:
- emulator-options: -no-snapshot-save -no-window -gpu swiftshader_indirect -noaudio -no-boot-anim -camera-back none
api-level: ${{ matrix.api-level }}
- profile: 24 # Pixel 5
+ arch: x86
+ profile: Nexus 5X
+ force-avd-creation: false
+ emulator-options: -no-snapshot-save -no-window -gpu swiftshader_indirect -noaudio -no-boot-anim -camera-back none
emulator-build: 7425822 # workaround to emulator bug: https://github.com/ReactiveCircus/android-emulator-runner/issues/160
script: |
adb root
@@ -67,12 +69,11 @@ jobs:
touch emulator.log
chmod 777 emulator.log
adb logcat >> emulator.log &
- ./gradlew $CI_GRADLE_ARG_PROPERTIES -PallWarningsAsErrors=false connectedGplayDebugAndroidTest -Pandroid.testInstrumentationRunnerArguments.class=im.vector.app.ui.UiAllScreensSanityTest || adb pull storage/emulated/0/Pictures/failure_screenshots && exit 1
- - name: Upload Failing Test Report Log
+ ./gradlew $CI_GRADLE_ARG_PROPERTIES -PallWarningsAsErrors=false connectedGplayDebugAndroidTest -Pandroid.testInstrumentationRunnerArguments.class=im.vector.app.ui.UiAllScreensSanityTest || adb pull storage/emulated/0/Pictures/failure_screenshots
+ - name: Upload Test Report Log
uses: actions/upload-artifact@v2
- if: failure()
with:
name: sanity-error-results
path: |
emulator.log
- failure_screenshots/
\ No newline at end of file
+ failure_screenshots/
diff --git a/changelog.d/4640.bugfix b/changelog.d/4640.bugfix
new file mode 100644
index 0000000000..f5fa5a5bde
--- /dev/null
+++ b/changelog.d/4640.bugfix
@@ -0,0 +1 @@
+Right align the notifications badge in the rooms list (and DMs) so that it's always in a consistent place on the screen.
\ No newline at end of file
diff --git a/changelog.d/5204.feature b/changelog.d/5204.feature
new file mode 100644
index 0000000000..a107342de7
--- /dev/null
+++ b/changelog.d/5204.feature
@@ -0,0 +1 @@
+Improve UI of reactions in timeline, including quick add reaction.
\ No newline at end of file
diff --git a/changelog.d/5209.misc b/changelog.d/5209.misc
new file mode 100644
index 0000000000..a238da9d22
--- /dev/null
+++ b/changelog.d/5209.misc
@@ -0,0 +1 @@
+Reduce verbosity of debug logging,
diff --git a/changelog.d/5210.misc b/changelog.d/5210.misc
new file mode 100644
index 0000000000..0b68e8b23a
--- /dev/null
+++ b/changelog.d/5210.misc
@@ -0,0 +1 @@
+Standardise emulator versions of GHA integration tests.
diff --git a/gradle.properties b/gradle.properties
index 5c99297107..6de52be607 100644
--- a/gradle.properties
+++ b/gradle.properties
@@ -8,7 +8,7 @@
# The setting is particularly useful for tweaking memory settings.
# Build Time Optimizations
-org.gradle.jvmargs=-Xmx3g -Xms512M -XX:MaxPermSize=2048m -XX:MaxMetaspaceSize=1g -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8 -XX:+UseParallelGC
+org.gradle.jvmargs=-Xmx4g -Xms512M -XX:MaxPermSize=2048m -XX:MaxMetaspaceSize=1g -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8 -XX:+UseParallelGC
org.gradle.configureondemand=true
org.gradle.parallel=true
org.gradle.vfs.watch=true
diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar
index 7454180f2a..41d9927a4d 100644
Binary files a/gradle/wrapper/gradle-wrapper.jar and b/gradle/wrapper/gradle-wrapper.jar differ
diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties
index ee6ba9a3ac..dcf5e2cb7b 100644
--- a/gradle/wrapper/gradle-wrapper.properties
+++ b/gradle/wrapper/gradle-wrapper.properties
@@ -1,6 +1,6 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
-distributionSha256Sum=c9490e938b221daf0094982288e4038deed954a3f12fb54cbf270ddf4e37d879
-distributionUrl=https\://services.gradle.org/distributions/gradle-7.3.3-all.zip
+distributionSha256Sum=cd5c2958a107ee7f0722004a12d0f8559b4564c34daad7df06cffd4d12a426d0
+distributionUrl=https\://services.gradle.org/distributions/gradle-7.4-all.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
diff --git a/library/ui-styles/src/main/res/values/colors.xml b/library/ui-styles/src/main/res/values/colors.xml
index ca6f6d3142..48ac48a8ca 100644
--- a/library/ui-styles/src/main/res/values/colors.xml
+++ b/library/ui-styles/src/main/res/values/colors.xml
@@ -58,9 +58,9 @@
- #FF61708B
- #FF61708B
- #FF61708B
+ @color/palette_gray_200
+ @color/palette_gray_250
+ @color/palette_gray_250
@android:color/white
diff --git a/library/ui-styles/src/main/res/values/styles_timeline.xml b/library/ui-styles/src/main/res/values/styles_timeline.xml
index 3bd3543de2..c86eeb8efb 100644
--- a/library/ui-styles/src/main/res/values/styles_timeline.xml
+++ b/library/ui-styles/src/main/res/values/styles_timeline.xml
@@ -23,4 +23,15 @@
- ?vctr_content_quinary
+
+
+
\ No newline at end of file
diff --git a/matrix-sdk-android/build.gradle b/matrix-sdk-android/build.gradle
index ead4d4f397..083c198720 100644
--- a/matrix-sdk-android/build.gradle
+++ b/matrix-sdk-android/build.gradle
@@ -169,7 +169,7 @@ dependencies {
implementation libs.apache.commonsImaging
// Phone number https://github.com/google/libphonenumber
- implementation 'com.googlecode.libphonenumber:libphonenumber:8.12.42'
+ implementation 'com.googlecode.libphonenumber:libphonenumber:8.12.43'
testImplementation libs.tests.junit
testImplementation 'org.robolectric:robolectric:4.7.3'
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/AuthModule.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/AuthModule.kt
index bb62dbbfe9..298e116199 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/AuthModule.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/AuthModule.kt
@@ -46,7 +46,9 @@ internal abstract class AuthModule {
@JvmStatic
@Provides
@AuthDatabase
- fun providesRealmConfiguration(context: Context, realmKeysUtils: RealmKeysUtils): RealmConfiguration {
+ fun providesRealmConfiguration(context: Context,
+ realmKeysUtils: RealmKeysUtils,
+ authRealmMigration: AuthRealmMigration): RealmConfiguration {
val old = File(context.filesDir, "matrix-sdk-auth")
if (old.exists()) {
old.renameTo(File(context.filesDir, "matrix-sdk-auth.realm"))
@@ -58,8 +60,8 @@ internal abstract class AuthModule {
}
.name("matrix-sdk-auth.realm")
.modules(AuthRealmModule())
- .schemaVersion(AuthRealmMigration.SCHEMA_VERSION)
- .migration(AuthRealmMigration)
+ .schemaVersion(authRealmMigration.schemaVersion)
+ .migration(authRealmMigration)
.build()
}
}
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/db/AuthRealmMigration.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/db/AuthRealmMigration.kt
index c2104690b3..59b6471a05 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/db/AuthRealmMigration.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/db/AuthRealmMigration.kt
@@ -16,102 +16,31 @@
package org.matrix.android.sdk.internal.auth.db
-import android.net.Uri
import io.realm.DynamicRealm
import io.realm.RealmMigration
-import org.matrix.android.sdk.api.auth.data.Credentials
-import org.matrix.android.sdk.api.auth.data.HomeServerConnectionConfig
-import org.matrix.android.sdk.api.auth.data.sessionId
-import org.matrix.android.sdk.internal.di.MoshiProvider
+import org.matrix.android.sdk.internal.auth.db.migration.MigrateAuthTo001
+import org.matrix.android.sdk.internal.auth.db.migration.MigrateAuthTo002
+import org.matrix.android.sdk.internal.auth.db.migration.MigrateAuthTo003
+import org.matrix.android.sdk.internal.auth.db.migration.MigrateAuthTo004
import timber.log.Timber
+import javax.inject.Inject
-internal object AuthRealmMigration : RealmMigration {
+internal class AuthRealmMigration @Inject constructor() : RealmMigration {
+ /**
+ * Forces all AuthRealmMigration instances to be equal
+ * Avoids Realm throwing when multiple instances of the migration are set
+ */
+ override fun equals(other: Any?) = other is AuthRealmMigration
+ override fun hashCode() = 4000
- // Current schema version
- const val SCHEMA_VERSION = 4L
+ val schemaVersion = 4L
override fun migrate(realm: DynamicRealm, oldVersion: Long, newVersion: Long) {
Timber.d("Migrating Auth Realm from $oldVersion to $newVersion")
- if (oldVersion <= 0) migrateTo1(realm)
- if (oldVersion <= 1) migrateTo2(realm)
- if (oldVersion <= 2) migrateTo3(realm)
- if (oldVersion <= 3) migrateTo4(realm)
- }
-
- private fun migrateTo1(realm: DynamicRealm) {
- Timber.d("Step 0 -> 1")
- Timber.d("Create PendingSessionEntity")
-
- realm.schema.create("PendingSessionEntity")
- .addField(PendingSessionEntityFields.HOME_SERVER_CONNECTION_CONFIG_JSON, String::class.java)
- .setRequired(PendingSessionEntityFields.HOME_SERVER_CONNECTION_CONFIG_JSON, true)
- .addField(PendingSessionEntityFields.CLIENT_SECRET, String::class.java)
- .setRequired(PendingSessionEntityFields.CLIENT_SECRET, true)
- .addField(PendingSessionEntityFields.SEND_ATTEMPT, Integer::class.java)
- .setRequired(PendingSessionEntityFields.SEND_ATTEMPT, true)
- .addField(PendingSessionEntityFields.RESET_PASSWORD_DATA_JSON, String::class.java)
- .addField(PendingSessionEntityFields.CURRENT_SESSION, String::class.java)
- .addField(PendingSessionEntityFields.IS_REGISTRATION_STARTED, Boolean::class.java)
- .addField(PendingSessionEntityFields.CURRENT_THREE_PID_DATA_JSON, String::class.java)
- }
-
- private fun migrateTo2(realm: DynamicRealm) {
- Timber.d("Step 1 -> 2")
- Timber.d("Add boolean isTokenValid in SessionParamsEntity, with value true")
-
- realm.schema.get("SessionParamsEntity")
- ?.addField(SessionParamsEntityFields.IS_TOKEN_VALID, Boolean::class.java)
- ?.transform { it.set(SessionParamsEntityFields.IS_TOKEN_VALID, true) }
- }
-
- private fun migrateTo3(realm: DynamicRealm) {
- Timber.d("Step 2 -> 3")
- Timber.d("Update SessionParamsEntity primary key, to allow several sessions with the same userId")
-
- realm.schema.get("SessionParamsEntity")
- ?.removePrimaryKey()
- ?.addField(SessionParamsEntityFields.SESSION_ID, String::class.java)
- ?.setRequired(SessionParamsEntityFields.SESSION_ID, true)
- ?.transform {
- val credentialsJson = it.getString(SessionParamsEntityFields.CREDENTIALS_JSON)
-
- val credentials = MoshiProvider.providesMoshi()
- .adapter(Credentials::class.java)
- .fromJson(credentialsJson)
-
- it.set(SessionParamsEntityFields.SESSION_ID, credentials!!.sessionId())
- }
- ?.addPrimaryKey(SessionParamsEntityFields.SESSION_ID)
- }
-
- private fun migrateTo4(realm: DynamicRealm) {
- Timber.d("Step 3 -> 4")
- Timber.d("Update SessionParamsEntity to add HomeServerConnectionConfig.homeServerUriBase value")
-
- val adapter = MoshiProvider.providesMoshi()
- .adapter(HomeServerConnectionConfig::class.java)
-
- realm.schema.get("SessionParamsEntity")
- ?.transform {
- val homeserverConnectionConfigJson = it.getString(SessionParamsEntityFields.HOME_SERVER_CONNECTION_CONFIG_JSON)
-
- val homeserverConnectionConfig = adapter
- .fromJson(homeserverConnectionConfigJson)
-
- val homeserverUrl = homeserverConnectionConfig?.homeServerUri?.toString()
- // Special case for matrix.org. Old session may use "https://matrix.org", newer one may use
- // "https://matrix-client.matrix.org". So fix that here
- val alteredHomeserverConnectionConfig =
- if (homeserverUrl == "https://matrix.org" || homeserverUrl == "https://matrix-client.matrix.org") {
- homeserverConnectionConfig.copy(
- homeServerUri = Uri.parse("https://matrix.org"),
- homeServerUriBase = Uri.parse("https://matrix-client.matrix.org")
- )
- } else {
- homeserverConnectionConfig
- }
- it.set(SessionParamsEntityFields.HOME_SERVER_CONNECTION_CONFIG_JSON, adapter.toJson(alteredHomeserverConnectionConfig))
- }
+ if (oldVersion < 1) MigrateAuthTo001(realm).perform()
+ if (oldVersion < 2) MigrateAuthTo002(realm).perform()
+ if (oldVersion < 3) MigrateAuthTo003(realm).perform()
+ if (oldVersion < 4) MigrateAuthTo004(realm).perform()
}
}
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/db/migration/MigrateAuthTo001.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/db/migration/MigrateAuthTo001.kt
new file mode 100644
index 0000000000..627f4e16bc
--- /dev/null
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/db/migration/MigrateAuthTo001.kt
@@ -0,0 +1,41 @@
+/*
+ * Copyright (c) 2022 The Matrix.org Foundation C.I.C.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.matrix.android.sdk.internal.auth.db.migration
+
+import io.realm.DynamicRealm
+import org.matrix.android.sdk.internal.auth.db.PendingSessionEntityFields
+import org.matrix.android.sdk.internal.util.database.RealmMigrator
+import timber.log.Timber
+
+class MigrateAuthTo001(realm: DynamicRealm) : RealmMigrator(realm, 1) {
+
+ override fun doMigrate(realm: DynamicRealm) {
+ Timber.d("Create PendingSessionEntity")
+
+ realm.schema.create("PendingSessionEntity")
+ .addField(PendingSessionEntityFields.HOME_SERVER_CONNECTION_CONFIG_JSON, String::class.java)
+ .setRequired(PendingSessionEntityFields.HOME_SERVER_CONNECTION_CONFIG_JSON, true)
+ .addField(PendingSessionEntityFields.CLIENT_SECRET, String::class.java)
+ .setRequired(PendingSessionEntityFields.CLIENT_SECRET, true)
+ .addField(PendingSessionEntityFields.SEND_ATTEMPT, Integer::class.java)
+ .setRequired(PendingSessionEntityFields.SEND_ATTEMPT, true)
+ .addField(PendingSessionEntityFields.RESET_PASSWORD_DATA_JSON, String::class.java)
+ .addField(PendingSessionEntityFields.CURRENT_SESSION, String::class.java)
+ .addField(PendingSessionEntityFields.IS_REGISTRATION_STARTED, Boolean::class.java)
+ .addField(PendingSessionEntityFields.CURRENT_THREE_PID_DATA_JSON, String::class.java)
+ }
+}
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/db/migration/MigrateAuthTo002.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/db/migration/MigrateAuthTo002.kt
new file mode 100644
index 0000000000..6b133f8580
--- /dev/null
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/db/migration/MigrateAuthTo002.kt
@@ -0,0 +1,33 @@
+/*
+ * Copyright (c) 2022 The Matrix.org Foundation C.I.C.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.matrix.android.sdk.internal.auth.db.migration
+
+import io.realm.DynamicRealm
+import org.matrix.android.sdk.internal.auth.db.SessionParamsEntityFields
+import org.matrix.android.sdk.internal.util.database.RealmMigrator
+import timber.log.Timber
+
+class MigrateAuthTo002(realm: DynamicRealm) : RealmMigrator(realm, 2) {
+
+ override fun doMigrate(realm: DynamicRealm) {
+ Timber.d("Add boolean isTokenValid in SessionParamsEntity, with value true")
+
+ realm.schema.get("SessionParamsEntity")
+ ?.addField(SessionParamsEntityFields.IS_TOKEN_VALID, Boolean::class.java)
+ ?.transform { it.set(SessionParamsEntityFields.IS_TOKEN_VALID, true) }
+ }
+}
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/db/migration/MigrateAuthTo003.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/db/migration/MigrateAuthTo003.kt
new file mode 100644
index 0000000000..9319ec9987
--- /dev/null
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/db/migration/MigrateAuthTo003.kt
@@ -0,0 +1,47 @@
+/*
+ * Copyright (c) 2022 The Matrix.org Foundation C.I.C.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.matrix.android.sdk.internal.auth.db.migration
+
+import io.realm.DynamicRealm
+import org.matrix.android.sdk.api.auth.data.Credentials
+import org.matrix.android.sdk.api.auth.data.sessionId
+import org.matrix.android.sdk.internal.auth.db.SessionParamsEntityFields
+import org.matrix.android.sdk.internal.di.MoshiProvider
+import org.matrix.android.sdk.internal.util.database.RealmMigrator
+import timber.log.Timber
+
+class MigrateAuthTo003(realm: DynamicRealm) : RealmMigrator(realm, 3) {
+
+ override fun doMigrate(realm: DynamicRealm) {
+ Timber.d("Update SessionParamsEntity primary key, to allow several sessions with the same userId")
+
+ realm.schema.get("SessionParamsEntity")
+ ?.removePrimaryKey()
+ ?.addField(SessionParamsEntityFields.SESSION_ID, String::class.java)
+ ?.setRequired(SessionParamsEntityFields.SESSION_ID, true)
+ ?.transform {
+ val credentialsJson = it.getString(SessionParamsEntityFields.CREDENTIALS_JSON)
+
+ val credentials = MoshiProvider.providesMoshi()
+ .adapter(Credentials::class.java)
+ .fromJson(credentialsJson)
+
+ it.set(SessionParamsEntityFields.SESSION_ID, credentials!!.sessionId())
+ }
+ ?.addPrimaryKey(SessionParamsEntityFields.SESSION_ID)
+ }
+}
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/db/migration/MigrateAuthTo004.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/db/migration/MigrateAuthTo004.kt
new file mode 100644
index 0000000000..4a9b9022d5
--- /dev/null
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/db/migration/MigrateAuthTo004.kt
@@ -0,0 +1,57 @@
+/*
+ * Copyright (c) 2022 The Matrix.org Foundation C.I.C.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.matrix.android.sdk.internal.auth.db.migration
+
+import android.net.Uri
+import io.realm.DynamicRealm
+import org.matrix.android.sdk.api.auth.data.HomeServerConnectionConfig
+import org.matrix.android.sdk.internal.auth.db.SessionParamsEntityFields
+import org.matrix.android.sdk.internal.di.MoshiProvider
+import org.matrix.android.sdk.internal.util.database.RealmMigrator
+import timber.log.Timber
+
+class MigrateAuthTo004(realm: DynamicRealm) : RealmMigrator(realm, 4) {
+
+ override fun doMigrate(realm: DynamicRealm) {
+ Timber.d("Update SessionParamsEntity to add HomeServerConnectionConfig.homeServerUriBase value")
+
+ val adapter = MoshiProvider.providesMoshi()
+ .adapter(HomeServerConnectionConfig::class.java)
+
+ realm.schema.get("SessionParamsEntity")
+ ?.transform {
+ val homeserverConnectionConfigJson = it.getString(SessionParamsEntityFields.HOME_SERVER_CONNECTION_CONFIG_JSON)
+
+ val homeserverConnectionConfig = adapter
+ .fromJson(homeserverConnectionConfigJson)
+
+ val homeserverUrl = homeserverConnectionConfig?.homeServerUri?.toString()
+ // Special case for matrix.org. Old session may use "https://matrix.org", newer one may use
+ // "https://matrix-client.matrix.org". So fix that here
+ val alteredHomeserverConnectionConfig =
+ if (homeserverUrl == "https://matrix.org" || homeserverUrl == "https://matrix-client.matrix.org") {
+ homeserverConnectionConfig.copy(
+ homeServerUri = Uri.parse("https://matrix.org"),
+ homeServerUriBase = Uri.parse("https://matrix-client.matrix.org")
+ )
+ } else {
+ homeserverConnectionConfig
+ }
+ it.set(SessionParamsEntityFields.HOME_SERVER_CONNECTION_CONFIG_JSON, adapter.toJson(alteredHomeserverConnectionConfig))
+ }
+ }
+}
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/CryptoModule.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/CryptoModule.kt
index fe388b44e2..3130a6382f 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/CryptoModule.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/CryptoModule.kt
@@ -112,7 +112,8 @@ internal abstract class CryptoModule {
@SessionScope
fun providesRealmConfiguration(@SessionFilesDirectory directory: File,
@UserMd5 userMd5: String,
- realmKeysUtils: RealmKeysUtils): RealmConfiguration {
+ realmKeysUtils: RealmKeysUtils,
+ realmCryptoStoreMigration: RealmCryptoStoreMigration): RealmConfiguration {
return RealmConfiguration.Builder()
.directory(directory)
.apply {
@@ -121,8 +122,8 @@ internal abstract class CryptoModule {
.name("crypto_store.realm")
.modules(RealmCryptoStoreModule())
.allowWritesOnUiThread(true)
- .schemaVersion(RealmCryptoStoreMigration.CRYPTO_STORE_SCHEMA_VERSION)
- .migration(RealmCryptoStoreMigration)
+ .schemaVersion(realmCryptoStoreMigration.schemaVersion)
+ .migration(realmCryptoStoreMigration)
.build()
}
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/RealmCryptoStoreMigration.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/RealmCryptoStoreMigration.kt
index f73cbaf480..685b2d2967 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/RealmCryptoStoreMigration.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/RealmCryptoStoreMigration.kt
@@ -16,560 +16,54 @@
package org.matrix.android.sdk.internal.crypto.store.db
-import com.squareup.moshi.Moshi
-import com.squareup.moshi.Types
import io.realm.DynamicRealm
import io.realm.RealmMigration
-import io.realm.RealmObjectSchema
-import org.matrix.android.sdk.api.extensions.tryOrNull
-import org.matrix.android.sdk.api.util.JsonDict
-import org.matrix.android.sdk.internal.crypto.model.MXDeviceInfo
-import org.matrix.android.sdk.internal.crypto.model.OlmInboundGroupSessionWrapper
-import org.matrix.android.sdk.internal.crypto.model.OlmInboundGroupSessionWrapper2
-import org.matrix.android.sdk.internal.crypto.store.db.mapper.CrossSigningKeysMapper
-import org.matrix.android.sdk.internal.crypto.store.db.model.CrossSigningInfoEntityFields
-import org.matrix.android.sdk.internal.crypto.store.db.model.CryptoMetadataEntityFields
-import org.matrix.android.sdk.internal.crypto.store.db.model.CryptoRoomEntityFields
-import org.matrix.android.sdk.internal.crypto.store.db.model.DeviceInfoEntityFields
-import org.matrix.android.sdk.internal.crypto.store.db.model.GossipingEventEntityFields
-import org.matrix.android.sdk.internal.crypto.store.db.model.IncomingGossipingRequestEntityFields
-import org.matrix.android.sdk.internal.crypto.store.db.model.KeyInfoEntityFields
-import org.matrix.android.sdk.internal.crypto.store.db.model.KeysBackupDataEntityFields
-import org.matrix.android.sdk.internal.crypto.store.db.model.MyDeviceLastSeenInfoEntityFields
-import org.matrix.android.sdk.internal.crypto.store.db.model.OlmInboundGroupSessionEntityFields
-import org.matrix.android.sdk.internal.crypto.store.db.model.OlmSessionEntityFields
-import org.matrix.android.sdk.internal.crypto.store.db.model.OutboundGroupSessionInfoEntityFields
-import org.matrix.android.sdk.internal.crypto.store.db.model.OutgoingGossipingRequestEntityFields
-import org.matrix.android.sdk.internal.crypto.store.db.model.SharedSessionEntityFields
-import org.matrix.android.sdk.internal.crypto.store.db.model.TrustLevelEntityFields
-import org.matrix.android.sdk.internal.crypto.store.db.model.UserEntityFields
-import org.matrix.android.sdk.internal.crypto.store.db.model.WithHeldSessionEntityFields
-import org.matrix.android.sdk.internal.di.MoshiProvider
-import org.matrix.android.sdk.internal.di.SerializeNulls
-import org.matrix.androidsdk.crypto.data.MXOlmInboundGroupSession2
+import org.matrix.android.sdk.internal.crypto.store.db.migration.MigrateCryptoTo001Legacy
+import org.matrix.android.sdk.internal.crypto.store.db.migration.MigrateCryptoTo002Legacy
+import org.matrix.android.sdk.internal.crypto.store.db.migration.MigrateCryptoTo003RiotX
+import org.matrix.android.sdk.internal.crypto.store.db.migration.MigrateCryptoTo004
+import org.matrix.android.sdk.internal.crypto.store.db.migration.MigrateCryptoTo005
+import org.matrix.android.sdk.internal.crypto.store.db.migration.MigrateCryptoTo006
+import org.matrix.android.sdk.internal.crypto.store.db.migration.MigrateCryptoTo007
+import org.matrix.android.sdk.internal.crypto.store.db.migration.MigrateCryptoTo008
+import org.matrix.android.sdk.internal.crypto.store.db.migration.MigrateCryptoTo009
+import org.matrix.android.sdk.internal.crypto.store.db.migration.MigrateCryptoTo010
+import org.matrix.android.sdk.internal.crypto.store.db.migration.MigrateCryptoTo011
+import org.matrix.android.sdk.internal.crypto.store.db.migration.MigrateCryptoTo012
+import org.matrix.android.sdk.internal.crypto.store.db.migration.MigrateCryptoTo013
+import org.matrix.android.sdk.internal.crypto.store.db.migration.MigrateCryptoTo014
import timber.log.Timber
-import org.matrix.androidsdk.crypto.data.MXDeviceInfo as LegacyMXDeviceInfo
+import javax.inject.Inject
-internal object RealmCryptoStoreMigration : RealmMigration {
+internal class RealmCryptoStoreMigration @Inject constructor() : RealmMigration {
+ /**
+ * Forces all RealmCryptoStoreMigration instances to be equal
+ * Avoids Realm throwing when multiple instances of the migration are set
+ */
+ override fun equals(other: Any?) = other is RealmCryptoStoreMigration
+ override fun hashCode() = 5000
// 0, 1, 2: legacy Riot-Android
// 3: migrate to RiotX schema
// 4, 5, 6, 7, 8, 9: migrations from RiotX (which was previously 1, 2, 3, 4, 5, 6)
- const val CRYPTO_STORE_SCHEMA_VERSION = 14L
-
- private fun RealmObjectSchema.addFieldIfNotExists(fieldName: String, fieldType: Class<*>): RealmObjectSchema {
- if (!hasField(fieldName)) {
- addField(fieldName, fieldType)
- }
- return this
- }
-
- private fun RealmObjectSchema.removeFieldIfExists(fieldName: String): RealmObjectSchema {
- if (hasField(fieldName)) {
- removeField(fieldName)
- }
- return this
- }
-
- private fun RealmObjectSchema.setRequiredIfNotAlready(fieldName: String, isRequired: Boolean): RealmObjectSchema {
- if (isRequired != isRequired(fieldName)) {
- setRequired(fieldName, isRequired)
- }
- return this
- }
+ val schemaVersion = 14L
override fun migrate(realm: DynamicRealm, oldVersion: Long, newVersion: Long) {
- Timber.v("Migrating Realm Crypto from $oldVersion to $newVersion")
+ Timber.d("Migrating Realm Crypto from $oldVersion to $newVersion")
- if (oldVersion <= 0) migrateTo1Legacy(realm)
- if (oldVersion <= 1) migrateTo2Legacy(realm)
- if (oldVersion <= 2) migrateTo3RiotX(realm)
- if (oldVersion <= 3) migrateTo4(realm)
- if (oldVersion <= 4) migrateTo5(realm)
- if (oldVersion <= 5) migrateTo6(realm)
- if (oldVersion <= 6) migrateTo7(realm)
- if (oldVersion <= 7) migrateTo8(realm)
- if (oldVersion <= 8) migrateTo9(realm)
- if (oldVersion <= 9) migrateTo10(realm)
- if (oldVersion <= 10) migrateTo11(realm)
- if (oldVersion <= 11) migrateTo12(realm)
- if (oldVersion <= 12) migrateTo13(realm)
- if (oldVersion <= 13) migrateTo14(realm)
- }
-
- private fun migrateTo1Legacy(realm: DynamicRealm) {
- Timber.d("Step 0 -> 1")
- Timber.d("Add field lastReceivedMessageTs (Long) and set the value to 0")
-
- realm.schema.get("OlmSessionEntity")
- ?.addField(OlmSessionEntityFields.LAST_RECEIVED_MESSAGE_TS, Long::class.java)
- ?.transform {
- it.setLong(OlmSessionEntityFields.LAST_RECEIVED_MESSAGE_TS, 0)
- }
- }
-
- private fun migrateTo2Legacy(realm: DynamicRealm) {
- Timber.d("Step 1 -> 2")
- Timber.d("Update IncomingRoomKeyRequestEntity format: requestBodyString field is exploded into several fields")
-
- realm.schema.get("IncomingRoomKeyRequestEntity")
- ?.addFieldIfNotExists("requestBodyAlgorithm", String::class.java)
- ?.addFieldIfNotExists("requestBodyRoomId", String::class.java)
- ?.addFieldIfNotExists("requestBodySenderKey", String::class.java)
- ?.addFieldIfNotExists("requestBodySessionId", String::class.java)
- ?.transform { dynamicObject ->
- try {
- val requestBodyString = dynamicObject.getString("requestBodyString")
- // It was a map before
- val map: Map? = deserializeFromRealm(requestBodyString)
-
- map?.let {
- dynamicObject.setString("requestBodyAlgorithm", it["algorithm"])
- dynamicObject.setString("requestBodyRoomId", it["room_id"])
- dynamicObject.setString("requestBodySenderKey", it["sender_key"])
- dynamicObject.setString("requestBodySessionId", it["session_id"])
- }
- } catch (e: Exception) {
- Timber.e(e, "Error")
- }
- }
- ?.removeFieldIfExists("requestBodyString")
-
- Timber.d("Update IncomingRoomKeyRequestEntity format: requestBodyString field is exploded into several fields")
-
- realm.schema.get("OutgoingRoomKeyRequestEntity")
- ?.addFieldIfNotExists("requestBodyAlgorithm", String::class.java)
- ?.addFieldIfNotExists("requestBodyRoomId", String::class.java)
- ?.addFieldIfNotExists("requestBodySenderKey", String::class.java)
- ?.addFieldIfNotExists("requestBodySessionId", String::class.java)
- ?.transform { dynamicObject ->
- try {
- val requestBodyString = dynamicObject.getString("requestBodyString")
- // It was a map before
- val map: Map? = deserializeFromRealm(requestBodyString)
-
- map?.let {
- dynamicObject.setString("requestBodyAlgorithm", it["algorithm"])
- dynamicObject.setString("requestBodyRoomId", it["room_id"])
- dynamicObject.setString("requestBodySenderKey", it["sender_key"])
- dynamicObject.setString("requestBodySessionId", it["session_id"])
- }
- } catch (e: Exception) {
- Timber.e(e, "Error")
- }
- }
- ?.removeFieldIfExists("requestBodyString")
-
- Timber.d("Create KeysBackupDataEntity")
-
- if (!realm.schema.contains("KeysBackupDataEntity")) {
- realm.schema.create("KeysBackupDataEntity")
- .addField(KeysBackupDataEntityFields.PRIMARY_KEY, Integer::class.java)
- .addPrimaryKey(KeysBackupDataEntityFields.PRIMARY_KEY)
- .setRequired(KeysBackupDataEntityFields.PRIMARY_KEY, true)
- .addField(KeysBackupDataEntityFields.BACKUP_LAST_SERVER_HASH, String::class.java)
- .addField(KeysBackupDataEntityFields.BACKUP_LAST_SERVER_NUMBER_OF_KEYS, Integer::class.java)
- }
- }
-
- private fun migrateTo3RiotX(realm: DynamicRealm) {
- Timber.d("Step 2 -> 3")
- Timber.d("Migrate to RiotX model")
-
- realm.schema.get("CryptoRoomEntity")
- ?.addFieldIfNotExists(CryptoRoomEntityFields.SHOULD_ENCRYPT_FOR_INVITED_MEMBERS, Boolean::class.java)
- ?.setRequiredIfNotAlready(CryptoRoomEntityFields.SHOULD_ENCRYPT_FOR_INVITED_MEMBERS, false)
-
- // Convert format of MXDeviceInfo, package has to be the same.
- realm.schema.get("DeviceInfoEntity")
- ?.transform { obj ->
- try {
- val oldSerializedData = obj.getString("deviceInfoData")
- deserializeFromRealm(oldSerializedData)?.let { legacyMxDeviceInfo ->
- val newMxDeviceInfo = MXDeviceInfo(
- deviceId = legacyMxDeviceInfo.deviceId,
- userId = legacyMxDeviceInfo.userId,
- algorithms = legacyMxDeviceInfo.algorithms,
- keys = legacyMxDeviceInfo.keys,
- signatures = legacyMxDeviceInfo.signatures,
- unsigned = legacyMxDeviceInfo.unsigned,
- verified = legacyMxDeviceInfo.mVerified
- )
-
- obj.setString("deviceInfoData", serializeForRealm(newMxDeviceInfo))
- }
- } catch (e: Exception) {
- Timber.e(e, "Error")
- }
- }
-
- // Convert MXOlmInboundGroupSession2 to OlmInboundGroupSessionWrapper
- realm.schema.get("OlmInboundGroupSessionEntity")
- ?.transform { obj ->
- try {
- val oldSerializedData = obj.getString("olmInboundGroupSessionData")
- deserializeFromRealm(oldSerializedData)?.let { mxOlmInboundGroupSession2 ->
- val sessionKey = mxOlmInboundGroupSession2.mSession.sessionIdentifier()
- val newOlmInboundGroupSessionWrapper = OlmInboundGroupSessionWrapper(sessionKey, false)
- .apply {
- olmInboundGroupSession = mxOlmInboundGroupSession2.mSession
- roomId = mxOlmInboundGroupSession2.mRoomId
- senderKey = mxOlmInboundGroupSession2.mSenderKey
- keysClaimed = mxOlmInboundGroupSession2.mKeysClaimed
- forwardingCurve25519KeyChain = mxOlmInboundGroupSession2.mForwardingCurve25519KeyChain
- }
-
- obj.setString("olmInboundGroupSessionData", serializeForRealm(newOlmInboundGroupSessionWrapper))
- }
- } catch (e: Exception) {
- Timber.e(e, "Error")
- }
- }
- }
-
- // Version 4L added Cross Signing info persistence
- private fun migrateTo4(realm: DynamicRealm) {
- Timber.d("Step 3 -> 4")
-
- if (realm.schema.contains("TrustLevelEntity")) {
- Timber.d("Skipping Step 3 -> 4 because entities already exist")
- return
- }
-
- Timber.d("Create KeyInfoEntity")
- val trustLevelEntityEntitySchema = realm.schema.create("TrustLevelEntity")
- .addField(TrustLevelEntityFields.CROSS_SIGNED_VERIFIED, Boolean::class.java)
- .setNullable(TrustLevelEntityFields.CROSS_SIGNED_VERIFIED, true)
- .addField(TrustLevelEntityFields.LOCALLY_VERIFIED, Boolean::class.java)
- .setNullable(TrustLevelEntityFields.LOCALLY_VERIFIED, true)
-
- val keyInfoEntitySchema = realm.schema.create("KeyInfoEntity")
- .addField(KeyInfoEntityFields.PUBLIC_KEY_BASE64, String::class.java)
- .addField(KeyInfoEntityFields.SIGNATURES, String::class.java)
- .addRealmListField(KeyInfoEntityFields.USAGES.`$`, String::class.java)
- .addRealmObjectField(KeyInfoEntityFields.TRUST_LEVEL_ENTITY.`$`, trustLevelEntityEntitySchema)
-
- Timber.d("Create CrossSigningInfoEntity")
-
- val crossSigningInfoSchema = realm.schema.create("CrossSigningInfoEntity")
- .addField(CrossSigningInfoEntityFields.USER_ID, String::class.java)
- .addPrimaryKey(CrossSigningInfoEntityFields.USER_ID)
- .addRealmListField(CrossSigningInfoEntityFields.CROSS_SIGNING_KEYS.`$`, keyInfoEntitySchema)
-
- Timber.d("Updating UserEntity table")
- realm.schema.get("UserEntity")
- ?.addRealmObjectField(UserEntityFields.CROSS_SIGNING_INFO_ENTITY.`$`, crossSigningInfoSchema)
-
- Timber.d("Updating CryptoMetadataEntity table")
- realm.schema.get("CryptoMetadataEntity")
- ?.addField(CryptoMetadataEntityFields.X_SIGN_MASTER_PRIVATE_KEY, String::class.java)
- ?.addField(CryptoMetadataEntityFields.X_SIGN_USER_PRIVATE_KEY, String::class.java)
- ?.addField(CryptoMetadataEntityFields.X_SIGN_SELF_SIGNED_PRIVATE_KEY, String::class.java)
-
- val moshi = Moshi.Builder().add(SerializeNulls.JSON_ADAPTER_FACTORY).build()
- val listMigrationAdapter = moshi.adapter>(Types.newParameterizedType(
- List::class.java,
- String::class.java,
- Any::class.java
- ))
- val mapMigrationAdapter = moshi.adapter(Types.newParameterizedType(
- Map::class.java,
- String::class.java,
- Any::class.java
- ))
-
- realm.schema.get("DeviceInfoEntity")
- ?.addField(DeviceInfoEntityFields.USER_ID, String::class.java)
- ?.addField(DeviceInfoEntityFields.ALGORITHM_LIST_JSON, String::class.java)
- ?.addField(DeviceInfoEntityFields.KEYS_MAP_JSON, String::class.java)
- ?.addField(DeviceInfoEntityFields.SIGNATURE_MAP_JSON, String::class.java)
- ?.addField(DeviceInfoEntityFields.UNSIGNED_MAP_JSON, String::class.java)
- ?.addField(DeviceInfoEntityFields.IS_BLOCKED, Boolean::class.java)
- ?.setNullable(DeviceInfoEntityFields.IS_BLOCKED, true)
- ?.addRealmObjectField(DeviceInfoEntityFields.TRUST_LEVEL_ENTITY.`$`, trustLevelEntityEntitySchema)
- ?.transform { obj ->
-
- try {
- val oldSerializedData = obj.getString("deviceInfoData")
- deserializeFromRealm(oldSerializedData)?.let { oldDevice ->
-
- val trustLevel = realm.createObject("TrustLevelEntity")
- when (oldDevice.verified) {
- MXDeviceInfo.DEVICE_VERIFICATION_UNKNOWN -> {
- obj.setNull(DeviceInfoEntityFields.TRUST_LEVEL_ENTITY.`$`)
- }
- MXDeviceInfo.DEVICE_VERIFICATION_BLOCKED -> {
- trustLevel.setNull(TrustLevelEntityFields.LOCALLY_VERIFIED)
- trustLevel.setNull(TrustLevelEntityFields.CROSS_SIGNED_VERIFIED)
- obj.setBoolean(DeviceInfoEntityFields.IS_BLOCKED, oldDevice.isBlocked)
- obj.setObject(DeviceInfoEntityFields.TRUST_LEVEL_ENTITY.`$`, trustLevel)
- }
- MXDeviceInfo.DEVICE_VERIFICATION_UNVERIFIED -> {
- trustLevel.setBoolean(TrustLevelEntityFields.LOCALLY_VERIFIED, false)
- trustLevel.setBoolean(TrustLevelEntityFields.CROSS_SIGNED_VERIFIED, false)
- obj.setObject(DeviceInfoEntityFields.TRUST_LEVEL_ENTITY.`$`, trustLevel)
- }
- MXDeviceInfo.DEVICE_VERIFICATION_VERIFIED -> {
- trustLevel.setBoolean(TrustLevelEntityFields.LOCALLY_VERIFIED, true)
- trustLevel.setBoolean(TrustLevelEntityFields.CROSS_SIGNED_VERIFIED, false)
- obj.setObject(DeviceInfoEntityFields.TRUST_LEVEL_ENTITY.`$`, trustLevel)
- }
- }
-
- obj.setString(DeviceInfoEntityFields.USER_ID, oldDevice.userId)
- obj.setString(DeviceInfoEntityFields.IDENTITY_KEY, oldDevice.identityKey())
- obj.setString(DeviceInfoEntityFields.ALGORITHM_LIST_JSON, listMigrationAdapter.toJson(oldDevice.algorithms))
- obj.setString(DeviceInfoEntityFields.KEYS_MAP_JSON, mapMigrationAdapter.toJson(oldDevice.keys))
- obj.setString(DeviceInfoEntityFields.SIGNATURE_MAP_JSON, mapMigrationAdapter.toJson(oldDevice.signatures))
- obj.setString(DeviceInfoEntityFields.UNSIGNED_MAP_JSON, mapMigrationAdapter.toJson(oldDevice.unsigned))
- }
- } catch (failure: Throwable) {
- Timber.w(failure, "Crypto Data base migration error")
- // an unfortunate refactor did modify that class, making deserialization failing
- // so we just skip and ignore..
- }
- }
- ?.removeField("deviceInfoData")
- }
-
- private fun migrateTo5(realm: DynamicRealm) {
- Timber.d("Step 4 -> 5")
- realm.schema.remove("OutgoingRoomKeyRequestEntity")
- realm.schema.remove("IncomingRoomKeyRequestEntity")
-
- // Not need to migrate existing request, just start fresh?
-
- realm.schema.create("GossipingEventEntity")
- .addField(GossipingEventEntityFields.TYPE, String::class.java)
- .addIndex(GossipingEventEntityFields.TYPE)
- .addField(GossipingEventEntityFields.CONTENT, String::class.java)
- .addField(GossipingEventEntityFields.SENDER, String::class.java)
- .addIndex(GossipingEventEntityFields.SENDER)
- .addField(GossipingEventEntityFields.DECRYPTION_RESULT_JSON, String::class.java)
- .addField(GossipingEventEntityFields.DECRYPTION_ERROR_CODE, String::class.java)
- .addField(GossipingEventEntityFields.AGE_LOCAL_TS, Long::class.java)
- .setNullable(GossipingEventEntityFields.AGE_LOCAL_TS, true)
- .addField(GossipingEventEntityFields.SEND_STATE_STR, String::class.java)
-
- realm.schema.create("IncomingGossipingRequestEntity")
- .addField(IncomingGossipingRequestEntityFields.REQUEST_ID, String::class.java)
- .addIndex(IncomingGossipingRequestEntityFields.REQUEST_ID)
- .addField(IncomingGossipingRequestEntityFields.TYPE_STR, String::class.java)
- .addIndex(IncomingGossipingRequestEntityFields.TYPE_STR)
- .addField(IncomingGossipingRequestEntityFields.OTHER_USER_ID, String::class.java)
- .addField(IncomingGossipingRequestEntityFields.REQUESTED_INFO_STR, String::class.java)
- .addField(IncomingGossipingRequestEntityFields.OTHER_DEVICE_ID, String::class.java)
- .addField(IncomingGossipingRequestEntityFields.REQUEST_STATE_STR, String::class.java)
- .addField(IncomingGossipingRequestEntityFields.LOCAL_CREATION_TIMESTAMP, Long::class.java)
- .setNullable(IncomingGossipingRequestEntityFields.LOCAL_CREATION_TIMESTAMP, true)
-
- realm.schema.create("OutgoingGossipingRequestEntity")
- .addField(OutgoingGossipingRequestEntityFields.REQUEST_ID, String::class.java)
- .addIndex(OutgoingGossipingRequestEntityFields.REQUEST_ID)
- .addField(OutgoingGossipingRequestEntityFields.RECIPIENTS_DATA, String::class.java)
- .addField(OutgoingGossipingRequestEntityFields.REQUESTED_INFO_STR, String::class.java)
- .addField(OutgoingGossipingRequestEntityFields.TYPE_STR, String::class.java)
- .addIndex(OutgoingGossipingRequestEntityFields.TYPE_STR)
- .addField(OutgoingGossipingRequestEntityFields.REQUEST_STATE_STR, String::class.java)
- }
-
- private fun migrateTo6(realm: DynamicRealm) {
- Timber.d("Step 5 -> 6")
- Timber.d("Updating CryptoMetadataEntity table")
- realm.schema.get("CryptoMetadataEntity")
- ?.addField(CryptoMetadataEntityFields.KEY_BACKUP_RECOVERY_KEY, String::class.java)
- ?.addField(CryptoMetadataEntityFields.KEY_BACKUP_RECOVERY_KEY_VERSION, String::class.java)
- }
-
- private fun migrateTo7(realm: DynamicRealm) {
- Timber.d("Step 6 -> 7")
- Timber.d("Updating KeyInfoEntity table")
- val crossSigningKeysMapper = CrossSigningKeysMapper(MoshiProvider.providesMoshi())
-
- val keyInfoEntities = realm.where("KeyInfoEntity").findAll()
- try {
- keyInfoEntities.forEach {
- val stringSignatures = it.getString(KeyInfoEntityFields.SIGNATURES)
- val objectSignatures: Map>? = deserializeFromRealm(stringSignatures)
- val jsonSignatures = crossSigningKeysMapper.serializeSignatures(objectSignatures)
- it.setString(KeyInfoEntityFields.SIGNATURES, jsonSignatures)
- }
- } catch (failure: Throwable) {
- }
-
- // Migrate frozen classes
- val inboundGroupSessions = realm.where("OlmInboundGroupSessionEntity").findAll()
- inboundGroupSessions.forEach { dynamicObject ->
- dynamicObject.getString(OlmInboundGroupSessionEntityFields.OLM_INBOUND_GROUP_SESSION_DATA)?.let { serializedObject ->
- try {
- deserializeFromRealm(serializedObject)?.let { oldFormat ->
- val newFormat = oldFormat.exportKeys()?.let {
- OlmInboundGroupSessionWrapper2(it)
- }
- dynamicObject.setString(OlmInboundGroupSessionEntityFields.OLM_INBOUND_GROUP_SESSION_DATA, serializeForRealm(newFormat))
- }
- } catch (failure: Throwable) {
- Timber.e(failure, "## OlmInboundGroupSessionEntity migration failed")
- }
- }
- }
- }
-
- private fun migrateTo8(realm: DynamicRealm) {
- Timber.d("Step 7 -> 8")
- realm.schema.create("MyDeviceLastSeenInfoEntity")
- .addField(MyDeviceLastSeenInfoEntityFields.DEVICE_ID, String::class.java)
- .addPrimaryKey(MyDeviceLastSeenInfoEntityFields.DEVICE_ID)
- .addField(MyDeviceLastSeenInfoEntityFields.DISPLAY_NAME, String::class.java)
- .addField(MyDeviceLastSeenInfoEntityFields.LAST_SEEN_IP, String::class.java)
- .addField(MyDeviceLastSeenInfoEntityFields.LAST_SEEN_TS, Long::class.java)
- .setNullable(MyDeviceLastSeenInfoEntityFields.LAST_SEEN_TS, true)
-
- val now = System.currentTimeMillis()
- realm.schema.get("DeviceInfoEntity")
- ?.addField(DeviceInfoEntityFields.FIRST_TIME_SEEN_LOCAL_TS, Long::class.java)
- ?.setNullable(DeviceInfoEntityFields.FIRST_TIME_SEEN_LOCAL_TS, true)
- ?.transform { deviceInfoEntity ->
- tryOrNull {
- deviceInfoEntity.setLong(DeviceInfoEntityFields.FIRST_TIME_SEEN_LOCAL_TS, now)
- }
- }
- }
-
- // Fixes duplicate devices in UserEntity#devices
- private fun migrateTo9(realm: DynamicRealm) {
- Timber.d("Step 8 -> 9")
- val userEntities = realm.where("UserEntity").findAll()
- userEntities.forEach {
- try {
- val deviceList = it.getList(UserEntityFields.DEVICES.`$`)
- ?: return@forEach
- val distinct = deviceList.distinctBy { it.getString(DeviceInfoEntityFields.DEVICE_ID) }
- if (distinct.size != deviceList.size) {
- deviceList.clear()
- deviceList.addAll(distinct)
- }
- } catch (failure: Throwable) {
- Timber.w(failure, "Crypto Data base migration error for migrateTo9")
- }
- }
- }
-
- // Version 10L added WithHeld Keys Info (MSC2399)
- private fun migrateTo10(realm: DynamicRealm) {
- Timber.d("Step 9 -> 10")
- realm.schema.create("WithHeldSessionEntity")
- .addField(WithHeldSessionEntityFields.ROOM_ID, String::class.java)
- .addField(WithHeldSessionEntityFields.ALGORITHM, String::class.java)
- .addField(WithHeldSessionEntityFields.SESSION_ID, String::class.java)
- .addIndex(WithHeldSessionEntityFields.SESSION_ID)
- .addField(WithHeldSessionEntityFields.SENDER_KEY, String::class.java)
- .addIndex(WithHeldSessionEntityFields.SENDER_KEY)
- .addField(WithHeldSessionEntityFields.CODE_STRING, String::class.java)
- .addField(WithHeldSessionEntityFields.REASON, String::class.java)
-
- realm.schema.create("SharedSessionEntity")
- .addField(SharedSessionEntityFields.ROOM_ID, String::class.java)
- .addField(SharedSessionEntityFields.ALGORITHM, String::class.java)
- .addField(SharedSessionEntityFields.SESSION_ID, String::class.java)
- .addIndex(SharedSessionEntityFields.SESSION_ID)
- .addField(SharedSessionEntityFields.USER_ID, String::class.java)
- .addIndex(SharedSessionEntityFields.USER_ID)
- .addField(SharedSessionEntityFields.DEVICE_ID, String::class.java)
- .addIndex(SharedSessionEntityFields.DEVICE_ID)
- .addField(SharedSessionEntityFields.CHAIN_INDEX, Long::class.java)
- .setNullable(SharedSessionEntityFields.CHAIN_INDEX, true)
- }
-
- // Version 11L added deviceKeysSentToServer boolean to CryptoMetadataEntity
- private fun migrateTo11(realm: DynamicRealm) {
- Timber.d("Step 10 -> 11")
- realm.schema.get("CryptoMetadataEntity")
- ?.addField(CryptoMetadataEntityFields.DEVICE_KEYS_SENT_TO_SERVER, Boolean::class.java)
- }
-
- // Version 12L added outbound group session persistence
- private fun migrateTo12(realm: DynamicRealm) {
- Timber.d("Step 11 -> 12")
- val outboundEntitySchema = realm.schema.create("OutboundGroupSessionInfoEntity")
- .addField(OutboundGroupSessionInfoEntityFields.SERIALIZED_OUTBOUND_SESSION_DATA, String::class.java)
- .addField(OutboundGroupSessionInfoEntityFields.CREATION_TIME, Long::class.java)
- .setNullable(OutboundGroupSessionInfoEntityFields.CREATION_TIME, true)
-
- realm.schema.get("CryptoRoomEntity")
- ?.addRealmObjectField(CryptoRoomEntityFields.OUTBOUND_SESSION_INFO.`$`, outboundEntitySchema)
- }
-
- // Version 13L delete unreferenced TrustLevelEntity
- private fun migrateTo13(realm: DynamicRealm) {
- Timber.d("Step 12 -> 13")
-
- // Use a trick to do that... Ref: https://stackoverflow.com/questions/55221366
- val trustLevelEntitySchema = realm.schema.get("TrustLevelEntity")
-
- /*
- Creating a new temp field called isLinked which is set to true for those which are
- references by other objects. Rest of them are set to false. Then removing all
- those which are false and hence duplicate and unnecessary. Then removing the temp field
- isLinked
- */
- var mainCounter = 0
- var deviceInfoCounter = 0
- var keyInfoCounter = 0
- val deleteCounter: Int
-
- trustLevelEntitySchema
- ?.addField("isLinked", Boolean::class.java)
- ?.transform { obj ->
- // Setting to false for all by default
- obj.set("isLinked", false)
- mainCounter++
- }
-
- realm.schema.get("DeviceInfoEntity")?.transform { obj ->
- // Setting to true for those which are referenced in DeviceInfoEntity
- deviceInfoCounter++
- obj.getObject("trustLevelEntity")?.set("isLinked", true)
- }
-
- realm.schema.get("KeyInfoEntity")?.transform { obj ->
- // Setting to true for those which are referenced in KeyInfoEntity
- keyInfoCounter++
- obj.getObject("trustLevelEntity")?.set("isLinked", true)
- }
-
- // Removing all those which are set as false
- realm.where("TrustLevelEntity")
- .equalTo("isLinked", false)
- .findAll()
- .also { deleteCounter = it.size }
- .deleteAllFromRealm()
-
- trustLevelEntitySchema?.removeField("isLinked")
-
- Timber.w("TrustLevelEntity cleanup: $mainCounter entities")
- Timber.w("TrustLevelEntity cleanup: $deviceInfoCounter entities referenced in DeviceInfoEntities")
- Timber.w("TrustLevelEntity cleanup: $keyInfoCounter entities referenced in KeyInfoEntity")
- Timber.w("TrustLevelEntity cleanup: $deleteCounter entities deleted!")
- if (mainCounter != deviceInfoCounter + keyInfoCounter + deleteCounter) {
- Timber.e("TrustLevelEntity cleanup: Something is not correct...")
- }
- }
-
- // Version 14L Update the way we remember key sharing
- private fun migrateTo14(realm: DynamicRealm) {
- Timber.d("Step 13 -> 14")
- realm.schema.get("SharedSessionEntity")
- ?.addField(SharedSessionEntityFields.DEVICE_IDENTITY_KEY, String::class.java)
- ?.addIndex(SharedSessionEntityFields.DEVICE_IDENTITY_KEY)
- ?.transform {
- val sharedUserId = it.getString(SharedSessionEntityFields.USER_ID)
- val sharedDeviceId = it.getString(SharedSessionEntityFields.DEVICE_ID)
- val knownDevice = realm.where("DeviceInfoEntity")
- .equalTo(DeviceInfoEntityFields.USER_ID, sharedUserId)
- .equalTo(DeviceInfoEntityFields.DEVICE_ID, sharedDeviceId)
- .findFirst()
- it.setString(SharedSessionEntityFields.DEVICE_IDENTITY_KEY, knownDevice?.getString(DeviceInfoEntityFields.IDENTITY_KEY))
- }
+ if (oldVersion < 1) MigrateCryptoTo001Legacy(realm).perform()
+ if (oldVersion < 2) MigrateCryptoTo002Legacy(realm).perform()
+ if (oldVersion < 3) MigrateCryptoTo003RiotX(realm).perform()
+ if (oldVersion < 4) MigrateCryptoTo004(realm).perform()
+ if (oldVersion < 5) MigrateCryptoTo005(realm).perform()
+ if (oldVersion < 6) MigrateCryptoTo006(realm).perform()
+ if (oldVersion < 7) MigrateCryptoTo007(realm).perform()
+ if (oldVersion < 8) MigrateCryptoTo008(realm).perform()
+ if (oldVersion < 9) MigrateCryptoTo009(realm).perform()
+ if (oldVersion < 10) MigrateCryptoTo010(realm).perform()
+ if (oldVersion < 11) MigrateCryptoTo011(realm).perform()
+ if (oldVersion < 12) MigrateCryptoTo012(realm).perform()
+ if (oldVersion < 13) MigrateCryptoTo013(realm).perform()
+ if (oldVersion < 14) MigrateCryptoTo014(realm).perform()
}
}
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/migration/MigrateCryptoTo001Legacy.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/migration/MigrateCryptoTo001Legacy.kt
new file mode 100644
index 0000000000..0e44689428
--- /dev/null
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/migration/MigrateCryptoTo001Legacy.kt
@@ -0,0 +1,35 @@
+/*
+ * Copyright (c) 2022 The Matrix.org Foundation C.I.C.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.matrix.android.sdk.internal.crypto.store.db.migration
+
+import io.realm.DynamicRealm
+import org.matrix.android.sdk.internal.crypto.store.db.model.OlmSessionEntityFields
+import org.matrix.android.sdk.internal.util.database.RealmMigrator
+import timber.log.Timber
+
+class MigrateCryptoTo001Legacy(realm: DynamicRealm) : RealmMigrator(realm, 1) {
+
+ override fun doMigrate(realm: DynamicRealm) {
+ Timber.d("Add field lastReceivedMessageTs (Long) and set the value to 0")
+
+ realm.schema.get("OlmSessionEntity")
+ ?.addField(OlmSessionEntityFields.LAST_RECEIVED_MESSAGE_TS, Long::class.java)
+ ?.transform {
+ it.setLong(OlmSessionEntityFields.LAST_RECEIVED_MESSAGE_TS, 0)
+ }
+ }
+}
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/migration/MigrateCryptoTo002Legacy.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/migration/MigrateCryptoTo002Legacy.kt
new file mode 100644
index 0000000000..84e627a688
--- /dev/null
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/migration/MigrateCryptoTo002Legacy.kt
@@ -0,0 +1,86 @@
+/*
+ * Copyright (c) 2022 The Matrix.org Foundation C.I.C.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.matrix.android.sdk.internal.crypto.store.db.migration
+
+import io.realm.DynamicRealm
+import org.matrix.android.sdk.internal.crypto.store.db.deserializeFromRealm
+import org.matrix.android.sdk.internal.crypto.store.db.model.KeysBackupDataEntityFields
+import org.matrix.android.sdk.internal.util.database.RealmMigrator
+import timber.log.Timber
+
+class MigrateCryptoTo002Legacy(realm: DynamicRealm) : RealmMigrator(realm, 2) {
+
+ override fun doMigrate(realm: DynamicRealm) {
+ Timber.d("Update IncomingRoomKeyRequestEntity format: requestBodyString field is exploded into several fields")
+ realm.schema.get("IncomingRoomKeyRequestEntity")
+ ?.addFieldIfNotExists("requestBodyAlgorithm", String::class.java)
+ ?.addFieldIfNotExists("requestBodyRoomId", String::class.java)
+ ?.addFieldIfNotExists("requestBodySenderKey", String::class.java)
+ ?.addFieldIfNotExists("requestBodySessionId", String::class.java)
+ ?.transform { dynamicObject ->
+ try {
+ val requestBodyString = dynamicObject.getString("requestBodyString")
+ // It was a map before
+ val map: Map? = deserializeFromRealm(requestBodyString)
+
+ map?.let {
+ dynamicObject.setString("requestBodyAlgorithm", it["algorithm"])
+ dynamicObject.setString("requestBodyRoomId", it["room_id"])
+ dynamicObject.setString("requestBodySenderKey", it["sender_key"])
+ dynamicObject.setString("requestBodySessionId", it["session_id"])
+ }
+ } catch (e: Exception) {
+ Timber.e(e, "Error")
+ }
+ }
+ ?.removeFieldIfExists("requestBodyString")
+
+ Timber.d("Update IncomingRoomKeyRequestEntity format: requestBodyString field is exploded into several fields")
+ realm.schema.get("OutgoingRoomKeyRequestEntity")
+ ?.addFieldIfNotExists("requestBodyAlgorithm", String::class.java)
+ ?.addFieldIfNotExists("requestBodyRoomId", String::class.java)
+ ?.addFieldIfNotExists("requestBodySenderKey", String::class.java)
+ ?.addFieldIfNotExists("requestBodySessionId", String::class.java)
+ ?.transform { dynamicObject ->
+ try {
+ val requestBodyString = dynamicObject.getString("requestBodyString")
+ // It was a map before
+ val map: Map? = deserializeFromRealm(requestBodyString)
+
+ map?.let {
+ dynamicObject.setString("requestBodyAlgorithm", it["algorithm"])
+ dynamicObject.setString("requestBodyRoomId", it["room_id"])
+ dynamicObject.setString("requestBodySenderKey", it["sender_key"])
+ dynamicObject.setString("requestBodySessionId", it["session_id"])
+ }
+ } catch (e: Exception) {
+ Timber.e(e, "Error")
+ }
+ }
+ ?.removeFieldIfExists("requestBodyString")
+
+ Timber.d("Create KeysBackupDataEntity")
+ if (!realm.schema.contains("KeysBackupDataEntity")) {
+ realm.schema.create("KeysBackupDataEntity")
+ .addField(KeysBackupDataEntityFields.PRIMARY_KEY, Integer::class.java)
+ .addPrimaryKey(KeysBackupDataEntityFields.PRIMARY_KEY)
+ .setRequired(KeysBackupDataEntityFields.PRIMARY_KEY, true)
+ .addField(KeysBackupDataEntityFields.BACKUP_LAST_SERVER_HASH, String::class.java)
+ .addField(KeysBackupDataEntityFields.BACKUP_LAST_SERVER_NUMBER_OF_KEYS, Integer::class.java)
+ }
+ }
+}
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/migration/MigrateCryptoTo003RiotX.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/migration/MigrateCryptoTo003RiotX.kt
new file mode 100644
index 0000000000..b468a56af6
--- /dev/null
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/migration/MigrateCryptoTo003RiotX.kt
@@ -0,0 +1,83 @@
+/*
+ * Copyright (c) 2022 The Matrix.org Foundation C.I.C.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.matrix.android.sdk.internal.crypto.store.db.migration
+
+import io.realm.DynamicRealm
+import org.matrix.android.sdk.internal.crypto.model.OlmInboundGroupSessionWrapper
+import org.matrix.android.sdk.internal.crypto.store.db.deserializeFromRealm
+import org.matrix.android.sdk.internal.crypto.store.db.model.CryptoRoomEntityFields
+import org.matrix.android.sdk.internal.crypto.store.db.serializeForRealm
+import org.matrix.android.sdk.internal.util.database.RealmMigrator
+import org.matrix.androidsdk.crypto.data.MXDeviceInfo
+import org.matrix.androidsdk.crypto.data.MXOlmInboundGroupSession2
+import timber.log.Timber
+
+class MigrateCryptoTo003RiotX(realm: DynamicRealm) : RealmMigrator(realm, 3) {
+
+ override fun doMigrate(realm: DynamicRealm) {
+ Timber.d("Migrate to RiotX model")
+ realm.schema.get("CryptoRoomEntity")
+ ?.addFieldIfNotExists(CryptoRoomEntityFields.SHOULD_ENCRYPT_FOR_INVITED_MEMBERS, Boolean::class.java)
+ ?.setRequiredIfNotAlready(CryptoRoomEntityFields.SHOULD_ENCRYPT_FOR_INVITED_MEMBERS, false)
+
+ // Convert format of MXDeviceInfo, package has to be the same.
+ realm.schema.get("DeviceInfoEntity")
+ ?.transform { obj ->
+ try {
+ val oldSerializedData = obj.getString("deviceInfoData")
+ deserializeFromRealm(oldSerializedData)?.let { legacyMxDeviceInfo ->
+ val newMxDeviceInfo = org.matrix.android.sdk.internal.crypto.model.MXDeviceInfo(
+ deviceId = legacyMxDeviceInfo.deviceId,
+ userId = legacyMxDeviceInfo.userId,
+ algorithms = legacyMxDeviceInfo.algorithms,
+ keys = legacyMxDeviceInfo.keys,
+ signatures = legacyMxDeviceInfo.signatures,
+ unsigned = legacyMxDeviceInfo.unsigned,
+ verified = legacyMxDeviceInfo.mVerified
+ )
+
+ obj.setString("deviceInfoData", serializeForRealm(newMxDeviceInfo))
+ }
+ } catch (e: Exception) {
+ Timber.e(e, "Error")
+ }
+ }
+
+ // Convert MXOlmInboundGroupSession2 to OlmInboundGroupSessionWrapper
+ realm.schema.get("OlmInboundGroupSessionEntity")
+ ?.transform { obj ->
+ try {
+ val oldSerializedData = obj.getString("olmInboundGroupSessionData")
+ deserializeFromRealm(oldSerializedData)?.let { mxOlmInboundGroupSession2 ->
+ val sessionKey = mxOlmInboundGroupSession2.mSession.sessionIdentifier()
+ val newOlmInboundGroupSessionWrapper = OlmInboundGroupSessionWrapper(sessionKey, false)
+ .apply {
+ olmInboundGroupSession = mxOlmInboundGroupSession2.mSession
+ roomId = mxOlmInboundGroupSession2.mRoomId
+ senderKey = mxOlmInboundGroupSession2.mSenderKey
+ keysClaimed = mxOlmInboundGroupSession2.mKeysClaimed
+ forwardingCurve25519KeyChain = mxOlmInboundGroupSession2.mForwardingCurve25519KeyChain
+ }
+
+ obj.setString("olmInboundGroupSessionData", serializeForRealm(newOlmInboundGroupSessionWrapper))
+ }
+ } catch (e: Exception) {
+ Timber.e(e, "Error")
+ }
+ }
+ }
+}
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/migration/MigrateCryptoTo004.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/migration/MigrateCryptoTo004.kt
new file mode 100644
index 0000000000..20a4814b8d
--- /dev/null
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/migration/MigrateCryptoTo004.kt
@@ -0,0 +1,139 @@
+/*
+ * Copyright (c) 2022 The Matrix.org Foundation C.I.C.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.matrix.android.sdk.internal.crypto.store.db.migration
+
+import com.squareup.moshi.Moshi
+import com.squareup.moshi.Types
+import io.realm.DynamicRealm
+import org.matrix.android.sdk.api.util.JsonDict
+import org.matrix.android.sdk.internal.crypto.model.MXDeviceInfo
+import org.matrix.android.sdk.internal.crypto.store.db.deserializeFromRealm
+import org.matrix.android.sdk.internal.crypto.store.db.model.CrossSigningInfoEntityFields
+import org.matrix.android.sdk.internal.crypto.store.db.model.CryptoMetadataEntityFields
+import org.matrix.android.sdk.internal.crypto.store.db.model.DeviceInfoEntityFields
+import org.matrix.android.sdk.internal.crypto.store.db.model.KeyInfoEntityFields
+import org.matrix.android.sdk.internal.crypto.store.db.model.TrustLevelEntityFields
+import org.matrix.android.sdk.internal.crypto.store.db.model.UserEntityFields
+import org.matrix.android.sdk.internal.di.SerializeNulls
+import org.matrix.android.sdk.internal.util.database.RealmMigrator
+import timber.log.Timber
+
+// Version 4L added Cross Signing info persistence
+class MigrateCryptoTo004(realm: DynamicRealm) : RealmMigrator(realm, 4) {
+
+ override fun doMigrate(realm: DynamicRealm) {
+ if (realm.schema.contains("TrustLevelEntity")) {
+ Timber.d("Skipping Step 3 -> 4 because entities already exist")
+ return
+ }
+
+ Timber.d("Create KeyInfoEntity")
+ val trustLevelEntityEntitySchema = realm.schema.create("TrustLevelEntity")
+ .addField(TrustLevelEntityFields.CROSS_SIGNED_VERIFIED, Boolean::class.java)
+ .setNullable(TrustLevelEntityFields.CROSS_SIGNED_VERIFIED, true)
+ .addField(TrustLevelEntityFields.LOCALLY_VERIFIED, Boolean::class.java)
+ .setNullable(TrustLevelEntityFields.LOCALLY_VERIFIED, true)
+
+ val keyInfoEntitySchema = realm.schema.create("KeyInfoEntity")
+ .addField(KeyInfoEntityFields.PUBLIC_KEY_BASE64, String::class.java)
+ .addField(KeyInfoEntityFields.SIGNATURES, String::class.java)
+ .addRealmListField(KeyInfoEntityFields.USAGES.`$`, String::class.java)
+ .addRealmObjectField(KeyInfoEntityFields.TRUST_LEVEL_ENTITY.`$`, trustLevelEntityEntitySchema)
+
+ Timber.d("Create CrossSigningInfoEntity")
+
+ val crossSigningInfoSchema = realm.schema.create("CrossSigningInfoEntity")
+ .addField(CrossSigningInfoEntityFields.USER_ID, String::class.java)
+ .addPrimaryKey(CrossSigningInfoEntityFields.USER_ID)
+ .addRealmListField(CrossSigningInfoEntityFields.CROSS_SIGNING_KEYS.`$`, keyInfoEntitySchema)
+
+ Timber.d("Updating UserEntity table")
+ realm.schema.get("UserEntity")
+ ?.addRealmObjectField(UserEntityFields.CROSS_SIGNING_INFO_ENTITY.`$`, crossSigningInfoSchema)
+
+ Timber.d("Updating CryptoMetadataEntity table")
+ realm.schema.get("CryptoMetadataEntity")
+ ?.addField(CryptoMetadataEntityFields.X_SIGN_MASTER_PRIVATE_KEY, String::class.java)
+ ?.addField(CryptoMetadataEntityFields.X_SIGN_USER_PRIVATE_KEY, String::class.java)
+ ?.addField(CryptoMetadataEntityFields.X_SIGN_SELF_SIGNED_PRIVATE_KEY, String::class.java)
+
+ val moshi = Moshi.Builder().add(SerializeNulls.JSON_ADAPTER_FACTORY).build()
+ val listMigrationAdapter = moshi.adapter>(Types.newParameterizedType(
+ List::class.java,
+ String::class.java,
+ Any::class.java
+ ))
+ val mapMigrationAdapter = moshi.adapter(Types.newParameterizedType(
+ Map::class.java,
+ String::class.java,
+ Any::class.java
+ ))
+
+ realm.schema.get("DeviceInfoEntity")
+ ?.addField(DeviceInfoEntityFields.USER_ID, String::class.java)
+ ?.addField(DeviceInfoEntityFields.ALGORITHM_LIST_JSON, String::class.java)
+ ?.addField(DeviceInfoEntityFields.KEYS_MAP_JSON, String::class.java)
+ ?.addField(DeviceInfoEntityFields.SIGNATURE_MAP_JSON, String::class.java)
+ ?.addField(DeviceInfoEntityFields.UNSIGNED_MAP_JSON, String::class.java)
+ ?.addField(DeviceInfoEntityFields.IS_BLOCKED, Boolean::class.java)
+ ?.setNullable(DeviceInfoEntityFields.IS_BLOCKED, true)
+ ?.addRealmObjectField(DeviceInfoEntityFields.TRUST_LEVEL_ENTITY.`$`, trustLevelEntityEntitySchema)
+ ?.transform { obj ->
+
+ try {
+ val oldSerializedData = obj.getString("deviceInfoData")
+ deserializeFromRealm(oldSerializedData)?.let { oldDevice ->
+
+ val trustLevel = realm.createObject("TrustLevelEntity")
+ when (oldDevice.verified) {
+ MXDeviceInfo.DEVICE_VERIFICATION_UNKNOWN -> {
+ obj.setNull(DeviceInfoEntityFields.TRUST_LEVEL_ENTITY.`$`)
+ }
+ MXDeviceInfo.DEVICE_VERIFICATION_BLOCKED -> {
+ trustLevel.setNull(TrustLevelEntityFields.LOCALLY_VERIFIED)
+ trustLevel.setNull(TrustLevelEntityFields.CROSS_SIGNED_VERIFIED)
+ obj.setBoolean(DeviceInfoEntityFields.IS_BLOCKED, oldDevice.isBlocked)
+ obj.setObject(DeviceInfoEntityFields.TRUST_LEVEL_ENTITY.`$`, trustLevel)
+ }
+ MXDeviceInfo.DEVICE_VERIFICATION_UNVERIFIED -> {
+ trustLevel.setBoolean(TrustLevelEntityFields.LOCALLY_VERIFIED, false)
+ trustLevel.setBoolean(TrustLevelEntityFields.CROSS_SIGNED_VERIFIED, false)
+ obj.setObject(DeviceInfoEntityFields.TRUST_LEVEL_ENTITY.`$`, trustLevel)
+ }
+ MXDeviceInfo.DEVICE_VERIFICATION_VERIFIED -> {
+ trustLevel.setBoolean(TrustLevelEntityFields.LOCALLY_VERIFIED, true)
+ trustLevel.setBoolean(TrustLevelEntityFields.CROSS_SIGNED_VERIFIED, false)
+ obj.setObject(DeviceInfoEntityFields.TRUST_LEVEL_ENTITY.`$`, trustLevel)
+ }
+ }
+
+ obj.setString(DeviceInfoEntityFields.USER_ID, oldDevice.userId)
+ obj.setString(DeviceInfoEntityFields.IDENTITY_KEY, oldDevice.identityKey())
+ obj.setString(DeviceInfoEntityFields.ALGORITHM_LIST_JSON, listMigrationAdapter.toJson(oldDevice.algorithms))
+ obj.setString(DeviceInfoEntityFields.KEYS_MAP_JSON, mapMigrationAdapter.toJson(oldDevice.keys))
+ obj.setString(DeviceInfoEntityFields.SIGNATURE_MAP_JSON, mapMigrationAdapter.toJson(oldDevice.signatures))
+ obj.setString(DeviceInfoEntityFields.UNSIGNED_MAP_JSON, mapMigrationAdapter.toJson(oldDevice.unsigned))
+ }
+ } catch (failure: Throwable) {
+ Timber.w(failure, "Crypto Data base migration error")
+ // an unfortunate refactor did modify that class, making deserialization failing
+ // so we just skip and ignore..
+ }
+ }
+ ?.removeField("deviceInfoData")
+ }
+}
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/migration/MigrateCryptoTo005.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/migration/MigrateCryptoTo005.kt
new file mode 100644
index 0000000000..8365d34464
--- /dev/null
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/migration/MigrateCryptoTo005.kt
@@ -0,0 +1,66 @@
+/*
+ * Copyright (c) 2022 The Matrix.org Foundation C.I.C.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.matrix.android.sdk.internal.crypto.store.db.migration
+
+import io.realm.DynamicRealm
+import org.matrix.android.sdk.internal.crypto.store.db.model.GossipingEventEntityFields
+import org.matrix.android.sdk.internal.crypto.store.db.model.IncomingGossipingRequestEntityFields
+import org.matrix.android.sdk.internal.crypto.store.db.model.OutgoingGossipingRequestEntityFields
+import org.matrix.android.sdk.internal.util.database.RealmMigrator
+
+class MigrateCryptoTo005(realm: DynamicRealm) : RealmMigrator(realm, 5) {
+
+ override fun doMigrate(realm: DynamicRealm) {
+ realm.schema.remove("OutgoingRoomKeyRequestEntity")
+ realm.schema.remove("IncomingRoomKeyRequestEntity")
+
+ // Not need to migrate existing request, just start fresh?
+
+ realm.schema.create("GossipingEventEntity")
+ .addField(GossipingEventEntityFields.TYPE, String::class.java)
+ .addIndex(GossipingEventEntityFields.TYPE)
+ .addField(GossipingEventEntityFields.CONTENT, String::class.java)
+ .addField(GossipingEventEntityFields.SENDER, String::class.java)
+ .addIndex(GossipingEventEntityFields.SENDER)
+ .addField(GossipingEventEntityFields.DECRYPTION_RESULT_JSON, String::class.java)
+ .addField(GossipingEventEntityFields.DECRYPTION_ERROR_CODE, String::class.java)
+ .addField(GossipingEventEntityFields.AGE_LOCAL_TS, Long::class.java)
+ .setNullable(GossipingEventEntityFields.AGE_LOCAL_TS, true)
+ .addField(GossipingEventEntityFields.SEND_STATE_STR, String::class.java)
+
+ realm.schema.create("IncomingGossipingRequestEntity")
+ .addField(IncomingGossipingRequestEntityFields.REQUEST_ID, String::class.java)
+ .addIndex(IncomingGossipingRequestEntityFields.REQUEST_ID)
+ .addField(IncomingGossipingRequestEntityFields.TYPE_STR, String::class.java)
+ .addIndex(IncomingGossipingRequestEntityFields.TYPE_STR)
+ .addField(IncomingGossipingRequestEntityFields.OTHER_USER_ID, String::class.java)
+ .addField(IncomingGossipingRequestEntityFields.REQUESTED_INFO_STR, String::class.java)
+ .addField(IncomingGossipingRequestEntityFields.OTHER_DEVICE_ID, String::class.java)
+ .addField(IncomingGossipingRequestEntityFields.REQUEST_STATE_STR, String::class.java)
+ .addField(IncomingGossipingRequestEntityFields.LOCAL_CREATION_TIMESTAMP, Long::class.java)
+ .setNullable(IncomingGossipingRequestEntityFields.LOCAL_CREATION_TIMESTAMP, true)
+
+ realm.schema.create("OutgoingGossipingRequestEntity")
+ .addField(OutgoingGossipingRequestEntityFields.REQUEST_ID, String::class.java)
+ .addIndex(OutgoingGossipingRequestEntityFields.REQUEST_ID)
+ .addField(OutgoingGossipingRequestEntityFields.RECIPIENTS_DATA, String::class.java)
+ .addField(OutgoingGossipingRequestEntityFields.REQUESTED_INFO_STR, String::class.java)
+ .addField(OutgoingGossipingRequestEntityFields.TYPE_STR, String::class.java)
+ .addIndex(OutgoingGossipingRequestEntityFields.TYPE_STR)
+ .addField(OutgoingGossipingRequestEntityFields.REQUEST_STATE_STR, String::class.java)
+ }
+}
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/migration/MigrateCryptoTo006.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/migration/MigrateCryptoTo006.kt
new file mode 100644
index 0000000000..a29a791826
--- /dev/null
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/migration/MigrateCryptoTo006.kt
@@ -0,0 +1,32 @@
+/*
+ * Copyright (c) 2022 The Matrix.org Foundation C.I.C.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.matrix.android.sdk.internal.crypto.store.db.migration
+
+import io.realm.DynamicRealm
+import org.matrix.android.sdk.internal.crypto.store.db.model.CryptoMetadataEntityFields
+import org.matrix.android.sdk.internal.util.database.RealmMigrator
+import timber.log.Timber
+
+class MigrateCryptoTo006(realm: DynamicRealm) : RealmMigrator(realm, 6) {
+
+ override fun doMigrate(realm: DynamicRealm) {
+ Timber.d("Updating CryptoMetadataEntity table")
+ realm.schema.get("CryptoMetadataEntity")
+ ?.addField(CryptoMetadataEntityFields.KEY_BACKUP_RECOVERY_KEY, String::class.java)
+ ?.addField(CryptoMetadataEntityFields.KEY_BACKUP_RECOVERY_KEY_VERSION, String::class.java)
+ }
+}
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/migration/MigrateCryptoTo007.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/migration/MigrateCryptoTo007.kt
new file mode 100644
index 0000000000..7ae58e7fc0
--- /dev/null
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/migration/MigrateCryptoTo007.kt
@@ -0,0 +1,65 @@
+/*
+ * Copyright (c) 2022 The Matrix.org Foundation C.I.C.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.matrix.android.sdk.internal.crypto.store.db.migration
+
+import io.realm.DynamicRealm
+import org.matrix.android.sdk.internal.crypto.model.OlmInboundGroupSessionWrapper
+import org.matrix.android.sdk.internal.crypto.model.OlmInboundGroupSessionWrapper2
+import org.matrix.android.sdk.internal.crypto.store.db.deserializeFromRealm
+import org.matrix.android.sdk.internal.crypto.store.db.mapper.CrossSigningKeysMapper
+import org.matrix.android.sdk.internal.crypto.store.db.model.KeyInfoEntityFields
+import org.matrix.android.sdk.internal.crypto.store.db.model.OlmInboundGroupSessionEntityFields
+import org.matrix.android.sdk.internal.crypto.store.db.serializeForRealm
+import org.matrix.android.sdk.internal.di.MoshiProvider
+import org.matrix.android.sdk.internal.util.database.RealmMigrator
+import timber.log.Timber
+
+class MigrateCryptoTo007(realm: DynamicRealm) : RealmMigrator(realm, 7) {
+
+ override fun doMigrate(realm: DynamicRealm) {
+ Timber.d("Updating KeyInfoEntity table")
+ val crossSigningKeysMapper = CrossSigningKeysMapper(MoshiProvider.providesMoshi())
+
+ val keyInfoEntities = realm.where("KeyInfoEntity").findAll()
+ try {
+ keyInfoEntities.forEach {
+ val stringSignatures = it.getString(KeyInfoEntityFields.SIGNATURES)
+ val objectSignatures: Map>? = deserializeFromRealm(stringSignatures)
+ val jsonSignatures = crossSigningKeysMapper.serializeSignatures(objectSignatures)
+ it.setString(KeyInfoEntityFields.SIGNATURES, jsonSignatures)
+ }
+ } catch (failure: Throwable) {
+ }
+
+ // Migrate frozen classes
+ val inboundGroupSessions = realm.where("OlmInboundGroupSessionEntity").findAll()
+ inboundGroupSessions.forEach { dynamicObject ->
+ dynamicObject.getString(OlmInboundGroupSessionEntityFields.OLM_INBOUND_GROUP_SESSION_DATA)?.let { serializedObject ->
+ try {
+ deserializeFromRealm(serializedObject)?.let { oldFormat ->
+ val newFormat = oldFormat.exportKeys()?.let {
+ OlmInboundGroupSessionWrapper2(it)
+ }
+ dynamicObject.setString(OlmInboundGroupSessionEntityFields.OLM_INBOUND_GROUP_SESSION_DATA, serializeForRealm(newFormat))
+ }
+ } catch (failure: Throwable) {
+ Timber.e(failure, "## OlmInboundGroupSessionEntity migration failed")
+ }
+ }
+ }
+ }
+}
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/migration/MigrateCryptoTo008.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/migration/MigrateCryptoTo008.kt
new file mode 100644
index 0000000000..e3bd3f035a
--- /dev/null
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/migration/MigrateCryptoTo008.kt
@@ -0,0 +1,46 @@
+/*
+ * Copyright (c) 2022 The Matrix.org Foundation C.I.C.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.matrix.android.sdk.internal.crypto.store.db.migration
+
+import io.realm.DynamicRealm
+import org.matrix.android.sdk.api.extensions.tryOrNull
+import org.matrix.android.sdk.internal.crypto.store.db.model.DeviceInfoEntityFields
+import org.matrix.android.sdk.internal.crypto.store.db.model.MyDeviceLastSeenInfoEntityFields
+import org.matrix.android.sdk.internal.util.database.RealmMigrator
+
+class MigrateCryptoTo008(realm: DynamicRealm) : RealmMigrator(realm, 8) {
+
+ override fun doMigrate(realm: DynamicRealm) {
+ realm.schema.create("MyDeviceLastSeenInfoEntity")
+ .addField(MyDeviceLastSeenInfoEntityFields.DEVICE_ID, String::class.java)
+ .addPrimaryKey(MyDeviceLastSeenInfoEntityFields.DEVICE_ID)
+ .addField(MyDeviceLastSeenInfoEntityFields.DISPLAY_NAME, String::class.java)
+ .addField(MyDeviceLastSeenInfoEntityFields.LAST_SEEN_IP, String::class.java)
+ .addField(MyDeviceLastSeenInfoEntityFields.LAST_SEEN_TS, Long::class.java)
+ .setNullable(MyDeviceLastSeenInfoEntityFields.LAST_SEEN_TS, true)
+
+ val now = System.currentTimeMillis()
+ realm.schema.get("DeviceInfoEntity")
+ ?.addField(DeviceInfoEntityFields.FIRST_TIME_SEEN_LOCAL_TS, Long::class.java)
+ ?.setNullable(DeviceInfoEntityFields.FIRST_TIME_SEEN_LOCAL_TS, true)
+ ?.transform { deviceInfoEntity ->
+ tryOrNull {
+ deviceInfoEntity.setLong(DeviceInfoEntityFields.FIRST_TIME_SEEN_LOCAL_TS, now)
+ }
+ }
+ }
+}
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/migration/MigrateCryptoTo009.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/migration/MigrateCryptoTo009.kt
new file mode 100644
index 0000000000..ed705318f9
--- /dev/null
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/migration/MigrateCryptoTo009.kt
@@ -0,0 +1,44 @@
+/*
+ * Copyright (c) 2022 The Matrix.org Foundation C.I.C.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.matrix.android.sdk.internal.crypto.store.db.migration
+
+import io.realm.DynamicRealm
+import org.matrix.android.sdk.internal.crypto.store.db.model.DeviceInfoEntityFields
+import org.matrix.android.sdk.internal.crypto.store.db.model.UserEntityFields
+import org.matrix.android.sdk.internal.util.database.RealmMigrator
+import timber.log.Timber
+
+// Fixes duplicate devices in UserEntity#devices
+class MigrateCryptoTo009(realm: DynamicRealm) : RealmMigrator(realm, 9) {
+
+ override fun doMigrate(realm: DynamicRealm) {
+ val userEntities = realm.where("UserEntity").findAll()
+ userEntities.forEach {
+ try {
+ val deviceList = it.getList(UserEntityFields.DEVICES.`$`)
+ ?: return@forEach
+ val distinct = deviceList.distinctBy { it.getString(DeviceInfoEntityFields.DEVICE_ID) }
+ if (distinct.size != deviceList.size) {
+ deviceList.clear()
+ deviceList.addAll(distinct)
+ }
+ } catch (failure: Throwable) {
+ Timber.w(failure, "Crypto Data base migration error for migrateTo9")
+ }
+ }
+ }
+}
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/migration/MigrateCryptoTo010.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/migration/MigrateCryptoTo010.kt
new file mode 100644
index 0000000000..8d69ee5558
--- /dev/null
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/migration/MigrateCryptoTo010.kt
@@ -0,0 +1,50 @@
+/*
+ * Copyright (c) 2022 The Matrix.org Foundation C.I.C.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.matrix.android.sdk.internal.crypto.store.db.migration
+
+import io.realm.DynamicRealm
+import org.matrix.android.sdk.internal.crypto.store.db.model.SharedSessionEntityFields
+import org.matrix.android.sdk.internal.crypto.store.db.model.WithHeldSessionEntityFields
+import org.matrix.android.sdk.internal.util.database.RealmMigrator
+
+// Version 10L added WithHeld Keys Info (MSC2399)
+class MigrateCryptoTo010(realm: DynamicRealm) : RealmMigrator(realm, 10) {
+
+ override fun doMigrate(realm: DynamicRealm) {
+ realm.schema.create("WithHeldSessionEntity")
+ .addField(WithHeldSessionEntityFields.ROOM_ID, String::class.java)
+ .addField(WithHeldSessionEntityFields.ALGORITHM, String::class.java)
+ .addField(WithHeldSessionEntityFields.SESSION_ID, String::class.java)
+ .addIndex(WithHeldSessionEntityFields.SESSION_ID)
+ .addField(WithHeldSessionEntityFields.SENDER_KEY, String::class.java)
+ .addIndex(WithHeldSessionEntityFields.SENDER_KEY)
+ .addField(WithHeldSessionEntityFields.CODE_STRING, String::class.java)
+ .addField(WithHeldSessionEntityFields.REASON, String::class.java)
+
+ realm.schema.create("SharedSessionEntity")
+ .addField(SharedSessionEntityFields.ROOM_ID, String::class.java)
+ .addField(SharedSessionEntityFields.ALGORITHM, String::class.java)
+ .addField(SharedSessionEntityFields.SESSION_ID, String::class.java)
+ .addIndex(SharedSessionEntityFields.SESSION_ID)
+ .addField(SharedSessionEntityFields.USER_ID, String::class.java)
+ .addIndex(SharedSessionEntityFields.USER_ID)
+ .addField(SharedSessionEntityFields.DEVICE_ID, String::class.java)
+ .addIndex(SharedSessionEntityFields.DEVICE_ID)
+ .addField(SharedSessionEntityFields.CHAIN_INDEX, Long::class.java)
+ .setNullable(SharedSessionEntityFields.CHAIN_INDEX, true)
+ }
+}
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/migration/MigrateCryptoTo011.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/migration/MigrateCryptoTo011.kt
new file mode 100644
index 0000000000..c9825a7f3d
--- /dev/null
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/migration/MigrateCryptoTo011.kt
@@ -0,0 +1,30 @@
+/*
+ * Copyright (c) 2022 The Matrix.org Foundation C.I.C.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.matrix.android.sdk.internal.crypto.store.db.migration
+
+import io.realm.DynamicRealm
+import org.matrix.android.sdk.internal.crypto.store.db.model.CryptoMetadataEntityFields
+import org.matrix.android.sdk.internal.util.database.RealmMigrator
+
+// Version 11L added deviceKeysSentToServer boolean to CryptoMetadataEntity
+class MigrateCryptoTo011(realm: DynamicRealm) : RealmMigrator(realm, 11) {
+
+ override fun doMigrate(realm: DynamicRealm) {
+ realm.schema.get("CryptoMetadataEntity")
+ ?.addField(CryptoMetadataEntityFields.DEVICE_KEYS_SENT_TO_SERVER, Boolean::class.java)
+ }
+}
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/migration/MigrateCryptoTo012.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/migration/MigrateCryptoTo012.kt
new file mode 100644
index 0000000000..6b1460d9d6
--- /dev/null
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/migration/MigrateCryptoTo012.kt
@@ -0,0 +1,36 @@
+/*
+ * Copyright (c) 2022 The Matrix.org Foundation C.I.C.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.matrix.android.sdk.internal.crypto.store.db.migration
+
+import io.realm.DynamicRealm
+import org.matrix.android.sdk.internal.crypto.store.db.model.CryptoRoomEntityFields
+import org.matrix.android.sdk.internal.crypto.store.db.model.OutboundGroupSessionInfoEntityFields
+import org.matrix.android.sdk.internal.util.database.RealmMigrator
+
+// Version 12L added outbound group session persistence
+class MigrateCryptoTo012(realm: DynamicRealm) : RealmMigrator(realm, 12) {
+
+ override fun doMigrate(realm: DynamicRealm) {
+ val outboundEntitySchema = realm.schema.create("OutboundGroupSessionInfoEntity")
+ .addField(OutboundGroupSessionInfoEntityFields.SERIALIZED_OUTBOUND_SESSION_DATA, String::class.java)
+ .addField(OutboundGroupSessionInfoEntityFields.CREATION_TIME, Long::class.java)
+ .setNullable(OutboundGroupSessionInfoEntityFields.CREATION_TIME, true)
+
+ realm.schema.get("CryptoRoomEntity")
+ ?.addRealmObjectField(CryptoRoomEntityFields.OUTBOUND_SESSION_INFO.`$`, outboundEntitySchema)
+ }
+}
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/migration/MigrateCryptoTo013.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/migration/MigrateCryptoTo013.kt
new file mode 100644
index 0000000000..dc22c5f133
--- /dev/null
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/migration/MigrateCryptoTo013.kt
@@ -0,0 +1,78 @@
+/*
+ * Copyright (c) 2022 The Matrix.org Foundation C.I.C.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.matrix.android.sdk.internal.crypto.store.db.migration
+
+import io.realm.DynamicRealm
+import org.matrix.android.sdk.internal.util.database.RealmMigrator
+import timber.log.Timber
+
+// Version 13L delete unreferenced TrustLevelEntity
+class MigrateCryptoTo013(realm: DynamicRealm) : RealmMigrator(realm, 13) {
+
+ override fun doMigrate(realm: DynamicRealm) {
+ // Use a trick to do that... Ref: https://stackoverflow.com/questions/55221366
+ val trustLevelEntitySchema = realm.schema.get("TrustLevelEntity")
+
+ /*
+ Creating a new temp field called isLinked which is set to true for those which are
+ references by other objects. Rest of them are set to false. Then removing all
+ those which are false and hence duplicate and unnecessary. Then removing the temp field
+ isLinked
+ */
+ var mainCounter = 0
+ var deviceInfoCounter = 0
+ var keyInfoCounter = 0
+ val deleteCounter: Int
+
+ trustLevelEntitySchema
+ ?.addField("isLinked", Boolean::class.java)
+ ?.transform { obj ->
+ // Setting to false for all by default
+ obj.set("isLinked", false)
+ mainCounter++
+ }
+
+ realm.schema.get("DeviceInfoEntity")?.transform { obj ->
+ // Setting to true for those which are referenced in DeviceInfoEntity
+ deviceInfoCounter++
+ obj.getObject("trustLevelEntity")?.set("isLinked", true)
+ }
+
+ realm.schema.get("KeyInfoEntity")?.transform { obj ->
+ // Setting to true for those which are referenced in KeyInfoEntity
+ keyInfoCounter++
+ obj.getObject("trustLevelEntity")?.set("isLinked", true)
+ }
+
+ // Removing all those which are set as false
+ realm.where("TrustLevelEntity")
+ .equalTo("isLinked", false)
+ .findAll()
+ .also { deleteCounter = it.size }
+ .deleteAllFromRealm()
+
+ trustLevelEntitySchema?.removeField("isLinked")
+
+ Timber.w("TrustLevelEntity cleanup: $mainCounter entities")
+ Timber.w("TrustLevelEntity cleanup: $deviceInfoCounter entities referenced in DeviceInfoEntities")
+ Timber.w("TrustLevelEntity cleanup: $keyInfoCounter entities referenced in KeyInfoEntity")
+ Timber.w("TrustLevelEntity cleanup: $deleteCounter entities deleted!")
+ if (mainCounter != deviceInfoCounter + keyInfoCounter + deleteCounter) {
+ Timber.e("TrustLevelEntity cleanup: Something is not correct...")
+ }
+ }
+}
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/migration/MigrateCryptoTo014.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/migration/MigrateCryptoTo014.kt
new file mode 100644
index 0000000000..f0089e3427
--- /dev/null
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/migration/MigrateCryptoTo014.kt
@@ -0,0 +1,41 @@
+/*
+ * Copyright (c) 2022 The Matrix.org Foundation C.I.C.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.matrix.android.sdk.internal.crypto.store.db.migration
+
+import io.realm.DynamicRealm
+import org.matrix.android.sdk.internal.crypto.store.db.model.DeviceInfoEntityFields
+import org.matrix.android.sdk.internal.crypto.store.db.model.SharedSessionEntityFields
+import org.matrix.android.sdk.internal.util.database.RealmMigrator
+
+// Version 14L Update the way we remember key sharing
+class MigrateCryptoTo014(realm: DynamicRealm) : RealmMigrator(realm, 14) {
+
+ override fun doMigrate(realm: DynamicRealm) {
+ realm.schema.get("SharedSessionEntity")
+ ?.addField(SharedSessionEntityFields.DEVICE_IDENTITY_KEY, String::class.java)
+ ?.addIndex(SharedSessionEntityFields.DEVICE_IDENTITY_KEY)
+ ?.transform {
+ val sharedUserId = it.getString(SharedSessionEntityFields.USER_ID)
+ val sharedDeviceId = it.getString(SharedSessionEntityFields.DEVICE_ID)
+ val knownDevice = realm.where("DeviceInfoEntity")
+ .equalTo(DeviceInfoEntityFields.USER_ID, sharedUserId)
+ .equalTo(DeviceInfoEntityFields.DEVICE_ID, sharedDeviceId)
+ .findFirst()
+ it.setString(SharedSessionEntityFields.DEVICE_IDENTITY_KEY, knownDevice?.getString(DeviceInfoEntityFields.IDENTITY_KEY))
+ }
+ }
+}
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 5706a4ca06..f4a8ae2c67 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
@@ -17,37 +17,31 @@
package org.matrix.android.sdk.internal.database
import io.realm.DynamicRealm
-import io.realm.FieldAttribute
import io.realm.RealmMigration
-import org.matrix.android.sdk.api.session.events.model.EventType
-import org.matrix.android.sdk.api.session.room.model.Membership
-import org.matrix.android.sdk.api.session.room.model.RoomJoinRulesContent
-import org.matrix.android.sdk.api.session.room.model.VersioningState
-import org.matrix.android.sdk.api.session.room.model.create.RoomCreateContent
-import org.matrix.android.sdk.api.session.room.model.tag.RoomTag
-import org.matrix.android.sdk.api.session.threads.ThreadNotificationState
-import org.matrix.android.sdk.internal.crypto.model.event.EncryptionEventContent
-import org.matrix.android.sdk.internal.database.model.ChunkEntityFields
-import org.matrix.android.sdk.internal.database.model.CurrentStateEventEntityFields
-import org.matrix.android.sdk.internal.database.model.EditAggregatedSummaryEntityFields
-import org.matrix.android.sdk.internal.database.model.EditionOfEventFields
-import org.matrix.android.sdk.internal.database.model.EventEntityFields
-import org.matrix.android.sdk.internal.database.model.EventInsertEntityFields
-import org.matrix.android.sdk.internal.database.model.HomeServerCapabilitiesEntityFields
-import org.matrix.android.sdk.internal.database.model.PendingThreePidEntityFields
-import org.matrix.android.sdk.internal.database.model.PreviewUrlCacheEntityFields
-import org.matrix.android.sdk.internal.database.model.RoomAccountDataEntityFields
-import org.matrix.android.sdk.internal.database.model.RoomEntityFields
-import org.matrix.android.sdk.internal.database.model.RoomMemberSummaryEntityFields
-import org.matrix.android.sdk.internal.database.model.RoomMembersLoadStatusType
-import org.matrix.android.sdk.internal.database.model.RoomSummaryEntityFields
-import org.matrix.android.sdk.internal.database.model.RoomTagEntityFields
-import org.matrix.android.sdk.internal.database.model.SpaceChildSummaryEntityFields
-import org.matrix.android.sdk.internal.database.model.SpaceParentSummaryEntityFields
-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.database.migration.MigrateSessionTo001
+import org.matrix.android.sdk.internal.database.migration.MigrateSessionTo002
+import org.matrix.android.sdk.internal.database.migration.MigrateSessionTo003
+import org.matrix.android.sdk.internal.database.migration.MigrateSessionTo004
+import org.matrix.android.sdk.internal.database.migration.MigrateSessionTo005
+import org.matrix.android.sdk.internal.database.migration.MigrateSessionTo006
+import org.matrix.android.sdk.internal.database.migration.MigrateSessionTo007
+import org.matrix.android.sdk.internal.database.migration.MigrateSessionTo008
+import org.matrix.android.sdk.internal.database.migration.MigrateSessionTo009
+import org.matrix.android.sdk.internal.database.migration.MigrateSessionTo010
+import org.matrix.android.sdk.internal.database.migration.MigrateSessionTo011
+import org.matrix.android.sdk.internal.database.migration.MigrateSessionTo012
+import org.matrix.android.sdk.internal.database.migration.MigrateSessionTo013
+import org.matrix.android.sdk.internal.database.migration.MigrateSessionTo014
+import org.matrix.android.sdk.internal.database.migration.MigrateSessionTo015
+import org.matrix.android.sdk.internal.database.migration.MigrateSessionTo016
+import org.matrix.android.sdk.internal.database.migration.MigrateSessionTo017
+import org.matrix.android.sdk.internal.database.migration.MigrateSessionTo018
+import org.matrix.android.sdk.internal.database.migration.MigrateSessionTo019
+import org.matrix.android.sdk.internal.database.migration.MigrateSessionTo020
+import org.matrix.android.sdk.internal.database.migration.MigrateSessionTo021
+import org.matrix.android.sdk.internal.database.migration.MigrateSessionTo022
+import org.matrix.android.sdk.internal.database.migration.MigrateSessionTo023
+import org.matrix.android.sdk.internal.database.migration.MigrateSessionTo024
import org.matrix.android.sdk.internal.util.Normalizer
import timber.log.Timber
import javax.inject.Inject
@@ -55,11 +49,6 @@ import javax.inject.Inject
internal class RealmSessionStoreMigration @Inject constructor(
private val normalizer: Normalizer
) : RealmMigration {
-
- companion object {
- const val SESSION_STORE_SCHEMA_VERSION = 26L
- }
-
/**
* Forces all RealmSessionStoreMigration instances to be equal
* Avoids Realm throwing when multiple instances of the migration are set
@@ -67,437 +56,34 @@ internal class RealmSessionStoreMigration @Inject constructor(
override fun equals(other: Any?) = other is RealmSessionStoreMigration
override fun hashCode() = 1000
+ val schemaVersion = 24L
+
override fun migrate(realm: DynamicRealm, oldVersion: Long, newVersion: Long) {
- Timber.v("Migrating Realm Session from $oldVersion to $newVersion")
+ Timber.d("Migrating Realm Session from $oldVersion to $newVersion")
- if (oldVersion <= 0) migrateTo1(realm)
- if (oldVersion <= 1) migrateTo2(realm)
- if (oldVersion <= 2) migrateTo3(realm)
- if (oldVersion <= 3) migrateTo4(realm)
- if (oldVersion <= 4) migrateTo5(realm)
- if (oldVersion <= 5) migrateTo6(realm)
- if (oldVersion <= 6) migrateTo7(realm)
- if (oldVersion <= 7) migrateTo8(realm)
- if (oldVersion <= 8) migrateTo9(realm)
- if (oldVersion <= 9) migrateTo10(realm)
- if (oldVersion <= 10) migrateTo11(realm)
- if (oldVersion <= 11) migrateTo12(realm)
- if (oldVersion <= 12) migrateTo13(realm)
- if (oldVersion <= 13) migrateTo14(realm)
- if (oldVersion <= 14) migrateTo15(realm)
- if (oldVersion <= 15) migrateTo16(realm)
- if (oldVersion <= 16) migrateTo17(realm)
- if (oldVersion <= 17) migrateTo18(realm)
- if (oldVersion <= 18) migrateTo19(realm)
- if (oldVersion <= 19) migrateTo20(realm)
- if (oldVersion <= 20) migrateTo21(realm)
- if (oldVersion <= 21) migrateTo22(realm)
- if (oldVersion <= 22) migrateTo23(realm)
- if (oldVersion <= 23) migrateTo24(realm)
- if (oldVersion <= 24) migrateTo25(realm)
- }
-
- private fun migrateTo1(realm: DynamicRealm) {
- Timber.d("Step 0 -> 1")
- // Add hasFailedSending in RoomSummary and a small warning icon on room list
-
- realm.schema.get("RoomSummaryEntity")
- ?.addField(RoomSummaryEntityFields.HAS_FAILED_SENDING, Boolean::class.java)
- ?.transform { obj ->
- obj.setBoolean(RoomSummaryEntityFields.HAS_FAILED_SENDING, false)
- }
- }
-
- private fun migrateTo2(realm: DynamicRealm) {
- Timber.d("Step 1 -> 2")
- realm.schema.get("HomeServerCapabilitiesEntity")
- ?.addField("adminE2EByDefault", Boolean::class.java)
- ?.transform { obj ->
- obj.setBoolean("adminE2EByDefault", true)
- }
- }
-
- private fun migrateTo3(realm: DynamicRealm) {
- Timber.d("Step 2 -> 3")
- realm.schema.get("HomeServerCapabilitiesEntity")
- ?.addField("preferredJitsiDomain", String::class.java)
- ?.transform { obj ->
- // Schedule a refresh of the capabilities
- obj.setLong(HomeServerCapabilitiesEntityFields.LAST_UPDATED_TIMESTAMP, 0)
- }
- }
-
- private fun migrateTo4(realm: DynamicRealm) {
- Timber.d("Step 3 -> 4")
- realm.schema.create("PendingThreePidEntity")
- .addField(PendingThreePidEntityFields.CLIENT_SECRET, String::class.java)
- .setRequired(PendingThreePidEntityFields.CLIENT_SECRET, true)
- .addField(PendingThreePidEntityFields.EMAIL, String::class.java)
- .addField(PendingThreePidEntityFields.MSISDN, String::class.java)
- .addField(PendingThreePidEntityFields.SEND_ATTEMPT, Int::class.java)
- .addField(PendingThreePidEntityFields.SID, String::class.java)
- .setRequired(PendingThreePidEntityFields.SID, true)
- .addField(PendingThreePidEntityFields.SUBMIT_URL, String::class.java)
- }
-
- private fun migrateTo5(realm: DynamicRealm) {
- Timber.d("Step 4 -> 5")
- realm.schema.get("HomeServerCapabilitiesEntity")
- ?.removeField("adminE2EByDefault")
- ?.removeField("preferredJitsiDomain")
- }
-
- private fun migrateTo6(realm: DynamicRealm) {
- Timber.d("Step 5 -> 6")
- realm.schema.create("PreviewUrlCacheEntity")
- .addField(PreviewUrlCacheEntityFields.URL, String::class.java)
- .setRequired(PreviewUrlCacheEntityFields.URL, true)
- .addPrimaryKey(PreviewUrlCacheEntityFields.URL)
- .addField(PreviewUrlCacheEntityFields.URL_FROM_SERVER, String::class.java)
- .addField(PreviewUrlCacheEntityFields.SITE_NAME, String::class.java)
- .addField(PreviewUrlCacheEntityFields.TITLE, String::class.java)
- .addField(PreviewUrlCacheEntityFields.DESCRIPTION, String::class.java)
- .addField(PreviewUrlCacheEntityFields.MXC_URL, String::class.java)
- .addField(PreviewUrlCacheEntityFields.LAST_UPDATED_TIMESTAMP, Long::class.java)
- }
-
- private fun migrateTo7(realm: DynamicRealm) {
- Timber.d("Step 6 -> 7")
- realm.schema.get("RoomEntity")
- ?.addField(RoomEntityFields.MEMBERS_LOAD_STATUS_STR, String::class.java)
- ?.transform { obj ->
- if (obj.getBoolean("areAllMembersLoaded")) {
- obj.setString("membersLoadStatusStr", RoomMembersLoadStatusType.LOADED.name)
- } else {
- obj.setString("membersLoadStatusStr", RoomMembersLoadStatusType.NONE.name)
- }
- }
- ?.removeField("areAllMembersLoaded")
- }
-
- private fun migrateTo8(realm: DynamicRealm) {
- Timber.d("Step 7 -> 8")
-
- val editionOfEventSchema = realm.schema.create("EditionOfEvent")
- .addField(EditionOfEventFields.CONTENT, String::class.java)
- .addField(EditionOfEventFields.EVENT_ID, String::class.java)
- .setRequired(EditionOfEventFields.EVENT_ID, true)
- .addField(EditionOfEventFields.SENDER_ID, String::class.java)
- .setRequired(EditionOfEventFields.SENDER_ID, true)
- .addField(EditionOfEventFields.TIMESTAMP, Long::class.java)
- .addField(EditionOfEventFields.IS_LOCAL_ECHO, Boolean::class.java)
-
- realm.schema.get("EditAggregatedSummaryEntity")
- ?.removeField("aggregatedContent")
- ?.removeField("sourceEvents")
- ?.removeField("lastEditTs")
- ?.removeField("sourceLocalEchoEvents")
- ?.addRealmListField(EditAggregatedSummaryEntityFields.EDITIONS.`$`, editionOfEventSchema)
-
- // This has to be done once a parent use the model as a child
- // See https://github.com/realm/realm-java/issues/7402
- editionOfEventSchema.isEmbedded = true
- }
-
- private fun migrateTo9(realm: DynamicRealm) {
- Timber.d("Step 8 -> 9")
-
- realm.schema.get("RoomSummaryEntity")
- ?.addField(RoomSummaryEntityFields.LAST_ACTIVITY_TIME, Long::class.java, FieldAttribute.INDEXED)
- ?.setNullable(RoomSummaryEntityFields.LAST_ACTIVITY_TIME, true)
- ?.addIndex(RoomSummaryEntityFields.MEMBERSHIP_STR)
- ?.addIndex(RoomSummaryEntityFields.IS_DIRECT)
- ?.addIndex(RoomSummaryEntityFields.VERSIONING_STATE_STR)
-
- ?.addField(RoomSummaryEntityFields.IS_FAVOURITE, Boolean::class.java)
- ?.addIndex(RoomSummaryEntityFields.IS_FAVOURITE)
- ?.addField(RoomSummaryEntityFields.IS_LOW_PRIORITY, Boolean::class.java)
- ?.addIndex(RoomSummaryEntityFields.IS_LOW_PRIORITY)
- ?.addField(RoomSummaryEntityFields.IS_SERVER_NOTICE, Boolean::class.java)
- ?.addIndex(RoomSummaryEntityFields.IS_SERVER_NOTICE)
-
- ?.transform { obj ->
- val isFavorite = obj.getList(RoomSummaryEntityFields.TAGS.`$`).any {
- it.getString(RoomTagEntityFields.TAG_NAME) == RoomTag.ROOM_TAG_FAVOURITE
- }
- obj.setBoolean(RoomSummaryEntityFields.IS_FAVOURITE, isFavorite)
-
- val isLowPriority = obj.getList(RoomSummaryEntityFields.TAGS.`$`).any {
- it.getString(RoomTagEntityFields.TAG_NAME) == RoomTag.ROOM_TAG_LOW_PRIORITY
- }
-
- obj.setBoolean(RoomSummaryEntityFields.IS_LOW_PRIORITY, isLowPriority)
-
-// XXX migrate last message origin server ts
- obj.getObject(RoomSummaryEntityFields.LATEST_PREVIEWABLE_EVENT.`$`)
- ?.getObject(TimelineEventEntityFields.ROOT.`$`)
- ?.getLong(EventEntityFields.ORIGIN_SERVER_TS)?.let {
- obj.setLong(RoomSummaryEntityFields.LAST_ACTIVITY_TIME, it)
- }
- }
- }
-
- private fun migrateTo10(realm: DynamicRealm) {
- Timber.d("Step 9 -> 10")
- realm.schema.create("SpaceChildSummaryEntity")
- ?.addField(SpaceChildSummaryEntityFields.ORDER, String::class.java)
- ?.addField(SpaceChildSummaryEntityFields.CHILD_ROOM_ID, String::class.java)
- ?.addField(SpaceChildSummaryEntityFields.AUTO_JOIN, Boolean::class.java)
- ?.setNullable(SpaceChildSummaryEntityFields.AUTO_JOIN, true)
- ?.addRealmObjectField(SpaceChildSummaryEntityFields.CHILD_SUMMARY_ENTITY.`$`, realm.schema.get("RoomSummaryEntity")!!)
- ?.addRealmListField(SpaceChildSummaryEntityFields.VIA_SERVERS.`$`, String::class.java)
-
- realm.schema.create("SpaceParentSummaryEntity")
- ?.addField(SpaceParentSummaryEntityFields.PARENT_ROOM_ID, String::class.java)
- ?.addField(SpaceParentSummaryEntityFields.CANONICAL, Boolean::class.java)
- ?.setNullable(SpaceParentSummaryEntityFields.CANONICAL, true)
- ?.addRealmObjectField(SpaceParentSummaryEntityFields.PARENT_SUMMARY_ENTITY.`$`, realm.schema.get("RoomSummaryEntity")!!)
- ?.addRealmListField(SpaceParentSummaryEntityFields.VIA_SERVERS.`$`, String::class.java)
-
- val creationContentAdapter = MoshiProvider.providesMoshi().adapter(RoomCreateContent::class.java)
- realm.schema.get("RoomSummaryEntity")
- ?.addField(RoomSummaryEntityFields.ROOM_TYPE, String::class.java)
- ?.addField(RoomSummaryEntityFields.FLATTEN_PARENT_IDS, String::class.java)
- ?.addField(RoomSummaryEntityFields.GROUP_IDS, String::class.java)
- ?.transform { obj ->
-
- val creationEvent = realm.where("CurrentStateEventEntity")
- .equalTo(CurrentStateEventEntityFields.ROOM_ID, obj.getString(RoomSummaryEntityFields.ROOM_ID))
- .equalTo(CurrentStateEventEntityFields.TYPE, EventType.STATE_ROOM_CREATE)
- .findFirst()
-
- val roomType = creationEvent?.getObject(CurrentStateEventEntityFields.ROOT.`$`)
- ?.getString(EventEntityFields.CONTENT)?.let {
- creationContentAdapter.fromJson(it)?.type
- }
-
- obj.setString(RoomSummaryEntityFields.ROOM_TYPE, roomType)
- }
- ?.addRealmListField(RoomSummaryEntityFields.PARENTS.`$`, realm.schema.get("SpaceParentSummaryEntity")!!)
- ?.addRealmListField(RoomSummaryEntityFields.CHILDREN.`$`, realm.schema.get("SpaceChildSummaryEntity")!!)
- }
-
- private fun migrateTo11(realm: DynamicRealm) {
- Timber.d("Step 10 -> 11")
- realm.schema.get("EventEntity")
- ?.addField(EventEntityFields.SEND_STATE_DETAILS, String::class.java)
- }
-
- private fun migrateTo12(realm: DynamicRealm) {
- Timber.d("Step 11 -> 12")
-
- val joinRulesContentAdapter = MoshiProvider.providesMoshi().adapter(RoomJoinRulesContent::class.java)
- realm.schema.get("RoomSummaryEntity")
- ?.addField(RoomSummaryEntityFields.JOIN_RULES_STR, String::class.java)
- ?.transform { obj ->
- val joinRulesEvent = realm.where("CurrentStateEventEntity")
- .equalTo(CurrentStateEventEntityFields.ROOM_ID, obj.getString(RoomSummaryEntityFields.ROOM_ID))
- .equalTo(CurrentStateEventEntityFields.TYPE, EventType.STATE_ROOM_JOIN_RULES)
- .findFirst()
-
- val roomJoinRules = joinRulesEvent?.getObject(CurrentStateEventEntityFields.ROOT.`$`)
- ?.getString(EventEntityFields.CONTENT)?.let {
- joinRulesContentAdapter.fromJson(it)?.joinRules
- }
-
- obj.setString(RoomSummaryEntityFields.JOIN_RULES_STR, roomJoinRules?.name)
- }
-
- realm.schema.get("SpaceChildSummaryEntity")
- ?.addField(SpaceChildSummaryEntityFields.SUGGESTED, Boolean::class.java)
- ?.setNullable(SpaceChildSummaryEntityFields.SUGGESTED, true)
- }
-
- private fun migrateTo13(realm: DynamicRealm) {
- Timber.d("Step 12 -> 13")
- // Fix issue with the nightly build. Eventually play again the migration which has been included in migrateTo12()
- realm.schema.get("SpaceChildSummaryEntity")
- ?.takeIf { !it.hasField(SpaceChildSummaryEntityFields.SUGGESTED) }
- ?.addField(SpaceChildSummaryEntityFields.SUGGESTED, Boolean::class.java)
- ?.setNullable(SpaceChildSummaryEntityFields.SUGGESTED, true)
- }
-
- private fun migrateTo14(realm: DynamicRealm) {
- Timber.d("Step 13 -> 14")
- val roomAccountDataSchema = realm.schema.create("RoomAccountDataEntity")
- .addField(RoomAccountDataEntityFields.CONTENT_STR, String::class.java)
- .addField(RoomAccountDataEntityFields.TYPE, String::class.java, FieldAttribute.INDEXED)
-
- realm.schema.get("RoomEntity")
- ?.addRealmListField(RoomEntityFields.ACCOUNT_DATA.`$`, roomAccountDataSchema)
-
- realm.schema.get("RoomSummaryEntity")
- ?.addField(RoomSummaryEntityFields.IS_HIDDEN_FROM_USER, Boolean::class.java, FieldAttribute.INDEXED)
- ?.transform {
- val isHiddenFromUser = it.getString(RoomSummaryEntityFields.VERSIONING_STATE_STR) == VersioningState.UPGRADED_ROOM_JOINED.name
- it.setBoolean(RoomSummaryEntityFields.IS_HIDDEN_FROM_USER, isHiddenFromUser)
- }
-
- roomAccountDataSchema.isEmbedded = true
- }
-
- private fun migrateTo15(realm: DynamicRealm) {
- Timber.d("Step 14 -> 15")
- // fix issue with flattenParentIds on DM that kept growing with duplicate
- // so we reset it, will be updated next sync
- realm.where("RoomSummaryEntity")
- .process(RoomSummaryEntityFields.MEMBERSHIP_STR, Membership.activeMemberships())
- .equalTo(RoomSummaryEntityFields.IS_DIRECT, true)
- .findAll()
- .onEach {
- it.setString(RoomSummaryEntityFields.FLATTEN_PARENT_IDS, null)
- }
- }
-
- private fun migrateTo16(realm: DynamicRealm) {
- Timber.d("Step 15 -> 16")
- realm.schema.get("HomeServerCapabilitiesEntity")
- ?.addField(HomeServerCapabilitiesEntityFields.ROOM_VERSIONS_JSON, String::class.java)
- ?.transform { obj ->
- // Schedule a refresh of the capabilities
- obj.setLong(HomeServerCapabilitiesEntityFields.LAST_UPDATED_TIMESTAMP, 0)
- }
- }
-
- private fun migrateTo17(realm: DynamicRealm) {
- Timber.d("Step 16 -> 17")
- realm.schema.get("EventInsertEntity")
- ?.addField(EventInsertEntityFields.CAN_BE_PROCESSED, Boolean::class.java)
- }
-
- private fun migrateTo18(realm: DynamicRealm) {
- Timber.d("Step 17 -> 18")
- realm.schema.create("UserPresenceEntity")
- ?.addField(UserPresenceEntityFields.USER_ID, String::class.java)
- ?.addPrimaryKey(UserPresenceEntityFields.USER_ID)
- ?.setRequired(UserPresenceEntityFields.USER_ID, true)
- ?.addField(UserPresenceEntityFields.PRESENCE_STR, String::class.java)
- ?.addField(UserPresenceEntityFields.LAST_ACTIVE_AGO, Long::class.java)
- ?.setNullable(UserPresenceEntityFields.LAST_ACTIVE_AGO, true)
- ?.addField(UserPresenceEntityFields.STATUS_MESSAGE, String::class.java)
- ?.addField(UserPresenceEntityFields.IS_CURRENTLY_ACTIVE, Boolean::class.java)
- ?.setNullable(UserPresenceEntityFields.IS_CURRENTLY_ACTIVE, true)
- ?.addField(UserPresenceEntityFields.AVATAR_URL, String::class.java)
- ?.addField(UserPresenceEntityFields.DISPLAY_NAME, String::class.java)
-
- val userPresenceEntity = realm.schema.get("UserPresenceEntity") ?: return
- realm.schema.get("RoomSummaryEntity")
- ?.addRealmObjectField(RoomSummaryEntityFields.DIRECT_USER_PRESENCE.`$`, userPresenceEntity)
-
- 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)
- }
- }
- }
-
- private fun migrateTo20(realm: DynamicRealm) {
- Timber.d("Step 19 -> 20")
-
- realm.schema.get("ChunkEntity")?.apply {
- if (hasField("numberOfTimelineEvents")) {
- removeField("numberOfTimelineEvents")
- }
- var cleanOldChunks = false
- if (!hasField(ChunkEntityFields.NEXT_CHUNK.`$`)) {
- cleanOldChunks = true
- addRealmObjectField(ChunkEntityFields.NEXT_CHUNK.`$`, this)
- }
- if (!hasField(ChunkEntityFields.PREV_CHUNK.`$`)) {
- cleanOldChunks = true
- addRealmObjectField(ChunkEntityFields.PREV_CHUNK.`$`, this)
- }
- if (cleanOldChunks) {
- val chunkEntities = realm.where("ChunkEntity").equalTo(ChunkEntityFields.IS_LAST_FORWARD, false).findAll()
- chunkEntities.deleteAllFromRealm()
- }
- }
- }
-
- private fun migrateTo21(realm: DynamicRealm) {
- Timber.d("Step 20 -> 21")
-
- realm.schema.get("RoomSummaryEntity")
- ?.addField(RoomSummaryEntityFields.E2E_ALGORITHM, String::class.java)
- ?.transform { obj ->
-
- val encryptionContentAdapter = MoshiProvider.providesMoshi().adapter(EncryptionEventContent::class.java)
-
- val encryptionEvent = realm.where("CurrentStateEventEntity")
- .equalTo(CurrentStateEventEntityFields.ROOM_ID, obj.getString(RoomSummaryEntityFields.ROOM_ID))
- .equalTo(CurrentStateEventEntityFields.TYPE, EventType.STATE_ROOM_ENCRYPTION)
- .findFirst()
-
- val encryptionEventRoot = encryptionEvent?.getObject(CurrentStateEventEntityFields.ROOT.`$`)
- val algorithm = encryptionEventRoot
- ?.getString(EventEntityFields.CONTENT)?.let {
- encryptionContentAdapter.fromJson(it)?.algorithm
- }
-
- obj.setString(RoomSummaryEntityFields.E2E_ALGORITHM, algorithm)
- obj.setBoolean(RoomSummaryEntityFields.IS_ENCRYPTED, encryptionEvent != null)
- encryptionEventRoot?.getLong(EventEntityFields.ORIGIN_SERVER_TS)?.let {
- obj.setLong(RoomSummaryEntityFields.ENCRYPTION_EVENT_TS, it)
- }
- }
- }
-
- private fun migrateTo22(realm: DynamicRealm) {
- Timber.d("Step 21 -> 22")
- val listJoinedRoomIds = realm.where("RoomEntity")
- .equalTo(RoomEntityFields.MEMBERSHIP_STR, Membership.JOIN.name).findAll()
- .map { it.getString(RoomEntityFields.ROOM_ID) }
-
- val hasMissingStateEvent = realm.where("CurrentStateEventEntity")
- .`in`(CurrentStateEventEntityFields.ROOM_ID, listJoinedRoomIds.toTypedArray())
- .isNull(CurrentStateEventEntityFields.ROOT.`$`).findFirst() != null
-
- if (hasMissingStateEvent) {
- Timber.v("Has some missing state event, clear session cache")
- realm.deleteAll()
- }
- }
-
- private fun migrateTo23(realm: DynamicRealm) {
- Timber.d("Step 22 -> 23")
- val eventEntity = realm.schema.get("TimelineEventEntity") ?: return
-
- realm.schema.get("EventEntity")
- ?.addField(EventEntityFields.IS_ROOT_THREAD, Boolean::class.java, FieldAttribute.INDEXED)
- ?.addField(EventEntityFields.ROOT_THREAD_EVENT_ID, String::class.java, FieldAttribute.INDEXED)
- ?.addField(EventEntityFields.NUMBER_OF_THREADS, Int::class.java)
- ?.addField(EventEntityFields.THREAD_NOTIFICATION_STATE_STR, String::class.java)
- ?.transform {
- it.setString(EventEntityFields.THREAD_NOTIFICATION_STATE_STR, ThreadNotificationState.NO_NEW_MESSAGE.name)
- }
- ?.addRealmObjectField(EventEntityFields.THREAD_SUMMARY_LATEST_MESSAGE.`$`, eventEntity)
- }
-
- private fun migrateTo24(realm: DynamicRealm) {
- Timber.d("Step 23 -> 24")
- realm.schema.get("PreviewUrlCacheEntity")
- ?.addField(PreviewUrlCacheEntityFields.IMAGE_WIDTH, Int::class.java)
- ?.setNullable(PreviewUrlCacheEntityFields.IMAGE_WIDTH, true)
- ?.addField(PreviewUrlCacheEntityFields.IMAGE_HEIGHT, Int::class.java)
- ?.setNullable(PreviewUrlCacheEntityFields.IMAGE_HEIGHT, true)
- }
-
- private fun migrateTo25(realm: DynamicRealm) {
- Timber.d("Step 24 -> 25")
- realm.schema.get("ChunkEntity")
- ?.addField(ChunkEntityFields.ROOT_THREAD_EVENT_ID, String::class.java, FieldAttribute.INDEXED)
- ?.addField(ChunkEntityFields.IS_LAST_FORWARD_THREAD, Boolean::class.java, FieldAttribute.INDEXED)
-
- realm.schema.get("TimelineEventEntity")
- ?.addField(TimelineEventEntityFields.OWNED_BY_THREAD_CHUNK, Boolean::class.java)
+ if (oldVersion < 1) MigrateSessionTo001(realm).perform()
+ if (oldVersion < 2) MigrateSessionTo002(realm).perform()
+ if (oldVersion < 3) MigrateSessionTo003(realm).perform()
+ if (oldVersion < 4) MigrateSessionTo004(realm).perform()
+ if (oldVersion < 5) MigrateSessionTo005(realm).perform()
+ if (oldVersion < 6) MigrateSessionTo006(realm).perform()
+ if (oldVersion < 7) MigrateSessionTo007(realm).perform()
+ if (oldVersion < 8) MigrateSessionTo008(realm).perform()
+ if (oldVersion < 9) MigrateSessionTo009(realm).perform()
+ if (oldVersion < 10) MigrateSessionTo010(realm).perform()
+ if (oldVersion < 11) MigrateSessionTo011(realm).perform()
+ if (oldVersion < 12) MigrateSessionTo012(realm).perform()
+ if (oldVersion < 13) MigrateSessionTo013(realm).perform()
+ if (oldVersion < 14) MigrateSessionTo014(realm).perform()
+ if (oldVersion < 15) MigrateSessionTo015(realm).perform()
+ if (oldVersion < 16) MigrateSessionTo016(realm).perform()
+ if (oldVersion < 17) MigrateSessionTo017(realm).perform()
+ if (oldVersion < 18) MigrateSessionTo018(realm).perform()
+ if (oldVersion < 19) MigrateSessionTo019(realm, normalizer).perform()
+ if (oldVersion < 20) MigrateSessionTo020(realm).perform()
+ if (oldVersion < 21) MigrateSessionTo021(realm).perform()
+ if (oldVersion < 22) MigrateSessionTo022(realm).perform()
+ if (oldVersion < 23) MigrateSessionTo023(realm).perform()
+ if (oldVersion < 24) MigrateSessionTo024(realm).perform()
}
}
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 04ca26a943..08d55b5647 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
@@ -71,7 +71,7 @@ internal class SessionRealmConfigurationFactory @Inject constructor(
}
.allowWritesOnUiThread(true)
.modules(SessionRealmModule())
- .schemaVersion(RealmSessionStoreMigration.SESSION_STORE_SCHEMA_VERSION)
+ .schemaVersion(realmSessionStoreMigration.schemaVersion)
.migration(realmSessionStoreMigration)
.build()
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/migration/MigrateSessionTo001.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/migration/MigrateSessionTo001.kt
new file mode 100644
index 0000000000..831c6280ad
--- /dev/null
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/migration/MigrateSessionTo001.kt
@@ -0,0 +1,33 @@
+/*
+ * Copyright (c) 2022 The Matrix.org Foundation C.I.C.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.matrix.android.sdk.internal.database.migration
+
+import io.realm.DynamicRealm
+import org.matrix.android.sdk.internal.database.model.RoomSummaryEntityFields
+import org.matrix.android.sdk.internal.util.database.RealmMigrator
+
+class MigrateSessionTo001(realm: DynamicRealm) : RealmMigrator(realm, 1) {
+
+ override fun doMigrate(realm: DynamicRealm) {
+ // Add hasFailedSending in RoomSummary and a small warning icon on room list
+ realm.schema.get("RoomSummaryEntity")
+ ?.addField(RoomSummaryEntityFields.HAS_FAILED_SENDING, Boolean::class.java)
+ ?.transform { obj ->
+ obj.setBoolean(RoomSummaryEntityFields.HAS_FAILED_SENDING, false)
+ }
+ }
+}
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/migration/MigrateSessionTo002.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/migration/MigrateSessionTo002.kt
new file mode 100644
index 0000000000..215e558e2a
--- /dev/null
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/migration/MigrateSessionTo002.kt
@@ -0,0 +1,31 @@
+/*
+ * Copyright (c) 2022 The Matrix.org Foundation C.I.C.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.matrix.android.sdk.internal.database.migration
+
+import io.realm.DynamicRealm
+import org.matrix.android.sdk.internal.util.database.RealmMigrator
+
+class MigrateSessionTo002(realm: DynamicRealm) : RealmMigrator(realm, 2) {
+
+ override fun doMigrate(realm: DynamicRealm) {
+ realm.schema.get("HomeServerCapabilitiesEntity")
+ ?.addField("adminE2EByDefault", Boolean::class.java)
+ ?.transform { obj ->
+ obj.setBoolean("adminE2EByDefault", true)
+ }
+ }
+}
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/migration/MigrateSessionTo003.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/migration/MigrateSessionTo003.kt
new file mode 100644
index 0000000000..ab848d129a
--- /dev/null
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/migration/MigrateSessionTo003.kt
@@ -0,0 +1,33 @@
+/*
+ * Copyright (c) 2022 The Matrix.org Foundation C.I.C.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.matrix.android.sdk.internal.database.migration
+
+import io.realm.DynamicRealm
+import org.matrix.android.sdk.internal.database.model.HomeServerCapabilitiesEntityFields
+import org.matrix.android.sdk.internal.util.database.RealmMigrator
+
+class MigrateSessionTo003(realm: DynamicRealm) : RealmMigrator(realm, 3) {
+
+ override fun doMigrate(realm: DynamicRealm) {
+ realm.schema.get("HomeServerCapabilitiesEntity")
+ ?.addField("preferredJitsiDomain", String::class.java)
+ ?.transform { obj ->
+ // Schedule a refresh of the capabilities
+ obj.setLong(HomeServerCapabilitiesEntityFields.LAST_UPDATED_TIMESTAMP, 0)
+ }
+ }
+}
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/migration/MigrateSessionTo004.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/migration/MigrateSessionTo004.kt
new file mode 100644
index 0000000000..be13ae2c2f
--- /dev/null
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/migration/MigrateSessionTo004.kt
@@ -0,0 +1,36 @@
+/*
+ * Copyright (c) 2022 The Matrix.org Foundation C.I.C.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.matrix.android.sdk.internal.database.migration
+
+import io.realm.DynamicRealm
+import org.matrix.android.sdk.internal.database.model.PendingThreePidEntityFields
+import org.matrix.android.sdk.internal.util.database.RealmMigrator
+
+class MigrateSessionTo004(realm: DynamicRealm) : RealmMigrator(realm, 4) {
+
+ override fun doMigrate(realm: DynamicRealm) {
+ realm.schema.create("PendingThreePidEntity")
+ .addField(PendingThreePidEntityFields.CLIENT_SECRET, String::class.java)
+ .setRequired(PendingThreePidEntityFields.CLIENT_SECRET, true)
+ .addField(PendingThreePidEntityFields.EMAIL, String::class.java)
+ .addField(PendingThreePidEntityFields.MSISDN, String::class.java)
+ .addField(PendingThreePidEntityFields.SEND_ATTEMPT, Int::class.java)
+ .addField(PendingThreePidEntityFields.SID, String::class.java)
+ .setRequired(PendingThreePidEntityFields.SID, true)
+ .addField(PendingThreePidEntityFields.SUBMIT_URL, String::class.java)
+ }
+}
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/migration/MigrateSessionTo005.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/migration/MigrateSessionTo005.kt
new file mode 100644
index 0000000000..b4826b23a4
--- /dev/null
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/migration/MigrateSessionTo005.kt
@@ -0,0 +1,29 @@
+/*
+ * Copyright (c) 2022 The Matrix.org Foundation C.I.C.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.matrix.android.sdk.internal.database.migration
+
+import io.realm.DynamicRealm
+import org.matrix.android.sdk.internal.util.database.RealmMigrator
+
+class MigrateSessionTo005(realm: DynamicRealm) : RealmMigrator(realm, 5) {
+
+ override fun doMigrate(realm: DynamicRealm) {
+ realm.schema.get("HomeServerCapabilitiesEntity")
+ ?.removeField("adminE2EByDefault")
+ ?.removeField("preferredJitsiDomain")
+ }
+}
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/migration/MigrateSessionTo006.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/migration/MigrateSessionTo006.kt
new file mode 100644
index 0000000000..3d7f26ccee
--- /dev/null
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/migration/MigrateSessionTo006.kt
@@ -0,0 +1,37 @@
+/*
+ * Copyright (c) 2022 The Matrix.org Foundation C.I.C.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.matrix.android.sdk.internal.database.migration
+
+import io.realm.DynamicRealm
+import org.matrix.android.sdk.internal.database.model.PreviewUrlCacheEntityFields
+import org.matrix.android.sdk.internal.util.database.RealmMigrator
+
+class MigrateSessionTo006(realm: DynamicRealm) : RealmMigrator(realm, 6) {
+
+ override fun doMigrate(realm: DynamicRealm) {
+ realm.schema.create("PreviewUrlCacheEntity")
+ .addField(PreviewUrlCacheEntityFields.URL, String::class.java)
+ .setRequired(PreviewUrlCacheEntityFields.URL, true)
+ .addPrimaryKey(PreviewUrlCacheEntityFields.URL)
+ .addField(PreviewUrlCacheEntityFields.URL_FROM_SERVER, String::class.java)
+ .addField(PreviewUrlCacheEntityFields.SITE_NAME, String::class.java)
+ .addField(PreviewUrlCacheEntityFields.TITLE, String::class.java)
+ .addField(PreviewUrlCacheEntityFields.DESCRIPTION, String::class.java)
+ .addField(PreviewUrlCacheEntityFields.MXC_URL, String::class.java)
+ .addField(PreviewUrlCacheEntityFields.LAST_UPDATED_TIMESTAMP, Long::class.java)
+ }
+}
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/migration/MigrateSessionTo007.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/migration/MigrateSessionTo007.kt
new file mode 100644
index 0000000000..be8c8ce9c6
--- /dev/null
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/migration/MigrateSessionTo007.kt
@@ -0,0 +1,38 @@
+/*
+ * Copyright (c) 2022 The Matrix.org Foundation C.I.C.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.matrix.android.sdk.internal.database.migration
+
+import io.realm.DynamicRealm
+import org.matrix.android.sdk.internal.database.model.RoomEntityFields
+import org.matrix.android.sdk.internal.database.model.RoomMembersLoadStatusType
+import org.matrix.android.sdk.internal.util.database.RealmMigrator
+
+class MigrateSessionTo007(realm: DynamicRealm) : RealmMigrator(realm, 7) {
+
+ override fun doMigrate(realm: DynamicRealm) {
+ realm.schema.get("RoomEntity")
+ ?.addField(RoomEntityFields.MEMBERS_LOAD_STATUS_STR, String::class.java)
+ ?.transform { obj ->
+ if (obj.getBoolean("areAllMembersLoaded")) {
+ obj.setString("membersLoadStatusStr", RoomMembersLoadStatusType.LOADED.name)
+ } else {
+ obj.setString("membersLoadStatusStr", RoomMembersLoadStatusType.NONE.name)
+ }
+ }
+ ?.removeField("areAllMembersLoaded")
+ }
+}
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/migration/MigrateSessionTo008.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/migration/MigrateSessionTo008.kt
new file mode 100644
index 0000000000..d46730ef70
--- /dev/null
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/migration/MigrateSessionTo008.kt
@@ -0,0 +1,47 @@
+/*
+ * Copyright (c) 2022 The Matrix.org Foundation C.I.C.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.matrix.android.sdk.internal.database.migration
+
+import io.realm.DynamicRealm
+import org.matrix.android.sdk.internal.database.model.EditAggregatedSummaryEntityFields
+import org.matrix.android.sdk.internal.database.model.EditionOfEventFields
+import org.matrix.android.sdk.internal.util.database.RealmMigrator
+
+class MigrateSessionTo008(realm: DynamicRealm) : RealmMigrator(realm, 8) {
+
+ override fun doMigrate(realm: DynamicRealm) {
+ val editionOfEventSchema = realm.schema.create("EditionOfEvent")
+ .addField(EditionOfEventFields.CONTENT, String::class.java)
+ .addField(EditionOfEventFields.EVENT_ID, String::class.java)
+ .setRequired(EditionOfEventFields.EVENT_ID, true)
+ .addField(EditionOfEventFields.SENDER_ID, String::class.java)
+ .setRequired(EditionOfEventFields.SENDER_ID, true)
+ .addField(EditionOfEventFields.TIMESTAMP, Long::class.java)
+ .addField(EditionOfEventFields.IS_LOCAL_ECHO, Boolean::class.java)
+
+ realm.schema.get("EditAggregatedSummaryEntity")
+ ?.removeField("aggregatedContent")
+ ?.removeField("sourceEvents")
+ ?.removeField("lastEditTs")
+ ?.removeField("sourceLocalEchoEvents")
+ ?.addRealmListField(EditAggregatedSummaryEntityFields.EDITIONS.`$`, editionOfEventSchema)
+
+ // This has to be done once a parent use the model as a child
+ // See https://github.com/realm/realm-java/issues/7402
+ editionOfEventSchema.isEmbedded = true
+ }
+}
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/migration/MigrateSessionTo009.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/migration/MigrateSessionTo009.kt
new file mode 100644
index 0000000000..370430b9e3
--- /dev/null
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/migration/MigrateSessionTo009.kt
@@ -0,0 +1,65 @@
+/*
+ * Copyright (c) 2022 The Matrix.org Foundation C.I.C.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.matrix.android.sdk.internal.database.migration
+
+import io.realm.DynamicRealm
+import io.realm.FieldAttribute
+import org.matrix.android.sdk.api.session.room.model.tag.RoomTag
+import org.matrix.android.sdk.internal.database.model.EventEntityFields
+import org.matrix.android.sdk.internal.database.model.RoomSummaryEntityFields
+import org.matrix.android.sdk.internal.database.model.RoomTagEntityFields
+import org.matrix.android.sdk.internal.database.model.TimelineEventEntityFields
+import org.matrix.android.sdk.internal.util.database.RealmMigrator
+
+class MigrateSessionTo009(realm: DynamicRealm) : RealmMigrator(realm, 9) {
+
+ override fun doMigrate(realm: DynamicRealm) {
+ realm.schema.get("RoomSummaryEntity")
+ ?.addField(RoomSummaryEntityFields.LAST_ACTIVITY_TIME, Long::class.java, FieldAttribute.INDEXED)
+ ?.setNullable(RoomSummaryEntityFields.LAST_ACTIVITY_TIME, true)
+ ?.addIndex(RoomSummaryEntityFields.MEMBERSHIP_STR)
+ ?.addIndex(RoomSummaryEntityFields.IS_DIRECT)
+ ?.addIndex(RoomSummaryEntityFields.VERSIONING_STATE_STR)
+
+ ?.addField(RoomSummaryEntityFields.IS_FAVOURITE, Boolean::class.java)
+ ?.addIndex(RoomSummaryEntityFields.IS_FAVOURITE)
+ ?.addField(RoomSummaryEntityFields.IS_LOW_PRIORITY, Boolean::class.java)
+ ?.addIndex(RoomSummaryEntityFields.IS_LOW_PRIORITY)
+ ?.addField(RoomSummaryEntityFields.IS_SERVER_NOTICE, Boolean::class.java)
+ ?.addIndex(RoomSummaryEntityFields.IS_SERVER_NOTICE)
+
+ ?.transform { obj ->
+ val isFavorite = obj.getList(RoomSummaryEntityFields.TAGS.`$`).any {
+ it.getString(RoomTagEntityFields.TAG_NAME) == RoomTag.ROOM_TAG_FAVOURITE
+ }
+ obj.setBoolean(RoomSummaryEntityFields.IS_FAVOURITE, isFavorite)
+
+ val isLowPriority = obj.getList(RoomSummaryEntityFields.TAGS.`$`).any {
+ it.getString(RoomTagEntityFields.TAG_NAME) == RoomTag.ROOM_TAG_LOW_PRIORITY
+ }
+
+ obj.setBoolean(RoomSummaryEntityFields.IS_LOW_PRIORITY, isLowPriority)
+
+// XXX migrate last message origin server ts
+ obj.getObject(RoomSummaryEntityFields.LATEST_PREVIEWABLE_EVENT.`$`)
+ ?.getObject(TimelineEventEntityFields.ROOT.`$`)
+ ?.getLong(EventEntityFields.ORIGIN_SERVER_TS)?.let {
+ obj.setLong(RoomSummaryEntityFields.LAST_ACTIVITY_TIME, it)
+ }
+ }
+ }
+}
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/migration/MigrateSessionTo010.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/migration/MigrateSessionTo010.kt
new file mode 100644
index 0000000000..b968862d10
--- /dev/null
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/migration/MigrateSessionTo010.kt
@@ -0,0 +1,70 @@
+/*
+ * Copyright (c) 2022 The Matrix.org Foundation C.I.C.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.matrix.android.sdk.internal.database.migration
+
+import io.realm.DynamicRealm
+import org.matrix.android.sdk.api.session.events.model.EventType
+import org.matrix.android.sdk.api.session.room.model.create.RoomCreateContent
+import org.matrix.android.sdk.internal.database.model.CurrentStateEventEntityFields
+import org.matrix.android.sdk.internal.database.model.EventEntityFields
+import org.matrix.android.sdk.internal.database.model.RoomSummaryEntityFields
+import org.matrix.android.sdk.internal.database.model.SpaceChildSummaryEntityFields
+import org.matrix.android.sdk.internal.database.model.SpaceParentSummaryEntityFields
+import org.matrix.android.sdk.internal.di.MoshiProvider
+import org.matrix.android.sdk.internal.util.database.RealmMigrator
+
+class MigrateSessionTo010(realm: DynamicRealm) : RealmMigrator(realm, 10) {
+
+ override fun doMigrate(realm: DynamicRealm) {
+ realm.schema.create("SpaceChildSummaryEntity")
+ ?.addField(SpaceChildSummaryEntityFields.ORDER, String::class.java)
+ ?.addField(SpaceChildSummaryEntityFields.CHILD_ROOM_ID, String::class.java)
+ ?.addField(SpaceChildSummaryEntityFields.AUTO_JOIN, Boolean::class.java)
+ ?.setNullable(SpaceChildSummaryEntityFields.AUTO_JOIN, true)
+ ?.addRealmObjectField(SpaceChildSummaryEntityFields.CHILD_SUMMARY_ENTITY.`$`, realm.schema.get("RoomSummaryEntity")!!)
+ ?.addRealmListField(SpaceChildSummaryEntityFields.VIA_SERVERS.`$`, String::class.java)
+
+ realm.schema.create("SpaceParentSummaryEntity")
+ ?.addField(SpaceParentSummaryEntityFields.PARENT_ROOM_ID, String::class.java)
+ ?.addField(SpaceParentSummaryEntityFields.CANONICAL, Boolean::class.java)
+ ?.setNullable(SpaceParentSummaryEntityFields.CANONICAL, true)
+ ?.addRealmObjectField(SpaceParentSummaryEntityFields.PARENT_SUMMARY_ENTITY.`$`, realm.schema.get("RoomSummaryEntity")!!)
+ ?.addRealmListField(SpaceParentSummaryEntityFields.VIA_SERVERS.`$`, String::class.java)
+
+ val creationContentAdapter = MoshiProvider.providesMoshi().adapter(RoomCreateContent::class.java)
+ realm.schema.get("RoomSummaryEntity")
+ ?.addField(RoomSummaryEntityFields.ROOM_TYPE, String::class.java)
+ ?.addField(RoomSummaryEntityFields.FLATTEN_PARENT_IDS, String::class.java)
+ ?.addField(RoomSummaryEntityFields.GROUP_IDS, String::class.java)
+ ?.transform { obj ->
+
+ val creationEvent = realm.where("CurrentStateEventEntity")
+ .equalTo(CurrentStateEventEntityFields.ROOM_ID, obj.getString(RoomSummaryEntityFields.ROOM_ID))
+ .equalTo(CurrentStateEventEntityFields.TYPE, EventType.STATE_ROOM_CREATE)
+ .findFirst()
+
+ val roomType = creationEvent?.getObject(CurrentStateEventEntityFields.ROOT.`$`)
+ ?.getString(EventEntityFields.CONTENT)?.let {
+ creationContentAdapter.fromJson(it)?.type
+ }
+
+ obj.setString(RoomSummaryEntityFields.ROOM_TYPE, roomType)
+ }
+ ?.addRealmListField(RoomSummaryEntityFields.PARENTS.`$`, realm.schema.get("SpaceParentSummaryEntity")!!)
+ ?.addRealmListField(RoomSummaryEntityFields.CHILDREN.`$`, realm.schema.get("SpaceChildSummaryEntity")!!)
+ }
+}
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/migration/MigrateSessionTo011.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/migration/MigrateSessionTo011.kt
new file mode 100644
index 0000000000..92ee26df42
--- /dev/null
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/migration/MigrateSessionTo011.kt
@@ -0,0 +1,29 @@
+/*
+ * Copyright (c) 2022 The Matrix.org Foundation C.I.C.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.matrix.android.sdk.internal.database.migration
+
+import io.realm.DynamicRealm
+import org.matrix.android.sdk.internal.database.model.EventEntityFields
+import org.matrix.android.sdk.internal.util.database.RealmMigrator
+
+class MigrateSessionTo011(realm: DynamicRealm) : RealmMigrator(realm, 11) {
+
+ override fun doMigrate(realm: DynamicRealm) {
+ realm.schema.get("EventEntity")
+ ?.addField(EventEntityFields.SEND_STATE_DETAILS, String::class.java)
+ }
+}
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/migration/MigrateSessionTo012.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/migration/MigrateSessionTo012.kt
new file mode 100644
index 0000000000..a914cadd80
--- /dev/null
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/migration/MigrateSessionTo012.kt
@@ -0,0 +1,53 @@
+/*
+ * Copyright (c) 2022 The Matrix.org Foundation C.I.C.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.matrix.android.sdk.internal.database.migration
+
+import io.realm.DynamicRealm
+import org.matrix.android.sdk.api.session.events.model.EventType
+import org.matrix.android.sdk.api.session.room.model.RoomJoinRulesContent
+import org.matrix.android.sdk.internal.database.model.CurrentStateEventEntityFields
+import org.matrix.android.sdk.internal.database.model.EventEntityFields
+import org.matrix.android.sdk.internal.database.model.RoomSummaryEntityFields
+import org.matrix.android.sdk.internal.database.model.SpaceChildSummaryEntityFields
+import org.matrix.android.sdk.internal.di.MoshiProvider
+import org.matrix.android.sdk.internal.util.database.RealmMigrator
+
+class MigrateSessionTo012(realm: DynamicRealm) : RealmMigrator(realm, 12) {
+
+ override fun doMigrate(realm: DynamicRealm) {
+ val joinRulesContentAdapter = MoshiProvider.providesMoshi().adapter(RoomJoinRulesContent::class.java)
+ realm.schema.get("RoomSummaryEntity")
+ ?.addField(RoomSummaryEntityFields.JOIN_RULES_STR, String::class.java)
+ ?.transform { obj ->
+ val joinRulesEvent = realm.where("CurrentStateEventEntity")
+ .equalTo(CurrentStateEventEntityFields.ROOM_ID, obj.getString(RoomSummaryEntityFields.ROOM_ID))
+ .equalTo(CurrentStateEventEntityFields.TYPE, EventType.STATE_ROOM_JOIN_RULES)
+ .findFirst()
+
+ val roomJoinRules = joinRulesEvent?.getObject(CurrentStateEventEntityFields.ROOT.`$`)
+ ?.getString(EventEntityFields.CONTENT)?.let {
+ joinRulesContentAdapter.fromJson(it)?.joinRules
+ }
+
+ obj.setString(RoomSummaryEntityFields.JOIN_RULES_STR, roomJoinRules?.name)
+ }
+
+ realm.schema.get("SpaceChildSummaryEntity")
+ ?.addField(SpaceChildSummaryEntityFields.SUGGESTED, Boolean::class.java)
+ ?.setNullable(SpaceChildSummaryEntityFields.SUGGESTED, true)
+ }
+}
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/migration/MigrateSessionTo013.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/migration/MigrateSessionTo013.kt
new file mode 100644
index 0000000000..2ea0303802
--- /dev/null
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/migration/MigrateSessionTo013.kt
@@ -0,0 +1,32 @@
+/*
+ * Copyright (c) 2022 The Matrix.org Foundation C.I.C.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.matrix.android.sdk.internal.database.migration
+
+import io.realm.DynamicRealm
+import org.matrix.android.sdk.internal.database.model.SpaceChildSummaryEntityFields
+import org.matrix.android.sdk.internal.util.database.RealmMigrator
+
+class MigrateSessionTo013(realm: DynamicRealm) : RealmMigrator(realm, 13) {
+
+ override fun doMigrate(realm: DynamicRealm) {
+ // Fix issue with the nightly build. Eventually play again the migration which has been included in migrateTo12()
+ realm.schema.get("SpaceChildSummaryEntity")
+ ?.takeIf { !it.hasField(SpaceChildSummaryEntityFields.SUGGESTED) }
+ ?.addField(SpaceChildSummaryEntityFields.SUGGESTED, Boolean::class.java)
+ ?.setNullable(SpaceChildSummaryEntityFields.SUGGESTED, true)
+ }
+}
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/migration/MigrateSessionTo014.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/migration/MigrateSessionTo014.kt
new file mode 100644
index 0000000000..c524b6f284
--- /dev/null
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/migration/MigrateSessionTo014.kt
@@ -0,0 +1,46 @@
+/*
+ * Copyright (c) 2022 The Matrix.org Foundation C.I.C.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.matrix.android.sdk.internal.database.migration
+
+import io.realm.DynamicRealm
+import io.realm.FieldAttribute
+import org.matrix.android.sdk.api.session.room.model.VersioningState
+import org.matrix.android.sdk.internal.database.model.RoomAccountDataEntityFields
+import org.matrix.android.sdk.internal.database.model.RoomEntityFields
+import org.matrix.android.sdk.internal.database.model.RoomSummaryEntityFields
+import org.matrix.android.sdk.internal.util.database.RealmMigrator
+
+class MigrateSessionTo014(realm: DynamicRealm) : RealmMigrator(realm, 14) {
+
+ override fun doMigrate(realm: DynamicRealm) {
+ val roomAccountDataSchema = realm.schema.create("RoomAccountDataEntity")
+ .addField(RoomAccountDataEntityFields.CONTENT_STR, String::class.java)
+ .addField(RoomAccountDataEntityFields.TYPE, String::class.java, FieldAttribute.INDEXED)
+
+ realm.schema.get("RoomEntity")
+ ?.addRealmListField(RoomEntityFields.ACCOUNT_DATA.`$`, roomAccountDataSchema)
+
+ realm.schema.get("RoomSummaryEntity")
+ ?.addField(RoomSummaryEntityFields.IS_HIDDEN_FROM_USER, Boolean::class.java, FieldAttribute.INDEXED)
+ ?.transform {
+ val isHiddenFromUser = it.getString(RoomSummaryEntityFields.VERSIONING_STATE_STR) == VersioningState.UPGRADED_ROOM_JOINED.name
+ it.setBoolean(RoomSummaryEntityFields.IS_HIDDEN_FROM_USER, isHiddenFromUser)
+ }
+
+ roomAccountDataSchema.isEmbedded = true
+ }
+}
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/migration/MigrateSessionTo015.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/migration/MigrateSessionTo015.kt
new file mode 100644
index 0000000000..329964a9a4
--- /dev/null
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/migration/MigrateSessionTo015.kt
@@ -0,0 +1,38 @@
+/*
+ * Copyright (c) 2022 The Matrix.org Foundation C.I.C.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.matrix.android.sdk.internal.database.migration
+
+import io.realm.DynamicRealm
+import org.matrix.android.sdk.api.session.room.model.Membership
+import org.matrix.android.sdk.internal.database.model.RoomSummaryEntityFields
+import org.matrix.android.sdk.internal.query.process
+import org.matrix.android.sdk.internal.util.database.RealmMigrator
+
+class MigrateSessionTo015(realm: DynamicRealm) : RealmMigrator(realm, 15) {
+
+ override fun doMigrate(realm: DynamicRealm) {
+ // fix issue with flattenParentIds on DM that kept growing with duplicate
+ // so we reset it, will be updated next sync
+ realm.where("RoomSummaryEntity")
+ .process(RoomSummaryEntityFields.MEMBERSHIP_STR, Membership.activeMemberships())
+ .equalTo(RoomSummaryEntityFields.IS_DIRECT, true)
+ .findAll()
+ .onEach {
+ it.setString(RoomSummaryEntityFields.FLATTEN_PARENT_IDS, null)
+ }
+ }
+}
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/migration/MigrateSessionTo016.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/migration/MigrateSessionTo016.kt
new file mode 100644
index 0000000000..e6b3d4a463
--- /dev/null
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/migration/MigrateSessionTo016.kt
@@ -0,0 +1,33 @@
+/*
+ * Copyright (c) 2022 The Matrix.org Foundation C.I.C.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.matrix.android.sdk.internal.database.migration
+
+import io.realm.DynamicRealm
+import org.matrix.android.sdk.internal.database.model.HomeServerCapabilitiesEntityFields
+import org.matrix.android.sdk.internal.util.database.RealmMigrator
+
+class MigrateSessionTo016(realm: DynamicRealm) : RealmMigrator(realm, 16) {
+
+ override fun doMigrate(realm: DynamicRealm) {
+ realm.schema.get("HomeServerCapabilitiesEntity")
+ ?.addField(HomeServerCapabilitiesEntityFields.ROOM_VERSIONS_JSON, String::class.java)
+ ?.transform { obj ->
+ // Schedule a refresh of the capabilities
+ obj.setLong(HomeServerCapabilitiesEntityFields.LAST_UPDATED_TIMESTAMP, 0)
+ }
+ }
+}
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/migration/MigrateSessionTo017.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/migration/MigrateSessionTo017.kt
new file mode 100644
index 0000000000..95d67b9ad8
--- /dev/null
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/migration/MigrateSessionTo017.kt
@@ -0,0 +1,29 @@
+/*
+ * Copyright (c) 2022 The Matrix.org Foundation C.I.C.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.matrix.android.sdk.internal.database.migration
+
+import io.realm.DynamicRealm
+import org.matrix.android.sdk.internal.database.model.EventInsertEntityFields
+import org.matrix.android.sdk.internal.util.database.RealmMigrator
+
+class MigrateSessionTo017(realm: DynamicRealm) : RealmMigrator(realm, 17) {
+
+ override fun doMigrate(realm: DynamicRealm) {
+ realm.schema.get("EventInsertEntity")
+ ?.addField(EventInsertEntityFields.CAN_BE_PROCESSED, Boolean::class.java)
+ }
+}
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/migration/MigrateSessionTo018.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/migration/MigrateSessionTo018.kt
new file mode 100644
index 0000000000..b415c51d4b
--- /dev/null
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/migration/MigrateSessionTo018.kt
@@ -0,0 +1,48 @@
+/*
+ * Copyright (c) 2022 The Matrix.org Foundation C.I.C.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.matrix.android.sdk.internal.database.migration
+
+import io.realm.DynamicRealm
+import org.matrix.android.sdk.internal.database.model.RoomMemberSummaryEntityFields
+import org.matrix.android.sdk.internal.database.model.RoomSummaryEntityFields
+import org.matrix.android.sdk.internal.database.model.presence.UserPresenceEntityFields
+import org.matrix.android.sdk.internal.util.database.RealmMigrator
+
+class MigrateSessionTo018(realm: DynamicRealm) : RealmMigrator(realm, 18) {
+
+ override fun doMigrate(realm: DynamicRealm) {
+ realm.schema.create("UserPresenceEntity")
+ ?.addField(UserPresenceEntityFields.USER_ID, String::class.java)
+ ?.addPrimaryKey(UserPresenceEntityFields.USER_ID)
+ ?.setRequired(UserPresenceEntityFields.USER_ID, true)
+ ?.addField(UserPresenceEntityFields.PRESENCE_STR, String::class.java)
+ ?.addField(UserPresenceEntityFields.LAST_ACTIVE_AGO, Long::class.java)
+ ?.setNullable(UserPresenceEntityFields.LAST_ACTIVE_AGO, true)
+ ?.addField(UserPresenceEntityFields.STATUS_MESSAGE, String::class.java)
+ ?.addField(UserPresenceEntityFields.IS_CURRENTLY_ACTIVE, Boolean::class.java)
+ ?.setNullable(UserPresenceEntityFields.IS_CURRENTLY_ACTIVE, true)
+ ?.addField(UserPresenceEntityFields.AVATAR_URL, String::class.java)
+ ?.addField(UserPresenceEntityFields.DISPLAY_NAME, String::class.java)
+
+ val userPresenceEntity = realm.schema.get("UserPresenceEntity") ?: return
+ realm.schema.get("RoomSummaryEntity")
+ ?.addRealmObjectField(RoomSummaryEntityFields.DIRECT_USER_PRESENCE.`$`, userPresenceEntity)
+
+ realm.schema.get("RoomMemberSummaryEntity")
+ ?.addRealmObjectField(RoomMemberSummaryEntityFields.USER_PRESENCE_ENTITY.`$`, userPresenceEntity)
+ }
+}
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/migration/MigrateSessionTo019.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/migration/MigrateSessionTo019.kt
new file mode 100644
index 0000000000..d0b368be46
--- /dev/null
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/migration/MigrateSessionTo019.kt
@@ -0,0 +1,37 @@
+/*
+ * Copyright (c) 2022 The Matrix.org Foundation C.I.C.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.matrix.android.sdk.internal.database.migration
+
+import io.realm.DynamicRealm
+import org.matrix.android.sdk.internal.database.model.RoomSummaryEntityFields
+import org.matrix.android.sdk.internal.util.Normalizer
+import org.matrix.android.sdk.internal.util.database.RealmMigrator
+
+class MigrateSessionTo019(realm: DynamicRealm,
+ private val normalizer: Normalizer) : RealmMigrator(realm, 19) {
+
+ override fun doMigrate(realm: DynamicRealm) {
+ 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/migration/MigrateSessionTo020.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/migration/MigrateSessionTo020.kt
new file mode 100644
index 0000000000..c7f6e3ceed
--- /dev/null
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/migration/MigrateSessionTo020.kt
@@ -0,0 +1,45 @@
+/*
+ * Copyright (c) 2022 The Matrix.org Foundation C.I.C.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.matrix.android.sdk.internal.database.migration
+
+import io.realm.DynamicRealm
+import org.matrix.android.sdk.internal.database.model.ChunkEntityFields
+import org.matrix.android.sdk.internal.util.database.RealmMigrator
+
+class MigrateSessionTo020(realm: DynamicRealm) : RealmMigrator(realm, 20) {
+
+ override fun doMigrate(realm: DynamicRealm) {
+ realm.schema.get("ChunkEntity")?.apply {
+ if (hasField("numberOfTimelineEvents")) {
+ removeField("numberOfTimelineEvents")
+ }
+ var cleanOldChunks = false
+ if (!hasField(ChunkEntityFields.NEXT_CHUNK.`$`)) {
+ cleanOldChunks = true
+ addRealmObjectField(ChunkEntityFields.NEXT_CHUNK.`$`, this)
+ }
+ if (!hasField(ChunkEntityFields.PREV_CHUNK.`$`)) {
+ cleanOldChunks = true
+ addRealmObjectField(ChunkEntityFields.PREV_CHUNK.`$`, this)
+ }
+ if (cleanOldChunks) {
+ val chunkEntities = realm.where("ChunkEntity").equalTo(ChunkEntityFields.IS_LAST_FORWARD, false).findAll()
+ chunkEntities.deleteAllFromRealm()
+ }
+ }
+ }
+}
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/migration/MigrateSessionTo021.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/migration/MigrateSessionTo021.kt
new file mode 100644
index 0000000000..6b6952e697
--- /dev/null
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/migration/MigrateSessionTo021.kt
@@ -0,0 +1,55 @@
+/*
+ * Copyright (c) 2022 The Matrix.org Foundation C.I.C.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.matrix.android.sdk.internal.database.migration
+
+import io.realm.DynamicRealm
+import org.matrix.android.sdk.api.session.events.model.EventType
+import org.matrix.android.sdk.internal.crypto.model.event.EncryptionEventContent
+import org.matrix.android.sdk.internal.database.model.CurrentStateEventEntityFields
+import org.matrix.android.sdk.internal.database.model.EventEntityFields
+import org.matrix.android.sdk.internal.database.model.RoomSummaryEntityFields
+import org.matrix.android.sdk.internal.di.MoshiProvider
+import org.matrix.android.sdk.internal.util.database.RealmMigrator
+
+class MigrateSessionTo021(realm: DynamicRealm) : RealmMigrator(realm, 21) {
+
+ override fun doMigrate(realm: DynamicRealm) {
+ realm.schema.get("RoomSummaryEntity")
+ ?.addField(RoomSummaryEntityFields.E2E_ALGORITHM, String::class.java)
+ ?.transform { obj ->
+
+ val encryptionContentAdapter = MoshiProvider.providesMoshi().adapter(EncryptionEventContent::class.java)
+
+ val encryptionEvent = realm.where("CurrentStateEventEntity")
+ .equalTo(CurrentStateEventEntityFields.ROOM_ID, obj.getString(RoomSummaryEntityFields.ROOM_ID))
+ .equalTo(CurrentStateEventEntityFields.TYPE, EventType.STATE_ROOM_ENCRYPTION)
+ .findFirst()
+
+ val encryptionEventRoot = encryptionEvent?.getObject(CurrentStateEventEntityFields.ROOT.`$`)
+ val algorithm = encryptionEventRoot
+ ?.getString(EventEntityFields.CONTENT)?.let {
+ encryptionContentAdapter.fromJson(it)?.algorithm
+ }
+
+ obj.setString(RoomSummaryEntityFields.E2E_ALGORITHM, algorithm)
+ obj.setBoolean(RoomSummaryEntityFields.IS_ENCRYPTED, encryptionEvent != null)
+ encryptionEventRoot?.getLong(EventEntityFields.ORIGIN_SERVER_TS)?.let {
+ obj.setLong(RoomSummaryEntityFields.ENCRYPTION_EVENT_TS, it)
+ }
+ }
+ }
+}
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/migration/MigrateSessionTo022.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/migration/MigrateSessionTo022.kt
new file mode 100644
index 0000000000..e78a9d05da
--- /dev/null
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/migration/MigrateSessionTo022.kt
@@ -0,0 +1,42 @@
+/*
+ * Copyright (c) 2022 The Matrix.org Foundation C.I.C.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.matrix.android.sdk.internal.database.migration
+
+import io.realm.DynamicRealm
+import org.matrix.android.sdk.api.session.room.model.Membership
+import org.matrix.android.sdk.internal.database.model.CurrentStateEventEntityFields
+import org.matrix.android.sdk.internal.database.model.RoomEntityFields
+import org.matrix.android.sdk.internal.util.database.RealmMigrator
+import timber.log.Timber
+
+class MigrateSessionTo022(realm: DynamicRealm) : RealmMigrator(realm, 22) {
+
+ override fun doMigrate(realm: DynamicRealm) {
+ val listJoinedRoomIds = realm.where("RoomEntity")
+ .equalTo(RoomEntityFields.MEMBERSHIP_STR, Membership.JOIN.name).findAll()
+ .map { it.getString(RoomEntityFields.ROOM_ID) }
+
+ val hasMissingStateEvent = realm.where("CurrentStateEventEntity")
+ .`in`(CurrentStateEventEntityFields.ROOM_ID, listJoinedRoomIds.toTypedArray())
+ .isNull(CurrentStateEventEntityFields.ROOT.`$`).findFirst() != null
+
+ if (hasMissingStateEvent) {
+ Timber.v("Has some missing state event, clear session cache")
+ realm.deleteAll()
+ }
+ }
+}
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/migration/MigrateSessionTo023.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/migration/MigrateSessionTo023.kt
new file mode 100644
index 0000000000..0bb8ceeaa5
--- /dev/null
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/migration/MigrateSessionTo023.kt
@@ -0,0 +1,40 @@
+/*
+ * Copyright (c) 2022 The Matrix.org Foundation C.I.C.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.matrix.android.sdk.internal.database.migration
+
+import io.realm.DynamicRealm
+import io.realm.FieldAttribute
+import org.matrix.android.sdk.api.session.threads.ThreadNotificationState
+import org.matrix.android.sdk.internal.database.model.EventEntityFields
+import org.matrix.android.sdk.internal.util.database.RealmMigrator
+
+class MigrateSessionTo023(realm: DynamicRealm) : RealmMigrator(realm, 23) {
+
+ override fun doMigrate(realm: DynamicRealm) {
+ val eventEntity = realm.schema.get("TimelineEventEntity") ?: return
+
+ realm.schema.get("EventEntity")
+ ?.addField(EventEntityFields.IS_ROOT_THREAD, Boolean::class.java, FieldAttribute.INDEXED)
+ ?.addField(EventEntityFields.ROOT_THREAD_EVENT_ID, String::class.java, FieldAttribute.INDEXED)
+ ?.addField(EventEntityFields.NUMBER_OF_THREADS, Int::class.java)
+ ?.addField(EventEntityFields.THREAD_NOTIFICATION_STATE_STR, String::class.java)
+ ?.transform {
+ it.setString(EventEntityFields.THREAD_NOTIFICATION_STATE_STR, ThreadNotificationState.NO_NEW_MESSAGE.name)
+ }
+ ?.addRealmObjectField(EventEntityFields.THREAD_SUMMARY_LATEST_MESSAGE.`$`, eventEntity)
+ }
+}
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/migration/MigrateSessionTo024.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/migration/MigrateSessionTo024.kt
new file mode 100644
index 0000000000..ff88972566
--- /dev/null
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/migration/MigrateSessionTo024.kt
@@ -0,0 +1,32 @@
+/*
+ * Copyright (c) 2022 The Matrix.org Foundation C.I.C.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.matrix.android.sdk.internal.database.migration
+
+import io.realm.DynamicRealm
+import org.matrix.android.sdk.internal.database.model.PreviewUrlCacheEntityFields
+import org.matrix.android.sdk.internal.util.database.RealmMigrator
+
+class MigrateSessionTo024(realm: DynamicRealm) : RealmMigrator(realm, 24) {
+
+ override fun doMigrate(realm: DynamicRealm) {
+ realm.schema.get("PreviewUrlCacheEntity")
+ ?.addField(PreviewUrlCacheEntityFields.IMAGE_WIDTH, Int::class.java)
+ ?.setNullable(PreviewUrlCacheEntityFields.IMAGE_WIDTH, true)
+ ?.addField(PreviewUrlCacheEntityFields.IMAGE_HEIGHT, Int::class.java)
+ ?.setNullable(PreviewUrlCacheEntityFields.IMAGE_HEIGHT, true)
+ }
+}
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/legacy/DefaultLegacySessionImporter.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/legacy/DefaultLegacySessionImporter.kt
index 445b6be8e8..22085e30fc 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/legacy/DefaultLegacySessionImporter.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/legacy/DefaultLegacySessionImporter.kt
@@ -42,7 +42,8 @@ import org.matrix.android.sdk.internal.legacy.riot.HomeServerConnectionConfig as
internal class DefaultLegacySessionImporter @Inject constructor(
private val context: Context,
private val sessionParamsStore: SessionParamsStore,
- private val realmKeysUtils: RealmKeysUtils
+ private val realmKeysUtils: RealmKeysUtils,
+ private val realmCryptoStoreMigration: RealmCryptoStoreMigration
) : LegacySessionImporter {
private val loginStorage = LoginStorage(context)
@@ -170,8 +171,8 @@ internal class DefaultLegacySessionImporter @Inject constructor(
.directory(File(context.filesDir, userMd5))
.name("crypto_store.realm")
.modules(RealmCryptoStoreModule())
- .schemaVersion(RealmCryptoStoreMigration.CRYPTO_STORE_SCHEMA_VERSION)
- .migration(RealmCryptoStoreMigration)
+ .schemaVersion(realmCryptoStoreMigration.schemaVersion)
+ .migration(realmCryptoStoreMigration)
.build()
Timber.d("Migration: copy DB to encrypted DB")
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/raw/GlobalRealmMigration.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/raw/GlobalRealmMigration.kt
index 49bcc72181..8dffac5fa0 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/raw/GlobalRealmMigration.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/raw/GlobalRealmMigration.kt
@@ -18,24 +18,23 @@ package org.matrix.android.sdk.internal.raw
import io.realm.DynamicRealm
import io.realm.RealmMigration
-import org.matrix.android.sdk.internal.database.model.KnownServerUrlEntityFields
+import org.matrix.android.sdk.internal.raw.migration.MigrateGlobalTo001
import timber.log.Timber
+import javax.inject.Inject
-internal object GlobalRealmMigration : RealmMigration {
+internal class GlobalRealmMigration @Inject constructor() : RealmMigration {
+ /**
+ * Forces all GlobalRealmMigration instances to be equal
+ * Avoids Realm throwing when multiple instances of the migration are set
+ */
+ override fun equals(other: Any?) = other is GlobalRealmMigration
+ override fun hashCode() = 2000
- // Current schema version
- const val SCHEMA_VERSION = 1L
+ val schemaVersion = 1L
override fun migrate(realm: DynamicRealm, oldVersion: Long, newVersion: Long) {
- Timber.d("Migrating Auth Realm from $oldVersion to $newVersion")
+ Timber.d("Migrating Global Realm from $oldVersion to $newVersion")
- if (oldVersion <= 0) migrateTo1(realm)
- }
-
- private fun migrateTo1(realm: DynamicRealm) {
- realm.schema.create("KnownServerUrlEntity")
- .addField(KnownServerUrlEntityFields.URL, String::class.java)
- .addPrimaryKey(KnownServerUrlEntityFields.URL)
- .setRequired(KnownServerUrlEntityFields.URL, true)
+ if (oldVersion < 1) MigrateGlobalTo001(realm).perform()
}
}
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/raw/RawModule.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/raw/RawModule.kt
index 50721b809a..a830976671 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/raw/RawModule.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/raw/RawModule.kt
@@ -51,14 +51,15 @@ internal abstract class RawModule {
@Provides
@GlobalDatabase
@MatrixScope
- fun providesRealmConfiguration(realmKeysUtils: RealmKeysUtils): RealmConfiguration {
+ fun providesRealmConfiguration(realmKeysUtils: RealmKeysUtils,
+ globalRealmMigration: GlobalRealmMigration): RealmConfiguration {
return RealmConfiguration.Builder()
.apply {
realmKeysUtils.configureEncryption(this, DB_ALIAS)
}
.name("matrix-sdk-global.realm")
- .schemaVersion(GlobalRealmMigration.SCHEMA_VERSION)
- .migration(GlobalRealmMigration)
+ .schemaVersion(globalRealmMigration.schemaVersion)
+ .migration(globalRealmMigration)
.allowWritesOnUiThread(true)
.modules(GlobalRealmModule())
.build()
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/raw/migration/MigrateGlobalTo001.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/raw/migration/MigrateGlobalTo001.kt
new file mode 100644
index 0000000000..cff2f7b8e8
--- /dev/null
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/raw/migration/MigrateGlobalTo001.kt
@@ -0,0 +1,31 @@
+/*
+ * Copyright (c) 2022 The Matrix.org Foundation C.I.C.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.matrix.android.sdk.internal.raw.migration
+
+import io.realm.DynamicRealm
+import org.matrix.android.sdk.internal.database.model.KnownServerUrlEntityFields
+import org.matrix.android.sdk.internal.util.database.RealmMigrator
+
+class MigrateGlobalTo001(realm: DynamicRealm) : RealmMigrator(realm, 1) {
+
+ override fun doMigrate(realm: DynamicRealm) {
+ realm.schema.create("KnownServerUrlEntity")
+ .addField(KnownServerUrlEntityFields.URL, String::class.java)
+ .addPrimaryKey(KnownServerUrlEntityFields.URL)
+ .setRequired(KnownServerUrlEntityFields.URL, true)
+ }
+}
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/cleanup/CleanupSession.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/cleanup/CleanupSession.kt
index c42141a0aa..44fff45917 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/cleanup/CleanupSession.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/cleanup/CleanupSession.kt
@@ -94,12 +94,12 @@ internal class CleanupSession @Inject constructor(
do {
val sessionRealmCount = Realm.getGlobalInstanceCount(realmSessionConfiguration)
val cryptoRealmCount = Realm.getGlobalInstanceCount(realmCryptoConfiguration)
- Timber.d("Wait for all Realm instance to be closed ($sessionRealmCount - $cryptoRealmCount)")
if (sessionRealmCount > 0 || cryptoRealmCount > 0) {
- Timber.d("Waiting ${TIME_TO_WAIT_MILLIS}ms")
+ Timber.d("Waiting ${TIME_TO_WAIT_MILLIS}ms for all Realm instance to be closed ($sessionRealmCount - $cryptoRealmCount)")
delay(TIME_TO_WAIT_MILLIS)
timeToWaitMillis -= TIME_TO_WAIT_MILLIS
} else {
+ Timber.d("Finished waiting for all Realm instance to be closed ($sessionRealmCount - $cryptoRealmCount)")
timeToWaitMillis = 0
}
} while (timeToWaitMillis > 0)
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 65794e6b14..4e9d7dc7f7 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
@@ -60,6 +60,7 @@ internal abstract class IdentityModule {
@IdentityDatabase
@SessionScope
fun providesIdentityRealmConfiguration(realmKeysUtils: RealmKeysUtils,
+ realmIdentityStoreMigration: RealmIdentityStoreMigration,
@SessionFilesDirectory directory: File,
@UserMd5 userMd5: String): RealmConfiguration {
return RealmConfiguration.Builder()
@@ -68,8 +69,8 @@ internal abstract class IdentityModule {
.apply {
realmKeysUtils.configureEncryption(this, SessionModule.getKeyAlias(userMd5))
}
- .schemaVersion(RealmIdentityStoreMigration.IDENTITY_STORE_SCHEMA_VERSION)
- .migration(RealmIdentityStoreMigration)
+ .schemaVersion(realmIdentityStoreMigration.schemaVersion)
+ .migration(realmIdentityStoreMigration)
.allowWritesOnUiThread(true)
.modules(IdentityRealmModule())
.build()
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/identity/db/RealmIdentityStoreMigration.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/identity/db/RealmIdentityStoreMigration.kt
index 21c0f8eb9e..0c279d8a7e 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/identity/db/RealmIdentityStoreMigration.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/identity/db/RealmIdentityStoreMigration.kt
@@ -18,23 +18,23 @@ package org.matrix.android.sdk.internal.session.identity.db
import io.realm.DynamicRealm
import io.realm.RealmMigration
+import org.matrix.android.sdk.internal.session.identity.db.migration.MigrateIdentityTo001
import timber.log.Timber
+import javax.inject.Inject
-internal object RealmIdentityStoreMigration : RealmMigration {
+internal class RealmIdentityStoreMigration @Inject constructor() : RealmMigration {
+ /**
+ * Forces all RealmIdentityStoreMigration instances to be equal
+ * Avoids Realm throwing when multiple instances of the migration are set
+ */
+ override fun equals(other: Any?) = other is RealmIdentityStoreMigration
+ override fun hashCode() = 3000
- const val IDENTITY_STORE_SCHEMA_VERSION = 1L
+ val schemaVersion = 1L
override fun migrate(realm: DynamicRealm, oldVersion: Long, newVersion: Long) {
- Timber.v("Migrating Realm Identity from $oldVersion to $newVersion")
+ Timber.d("Migrating Realm Identity from $oldVersion to $newVersion")
- if (oldVersion <= 0) migrateTo1(realm)
- }
-
- private fun migrateTo1(realm: DynamicRealm) {
- Timber.d("Step 0 -> 1")
- Timber.d("Add field userConsent (Boolean) and set the value to false")
-
- realm.schema.get("IdentityDataEntity")
- ?.addField(IdentityDataEntityFields.USER_CONSENT, Boolean::class.java)
+ if (oldVersion < 1) MigrateIdentityTo001(realm).perform()
}
}
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/identity/db/migration/MigrateIdentityTo001.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/identity/db/migration/MigrateIdentityTo001.kt
new file mode 100644
index 0000000000..002601470d
--- /dev/null
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/identity/db/migration/MigrateIdentityTo001.kt
@@ -0,0 +1,31 @@
+/*
+ * Copyright (c) 2022 The Matrix.org Foundation C.I.C.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.matrix.android.sdk.internal.session.identity.db.migration
+
+import io.realm.DynamicRealm
+import org.matrix.android.sdk.internal.session.identity.db.IdentityDataEntityFields
+import org.matrix.android.sdk.internal.util.database.RealmMigrator
+import timber.log.Timber
+
+class MigrateIdentityTo001(realm: DynamicRealm) : RealmMigrator(realm, 1) {
+
+ override fun doMigrate(realm: DynamicRealm) {
+ Timber.d("Add field userConsent (Boolean) and set the value to false")
+ realm.schema.get("IdentityDataEntity")
+ ?.addField(IdentityDataEntityFields.USER_CONSENT, Boolean::class.java)
+ }
+}
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/UIEchoManager.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/UIEchoManager.kt
index 16d36c0cd9..bb92623249 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/UIEchoManager.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/UIEchoManager.kt
@@ -66,11 +66,11 @@ internal class UIEchoManager(private val listener: Listener) {
return existingState != sendState
}
- fun onLocalEchoCreated(timelineEvent: TimelineEvent): Boolean {
+ fun onLocalEchoCreated(timelineEvent: TimelineEvent): Boolean {
when (timelineEvent.root.getClearType()) {
EventType.REDACTION -> {
}
- EventType.REACTION -> {
+ EventType.REACTION -> {
val content: ReactionContent? = timelineEvent.root.content?.toModel()
if (RelationType.ANNOTATION == content?.relatesTo?.type) {
val reaction = content.relatesTo.key
@@ -104,8 +104,8 @@ internal class UIEchoManager(private val listener: Listener) {
val updateReactions = existingAnnotationSummary.reactionsSummary.toMutableList()
contents.forEach { uiEchoReaction ->
- val existing = updateReactions.firstOrNull { it.key == uiEchoReaction.reaction }
- if (existing == null) {
+ val indexOfExistingReaction = updateReactions.indexOfFirst { it.key == uiEchoReaction.reaction }
+ if (indexOfExistingReaction == -1) {
// just add the new key
ReactionAggregatedSummary(
key = uiEchoReaction.reaction,
@@ -117,6 +117,7 @@ internal class UIEchoManager(private val listener: Listener) {
).let { updateReactions.add(it) }
} else {
// update Existing Key
+ val existing = updateReactions[indexOfExistingReaction]
if (!existing.localEchoEvents.contains(uiEchoReaction.localEchoId)) {
updateReactions.remove(existing)
// only update if echo is not yet there
@@ -128,7 +129,7 @@ internal class UIEchoManager(private val listener: Listener) {
sourceEvents = existing.sourceEvents,
localEchoEvents = existing.localEchoEvents + uiEchoReaction.localEchoId
- ).let { updateReactions.add(it) }
+ ).let { updateReactions.add(indexOfExistingReaction, it) }
}
}
}
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/util/database/RealmMigrator.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/util/database/RealmMigrator.kt
new file mode 100644
index 0000000000..15e82f3cc0
--- /dev/null
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/util/database/RealmMigrator.kt
@@ -0,0 +1,52 @@
+/*
+ * Copyright (c) 2022 The Matrix.org Foundation C.I.C.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.matrix.android.sdk.internal.util.database
+
+import io.realm.DynamicRealm
+import io.realm.RealmObjectSchema
+import timber.log.Timber
+
+abstract class RealmMigrator(private val realm: DynamicRealm,
+ private val targetSchemaVersion: Int) {
+ fun perform() {
+ Timber.d("Migrate ${realm.configuration.realmFileName} to $targetSchemaVersion")
+ doMigrate(realm)
+ }
+
+ abstract fun doMigrate(realm: DynamicRealm)
+
+ protected fun RealmObjectSchema.addFieldIfNotExists(fieldName: String, fieldType: Class<*>): RealmObjectSchema {
+ if (!hasField(fieldName)) {
+ addField(fieldName, fieldType)
+ }
+ return this
+ }
+
+ protected fun RealmObjectSchema.removeFieldIfExists(fieldName: String): RealmObjectSchema {
+ if (hasField(fieldName)) {
+ removeField(fieldName)
+ }
+ return this
+ }
+
+ protected fun RealmObjectSchema.setRequiredIfNotAlready(fieldName: String, isRequired: Boolean): RealmObjectSchema {
+ if (isRequired != isRequired(fieldName)) {
+ setRequired(fieldName, isRequired)
+ }
+ return this
+ }
+}
diff --git a/tools/emojis/emoji_picker_datasource_formatted.json b/tools/emojis/emoji_picker_datasource_formatted.json
index 341cdc0c54..c1aa590003 100644
--- a/tools/emojis/emoji_picker_datasource_formatted.json
+++ b/tools/emojis/emoji_picker_datasource_formatted.json
@@ -2475,9 +2475,11 @@
"b": "1F636-200D-1F32B-FE0F",
"j": [
"absentminded",
- "face in clouds",
"face in the fog",
- "head in clouds"
+ "head in clouds",
+ "shower",
+ "steam",
+ "dream"
]
},
"smirking-face": {
@@ -2536,12 +2538,14 @@
"b": "1F62E-200D-1F4A8",
"j": [
"exhale",
- "face exhaling",
"gasp",
"groan",
"relief",
"whisper",
- "whistle"
+ "whistle",
+ "relieve",
+ "tired",
+ "sigh"
]
},
"lying-face": {
@@ -2745,11 +2749,15 @@
"b": "1F635-200D-1F4AB",
"j": [
"dizzy",
- "face with spiral eyes",
"hypnotized",
"spiral",
"trouble",
- "whoa"
+ "whoa",
+ "sick",
+ "ill",
+ "confused",
+ "nauseous",
+ "nausea"
]
},
"exploding-head": {
@@ -3704,10 +3712,11 @@
"j": [
"burn",
"heart",
- "heart on fire",
"love",
"lust",
- "sacred heart"
+ "sacred heart",
+ "passionate",
+ "enthusiastic"
]
},
"mending-heart": {
@@ -3717,10 +3726,12 @@
"healthier",
"improving",
"mending",
- "mending heart",
"recovering",
"recuperating",
- "well"
+ "well",
+ "broken heart",
+ "bandage",
+ "wounded"
]
},
"red-heart": {
@@ -4748,7 +4759,8 @@
"j": [
"beard",
"man",
- "man: beard"
+ "man: beard",
+ "facial hair"
]
},
"woman-beard": {
@@ -4757,7 +4769,8 @@
"j": [
"beard",
"woman",
- "woman: beard"
+ "woman: beard",
+ "facial hair"
]
},
"man-red-hair": {
diff --git a/vector/build.gradle b/vector/build.gradle
index 63c749daeb..91a6319975 100644
--- a/vector/build.gradle
+++ b/vector/build.gradle
@@ -376,7 +376,7 @@ dependencies {
implementation 'com.facebook.stetho:stetho:1.6.0'
// Phone number https://github.com/google/libphonenumber
- implementation 'com.googlecode.libphonenumber:libphonenumber:8.12.42'
+ implementation 'com.googlecode.libphonenumber:libphonenumber:8.12.43'
// FlowBinding
implementation libs.github.flowBinding
diff --git a/vector/src/main/java/im/vector/app/core/extensions/Context.kt b/vector/src/main/java/im/vector/app/core/extensions/Context.kt
index b8b367b740..b1e24c9502 100644
--- a/vector/src/main/java/im/vector/app/core/extensions/Context.kt
+++ b/vector/src/main/java/im/vector/app/core/extensions/Context.kt
@@ -18,6 +18,9 @@ package im.vector.app.core.extensions
import android.content.Context
import android.graphics.drawable.Drawable
+import android.text.Spannable
+import android.text.SpannableString
+import android.text.style.ImageSpan
import androidx.annotation.ColorInt
import androidx.annotation.ColorRes
import androidx.annotation.DrawableRes
@@ -34,6 +37,16 @@ fun Context.singletonEntryPoint(): SingletonEntryPoint {
return EntryPoints.get(applicationContext, SingletonEntryPoint::class.java)
}
+fun Context.getDrawableAsSpannable(@DrawableRes drawableRes: Int, alignment: Int = ImageSpan.ALIGN_BOTTOM): Spannable {
+ return SpannableString(" ").apply {
+ val span = ContextCompat.getDrawable(this@getDrawableAsSpannable, drawableRes)?.let {
+ it.setBounds(0, 0, it.intrinsicWidth, it.intrinsicHeight)
+ ImageSpan(it, alignment)
+ }
+ setSpan(span, 0, 1, Spannable.SPAN_INCLUSIVE_EXCLUSIVE)
+ }
+}
+
fun Context.getResTintedDrawable(@DrawableRes drawableRes: Int, @ColorRes tint: Int, @FloatRange(from = 0.0, to = 1.0) alpha: Float = 1f): Drawable? {
return getTintedDrawable(drawableRes, ContextCompat.getColor(this, tint), alpha)
}
diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/TimelineFragment.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/TimelineFragment.kt
index c757fd02ba..92319fabdc 100644
--- a/vector/src/main/java/im/vector/app/features/home/room/detail/TimelineFragment.kt
+++ b/vector/src/main/java/im/vector/app/features/home/room/detail/TimelineFragment.kt
@@ -1915,6 +1915,10 @@ class TimelineFragment @Inject constructor(
timelineViewModel.handle(RoomDetailAction.LoadMoreTimelineEvents(direction))
}
+ override fun onAddMoreReaction(event: TimelineEvent) {
+ openEmojiReactionPicker(event.eventId)
+ }
+
override fun onEventCellClicked(informationData: MessageInformationData, messageContent: Any?, view: View, isRootThreadEvent: Boolean) {
when (messageContent) {
is MessageVerificationRequestContent -> {
@@ -2119,7 +2123,7 @@ class TimelineFragment @Inject constructor(
openRoomMemberProfile(action.userId)
}
is EventSharedAction.AddReaction -> {
- emojiActivityResultLauncher.launch(EmojiReactionPickerActivity.intent(requireContext(), action.eventId))
+ openEmojiReactionPicker(action.eventId)
}
is EventSharedAction.ViewReactions -> {
ViewReactionsBottomSheet.newInstance(timelineArgs.roomId, action.messageInformationData)
@@ -2241,6 +2245,10 @@ class TimelineFragment @Inject constructor(
}
}
+ private fun openEmojiReactionPicker(eventId: String) {
+ emojiActivityResultLauncher.launch(EmojiReactionPickerActivity.intent(requireContext(), eventId))
+ }
+
private fun askConfirmationToEndPoll(eventId: String) {
MaterialAlertDialogBuilder(requireContext(), R.style.ThemeOverlay_Vector_MaterialAlertDialog)
.setTitle(R.string.end_poll_confirmation_title)
diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/TimelineEventController.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/TimelineEventController.kt
index 2ac592797c..e3f162dfd4 100644
--- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/TimelineEventController.kt
+++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/TimelineEventController.kt
@@ -41,6 +41,7 @@ import im.vector.app.features.home.room.detail.timeline.factory.TimelineItemFact
import im.vector.app.features.home.room.detail.timeline.factory.TimelineItemFactoryParams
import im.vector.app.features.home.room.detail.timeline.helper.ContentDownloadStateTrackerBinder
import im.vector.app.features.home.room.detail.timeline.helper.ContentUploadStateTrackerBinder
+import im.vector.app.features.home.room.detail.timeline.helper.ReactionsSummaryFactory
import im.vector.app.features.home.room.detail.timeline.helper.TimelineControllerInterceptorHelper
import im.vector.app.features.home.room.detail.timeline.helper.TimelineEventDiffUtilCallback
import im.vector.app.features.home.room.detail.timeline.helper.TimelineEventVisibilityHelper
@@ -86,7 +87,8 @@ class TimelineEventController @Inject constructor(private val dateFormatter: Vec
@TimelineEventControllerHandler
private val backgroundHandler: Handler,
private val timelineEventVisibilityHelper: TimelineEventVisibilityHelper,
- private val readReceiptsItemFactory: ReadReceiptsItemFactory
+ private val readReceiptsItemFactory: ReadReceiptsItemFactory,
+ private val reactionListFactory: ReactionsSummaryFactory
) : EpoxyController(backgroundHandler, backgroundHandler), Timeline.Listener, EpoxyController.Interceptor {
/**
@@ -138,6 +140,8 @@ class TimelineEventController @Inject constructor(private val dateFormatter: Vec
fun getPreviewUrlRetriever(): PreviewUrlRetriever
fun onVoiceControlButtonClicked(eventId: String, messageAudioContent: MessageAudioContent)
+
+ fun onAddMoreReaction(event: TimelineEvent)
}
interface ReactionPillCallback {
@@ -283,6 +287,7 @@ class TimelineEventController @Inject constructor(private val dateFormatter: Vec
super.onAttachedToRecyclerView(recyclerView)
timeline?.addListener(this)
timelineMediaSizeProvider.recyclerView = recyclerView
+ reactionListFactory.onRequestBuild = { requestModelBuild() }
}
override fun onDetachedFromRecyclerView(recyclerView: RecyclerView) {
@@ -290,6 +295,7 @@ class TimelineEventController @Inject constructor(private val dateFormatter: Vec
contentUploadStateTrackerBinder.clear()
contentDownloadStateTrackerBinder.clear()
timeline?.removeListener(this)
+ reactionListFactory.onRequestBuild = null
super.onDetachedFromRecyclerView(recyclerView)
}
@@ -383,7 +389,7 @@ class TimelineEventController @Inject constructor(private val dateFormatter: Vec
val event = currentSnapshot[position]
val nextEvent = currentSnapshot.nextOrNull(position)
// Should be build if not cached or if model should be refreshed
- if (modelCache[position] == null || modelCache[position]?.isCacheable(partialState) == false) {
+ if (modelCache[position] == null || modelCache[position]?.isCacheable(partialState) == false || reactionListFactory.needsRebuild(event)) {
val prevEvent = currentSnapshot.prevOrNull(position)
val prevDisplayableEvent = currentSnapshot.subList(0, position).lastOrNull {
timelineEventVisibilityHelper.shouldShowEvent(
diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/MessageInformationDataFactory.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/MessageInformationDataFactory.kt
index 276802e574..59b39d17ef 100644
--- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/MessageInformationDataFactory.kt
+++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/MessageInformationDataFactory.kt
@@ -24,7 +24,6 @@ import im.vector.app.features.home.room.detail.timeline.item.E2EDecoration
import im.vector.app.features.home.room.detail.timeline.item.MessageInformationData
import im.vector.app.features.home.room.detail.timeline.item.PollResponseData
import im.vector.app.features.home.room.detail.timeline.item.PollVoteSummaryData
-import im.vector.app.features.home.room.detail.timeline.item.ReactionInfoData
import im.vector.app.features.home.room.detail.timeline.item.ReferencesInfoData
import im.vector.app.features.home.room.detail.timeline.item.SendStateDecoration
import im.vector.app.features.home.room.detail.timeline.style.TimelineMessageLayoutFactory
@@ -50,7 +49,8 @@ import javax.inject.Inject
*/
class MessageInformationDataFactory @Inject constructor(private val session: Session,
private val dateFormatter: VectorDateFormatter,
- private val messageLayoutFactory: TimelineMessageLayoutFactory) {
+ private val messageLayoutFactory: TimelineMessageLayoutFactory,
+ private val reactionsSummaryFactory: ReactionsSummaryFactory) {
fun create(params: TimelineItemFactoryParams): MessageInformationData {
val event = params.event
@@ -93,11 +93,7 @@ class MessageInformationDataFactory @Inject constructor(private val session: Ses
avatarUrl = event.senderInfo.avatarUrl,
memberName = event.senderInfo.disambiguatedDisplayName,
messageLayout = messageLayout,
- orderedReactionList = event.annotations?.reactionsSummary
- // ?.filter { isSingleEmoji(it.key) }
- ?.map {
- ReactionInfoData(it.key, it.count, it.addedByMe, it.localEchoEvents.isEmpty())
- },
+ reactionsSummary = reactionsSummaryFactory.create(event, params.callback),
pollResponseAggregatedSummary = event.annotations?.pollResponseSummary?.let {
PollResponseData(
myVote = it.aggregatedContent?.myVote,
diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/ReactionsSummaryFactory.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/ReactionsSummaryFactory.kt
new file mode 100644
index 0000000000..fcc98ff729
--- /dev/null
+++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/ReactionsSummaryFactory.kt
@@ -0,0 +1,65 @@
+/*
+ * Copyright (c) 2021 New Vector Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package im.vector.app.features.home.room.detail.timeline.helper
+
+import dagger.hilt.android.scopes.ActivityScoped
+import im.vector.app.features.home.room.detail.timeline.TimelineEventController
+import im.vector.app.features.home.room.detail.timeline.item.ReactionInfoData
+import im.vector.app.features.home.room.detail.timeline.item.ReactionsSummaryData
+import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent
+import javax.inject.Inject
+
+@ActivityScoped
+class ReactionsSummaryFactory @Inject constructor() {
+
+ var onRequestBuild: (() -> Unit)? = null
+ private val showAllReactionsByEvent = HashSet()
+ private val eventsRequestingBuild = HashSet()
+
+ fun needsRebuild(event: TimelineEvent): Boolean {
+ return eventsRequestingBuild.remove(event.eventId)
+ }
+
+ fun create(event: TimelineEvent, callback: TimelineEventController.Callback?): ReactionsSummaryData {
+ val eventId = event.eventId
+ val showAllStates = showAllReactionsByEvent.contains(eventId)
+ val reactions = event.annotations?.reactionsSummary
+ ?.map {
+ ReactionInfoData(it.key, it.count, it.addedByMe, it.localEchoEvents.isEmpty())
+ }
+ return ReactionsSummaryData(
+ reactions = reactions,
+ showAll = showAllStates,
+ onShowMoreClicked = {
+ showAllReactionsByEvent.add(eventId)
+ onRequestBuild(eventId)
+ },
+ onShowLessClicked = {
+ showAllReactionsByEvent.remove(eventId)
+ onRequestBuild(eventId)
+ },
+ onAddMoreClicked = {
+ callback?.onAddMoreReaction(event)
+ }
+ )
+ }
+
+ private fun onRequestBuild(eventId: String) {
+ eventsRequestingBuild.add(eventId)
+ onRequestBuild?.invoke()
+ }
+}
diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/AbsBaseMessageItem.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/AbsBaseMessageItem.kt
index 9621b1c2f9..23f2aceff5 100644
--- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/AbsBaseMessageItem.kt
+++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/AbsBaseMessageItem.kt
@@ -16,24 +16,34 @@
package im.vector.app.features.home.room.detail.timeline.item
+import android.annotation.SuppressLint
+import android.graphics.Typeface
import android.view.View
import android.view.ViewGroup
import android.widget.ImageView
import android.widget.TextView
import androidx.annotation.IdRes
+import androidx.appcompat.view.ContextThemeWrapper
+import androidx.core.content.ContextCompat.getDrawable
import androidx.core.view.isVisible
+import androidx.core.widget.TextViewCompat
import im.vector.app.R
import im.vector.app.core.epoxy.ClickListener
import im.vector.app.core.epoxy.onClick
+import im.vector.app.core.extensions.getDrawableAsSpannable
import im.vector.app.core.ui.views.ShieldImageView
+import im.vector.app.core.utils.DimensionConverter
import im.vector.app.features.home.AvatarRenderer
import im.vector.app.features.home.room.detail.timeline.MessageColorProvider
import im.vector.app.features.home.room.detail.timeline.TimelineEventController
import im.vector.app.features.home.room.detail.timeline.view.TimelineMessageLayoutRenderer
import im.vector.app.features.reactions.widget.ReactionButton
+import im.vector.app.features.themes.ThemeUtils
import org.matrix.android.sdk.api.crypto.RoomEncryptionTrustLevel
import org.matrix.android.sdk.api.session.room.send.SendState
+private const val MAX_REACTIONS_TO_SHOW = 8
+
/**
* Base timeline item with reactions and read receipts.
* Manages associated click listeners and send status.
@@ -65,27 +75,10 @@ abstract class AbsBaseMessageItem : BaseEventItem
return listOf(baseAttributes.informationData.eventId)
}
+ @SuppressLint("SetTextI18n")
override fun bind(holder: H) {
super.bind(holder)
- val reactions = baseAttributes.informationData.orderedReactionList
- if (!shouldShowReactionAtBottom() || reactions.isNullOrEmpty()) {
- holder.reactionsContainer.isVisible = false
- } else {
- holder.reactionsContainer.isVisible = true
- holder.reactionsContainer.removeAllViews()
- reactions.take(8).forEach { reaction ->
- val reactionButton = ReactionButton(holder.view.context)
- reactionButton.reactedListener = reactionClickListener
- reactionButton.setTag(R.id.reactionsContainer, reaction.key)
- reactionButton.reactionString = reaction.key
- reactionButton.reactionCount = reaction.count
- reactionButton.setChecked(reaction.addedByMe)
- reactionButton.isEnabled = reaction.synced
- holder.reactionsContainer.addView(reactionButton)
- }
- holder.reactionsContainer.setOnLongClickListener(baseAttributes.itemLongClickListener)
- }
-
+ renderReactions(holder, baseAttributes.informationData.reactionsSummary)
when (baseAttributes.informationData.e2eDecoration) {
E2EDecoration.NONE -> {
holder.e2EDecorationView.render(null)
@@ -102,6 +95,58 @@ abstract class AbsBaseMessageItem : BaseEventItem
(holder.view as? TimelineMessageLayoutRenderer)?.renderMessageLayout(baseAttributes.informationData.messageLayout)
}
+ private fun renderReactions(holder: H, reactionsSummary: ReactionsSummaryData) {
+ val reactions = reactionsSummary.reactions
+ if (!shouldShowReactionAtBottom() || reactions.isNullOrEmpty()) {
+ holder.reactionsContainer.isVisible = false
+ } else {
+ holder.reactionsContainer.isVisible = true
+ holder.reactionsContainer.removeAllViews()
+ val reactionsToShow = if (reactionsSummary.showAll) {
+ reactions
+ } else {
+ reactions.take(MAX_REACTIONS_TO_SHOW)
+ }
+ reactionsToShow.forEach { reaction ->
+ val reactionButton = ReactionButton(holder.view.context)
+ reactionButton.reactedListener = reactionClickListener
+ reactionButton.setTag(R.id.reactionsContainer, reaction.key)
+ reactionButton.reactionString = reaction.key
+ reactionButton.reactionCount = reaction.count
+ reactionButton.setChecked(reaction.addedByMe)
+ reactionButton.isEnabled = reaction.synced
+ holder.reactionsContainer.addView(reactionButton)
+ }
+ if (reactions.count() > MAX_REACTIONS_TO_SHOW) {
+ val showReactionsTextView = createReactionTextView(holder)
+ if (reactionsSummary.showAll) {
+ showReactionsTextView.setText(R.string.message_reaction_show_less)
+ showReactionsTextView.onClick { reactionsSummary.onShowLessClicked() }
+ } else {
+ val moreCount = reactions.count() - MAX_REACTIONS_TO_SHOW
+ showReactionsTextView.text = holder.view.resources.getString(R.string.message_reaction_show_more, moreCount)
+ showReactionsTextView.onClick { reactionsSummary.onShowMoreClicked() }
+ }
+ holder.reactionsContainer.addView(showReactionsTextView)
+ }
+ val addMoreReactionsTextView = createReactionTextView(holder)
+
+ addMoreReactionsTextView.text = holder.view.context.getDrawableAsSpannable(R.drawable.ic_add_reaction_small)
+ addMoreReactionsTextView.onClick { reactionsSummary.onAddMoreClicked() }
+ holder.reactionsContainer.addView(addMoreReactionsTextView)
+ holder.reactionsContainer.setOnLongClickListener(baseAttributes.itemLongClickListener)
+ }
+ }
+
+ private fun createReactionTextView(holder: H): TextView {
+ return TextView(ContextThemeWrapper(holder.view.context, R.style.TimelineReactionView)).apply {
+ background = getDrawable(context, R.drawable.reaction_rounded_rect_shape_off)
+ TextViewCompat.setTextAppearance(this, R.style.TextAppearance_Vector_Micro)
+ setTypeface(typeface, Typeface.BOLD)
+ setTextColor(ThemeUtils.getColor(context, R.attr.vctr_content_secondary))
+ }
+ }
+
override fun unbind(holder: H) {
holder.reactionsContainer.setOnLongClickListener(null)
super.unbind(holder)
@@ -115,6 +160,9 @@ abstract class AbsBaseMessageItem : BaseEventItem
}
abstract class Holder(@IdRes stubId: Int) : BaseEventItem.BaseHolder(stubId) {
+ val dimensionConverter by lazy {
+ DimensionConverter(view.resources)
+ }
val reactionsContainer by bind(R.id.reactionsContainer)
val e2EDecorationView by bind(R.id.messageE2EDecoration)
}
diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageInformationData.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageInformationData.kt
index 629d20e898..2b68742e7e 100644
--- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageInformationData.kt
+++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageInformationData.kt
@@ -33,8 +33,7 @@ data class MessageInformationData(
val avatarUrl: String?,
val memberName: CharSequence? = null,
val messageLayout: TimelineMessageLayout,
- /*List of reactions (emoji,count,isSelected)*/
- val orderedReactionList: List? = null,
+ val reactionsSummary: ReactionsSummaryData,
val pollResponseAggregatedSummary: PollResponseData? = null,
val hasBeenEdited: Boolean = false,
val hasPendingEdits: Boolean = false,
@@ -55,6 +54,16 @@ data class ReferencesInfoData(
val verificationStatus: VerificationState
) : Parcelable
+@Parcelize
+data class ReactionsSummaryData(
+ /*List of reactions (emoji,count,isSelected)*/
+ val reactions: List? = null,
+ val showAll: Boolean = false,
+ val onShowMoreClicked: () -> Unit,
+ val onShowLessClicked: () -> Unit,
+ val onAddMoreClicked: () -> Unit
+) : Parcelable
+
@Parcelize
data class ReactionInfoData(
val key: String,
diff --git a/vector/src/main/java/im/vector/app/features/reactions/widget/ReactionButton.kt b/vector/src/main/java/im/vector/app/features/reactions/widget/ReactionButton.kt
index cc41141607..7340953f32 100644
--- a/vector/src/main/java/im/vector/app/features/reactions/widget/ReactionButton.kt
+++ b/vector/src/main/java/im/vector/app/features/reactions/widget/ReactionButton.kt
@@ -18,7 +18,6 @@ package im.vector.app.features.reactions.widget
import android.content.Context
import android.graphics.drawable.Drawable
import android.util.AttributeSet
-import android.view.Gravity
import android.view.View
import android.widget.LinearLayout
import androidx.core.content.ContextCompat
@@ -26,7 +25,6 @@ import androidx.core.content.withStyledAttributes
import dagger.hilt.android.AndroidEntryPoint
import im.vector.app.EmojiSpanify
import im.vector.app.R
-import im.vector.app.core.utils.DimensionConverter
import im.vector.app.core.utils.TextUtils
import im.vector.app.databinding.ReactionButtonBinding
import javax.inject.Inject
@@ -38,8 +36,9 @@ import javax.inject.Inject
@AndroidEntryPoint
class ReactionButton @JvmOverloads constructor(context: Context,
attrs: AttributeSet? = null,
- defStyleAttr: Int = 0) :
- LinearLayout(context, attrs, defStyleAttr), View.OnClickListener, View.OnLongClickListener {
+ defStyleAttr: Int = 0,
+ defStyleRes: Int = R.style.TimelineReactionView) :
+ LinearLayout(context, attrs, defStyleAttr, defStyleRes), View.OnClickListener, View.OnLongClickListener {
@Inject lateinit var emojiSpanify: EmojiSpanify
@@ -68,8 +67,6 @@ class ReactionButton @JvmOverloads constructor(context: Context,
init {
inflate(context, R.layout.reaction_button, this)
orientation = HORIZONTAL
- minimumHeight = DimensionConverter(context.resources).dpToPx(30)
- gravity = Gravity.CENTER
layoutDirection = View.LAYOUT_DIRECTION_LOCALE
views = ReactionButtonBinding.bind(this)
views.reactionCount.text = TextUtils.formatCountToShortDecimal(reactionCount)
diff --git a/vector/src/main/res/drawable/ic_add_reaction_small.xml b/vector/src/main/res/drawable/ic_add_reaction_small.xml
new file mode 100644
index 0000000000..ddd4367ce0
--- /dev/null
+++ b/vector/src/main/res/drawable/ic_add_reaction_small.xml
@@ -0,0 +1,4 @@
+
+
+
diff --git a/vector/src/main/res/drawable/reaction_divider.xml b/vector/src/main/res/drawable/reaction_divider.xml
index d68b6a9094..1d7ee57084 100644
--- a/vector/src/main/res/drawable/reaction_divider.xml
+++ b/vector/src/main/res/drawable/reaction_divider.xml
@@ -2,7 +2,7 @@
+ android:width="4dp"
+ android:height="4dp" />
\ No newline at end of file
diff --git a/vector/src/main/res/layout/item_room.xml b/vector/src/main/res/layout/item_room.xml
index defff5eeeb..68e3a85008 100644
--- a/vector/src/main/res/layout/item_room.xml
+++ b/vector/src/main/res/layout/item_room.xml
@@ -165,7 +165,6 @@
android:visibility="gone"
app:layout_constraintBottom_toBottomOf="@id/roomNameView"
app:layout_constraintEnd_toStartOf="@id/roomLastEventTimeView"
- app:layout_constraintStart_toEndOf="@id/roomDraftBadge"
app:layout_constraintTop_toTopOf="@id/roomNameView"
tools:background="@drawable/bg_unread_highlight"
tools:text="4"
@@ -190,9 +189,9 @@
android:layout_height="wrap_content"
android:layout_marginTop="3dp"
android:layout_marginEnd="8dp"
- android:textAlignment="viewStart"
android:ellipsize="end"
android:maxLines="2"
+ android:textAlignment="viewStart"
android:textColor="?vctr_content_secondary"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="@id/roomNameView"
@@ -203,12 +202,12 @@
android:id="@+id/roomTypingView"
style="@style/Widget.Vector.TextView.Body"
android:layout_width="0dp"
- android:textAlignment="viewStart"
android:layout_height="wrap_content"
android:layout_marginTop="3dp"
android:layout_marginEnd="8dp"
android:ellipsize="end"
android:maxLines="2"
+ android:textAlignment="viewStart"
android:textColor="?colorPrimary"
android:textStyle="bold"
app:layout_constraintEnd_toEndOf="parent"
diff --git a/vector/src/main/res/layout/reaction_button.xml b/vector/src/main/res/layout/reaction_button.xml
index a6d84407ce..ca1b17984b 100644
--- a/vector/src/main/res/layout/reaction_button.xml
+++ b/vector/src/main/res/layout/reaction_button.xml
@@ -3,11 +3,10 @@
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="wrap_content"
- android:layout_height="26dp"
+ android:layout_height="wrap_content"
android:background="@drawable/reaction_rounded_rect_shape"
android:clipChildren="false"
android:gravity="center"
- android:minWidth="44dp"
tools:parentTag="android.widget.LinearLayout">
@@ -20,12 +19,10 @@
android:id="@+id/reactionText"
style="@style/Widget.Vector.TextView.Caption"
android:layout_width="wrap_content"
- android:layout_height="20dp"
- android:layout_marginStart="6dp"
+ android:layout_height="wrap_content"
android:ellipsize="middle"
android:gravity="center"
android:maxEms="10"
- android:minWidth="20dp"
android:singleLine="true"
android:textColor="@color/emoji_color"
tools:text="* Party Parrot Again * 👀" />
@@ -36,7 +33,6 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="2dp"
- android:layout_marginEnd="8dp"
android:gravity="center"
android:maxLines="1"
android:textColor="?vctr_content_secondary"
diff --git a/vector/src/main/res/layout/view_message_bubble.xml b/vector/src/main/res/layout/view_message_bubble.xml
index 7e4d3e8f7d..e6f24e43c5 100644
--- a/vector/src/main/res/layout/view_message_bubble.xml
+++ b/vector/src/main/res/layout/view_message_bubble.xml
@@ -177,7 +177,7 @@
Create poll
Share location
+ Show less
+ "%1$d more"
+