diff --git a/.idea/misc.xml b/.idea/misc.xml index 860da66a5ea990f8e3c36fcdb0e2e05dacadf880..ef61796f57624d99df9ef9b2479763aed4ad62eb 100644 --- a/.idea/misc.xml +++ b/.idea/misc.xml @@ -1,6 +1,6 @@ <?xml version="1.0" encoding="UTF-8"?> <project version="4"> - <component name="ProjectRootManager" version="2" languageLevel="JDK_11" default="true" project-jdk-name="1.8" project-jdk-type="JavaSDK"> + <component name="ProjectRootManager" version="2" languageLevel="JDK_11" project-jdk-name="1.8" project-jdk-type="JavaSDK"> <output url="file://$PROJECT_DIR$/build/classes" /> </component> <component name="ProjectType"> diff --git a/.idea/runConfigurations.xml b/.idea/runConfigurations.xml deleted file mode 100644 index e497da999824b5c5629039f2e74794dd96b6c419..0000000000000000000000000000000000000000 --- a/.idea/runConfigurations.xml +++ /dev/null @@ -1,13 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<project version="4"> - <component name="RunConfigurationProducerService"> - <option name="ignoredProducers"> - <set> - <option value="com.android.tools.idea.compose.preview.runconfiguration.ComposePreviewRunConfigurationProducer" /> - <option value="org.jetbrains.plugins.gradle.execution.test.runner.AllInPackageGradleConfigurationProducer" /> - <option value="org.jetbrains.plugins.gradle.execution.test.runner.TestClassGradleConfigurationProducer" /> - <option value="org.jetbrains.plugins.gradle.execution.test.runner.TestMethodGradleConfigurationProducer" /> - </set> - </option> - </component> -</project> \ No newline at end of file diff --git a/build.gradle b/build.gradle index 22f247b6a7d440171c5033c0e9a34f013be83074..c8a95ea2eca6b0a5d1dab09b98e7defe539bc64c 100644 --- a/build.gradle +++ b/build.gradle @@ -2,7 +2,7 @@ buildscript { // Ref: https://kotlinlang.org/releases.html - ext.kotlin_version = '1.5.10' + ext.kotlin_version = '1.5.21' ext.kotlin_coroutines_version = "1.5.0" repositories { google() @@ -12,7 +12,7 @@ buildscript { } } dependencies { - classpath 'com.android.tools.build:gradle:4.2.1' + classpath 'com.android.tools.build:gradle:7.0.2' classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" // NOTE: Do not place your application dependencies here; they belong @@ -38,7 +38,8 @@ allprojects { tasks.withType(org.jetbrains.kotlin.gradle.tasks.KotlinCompile).all { // Warnings are potential errors, so stop ignoring them - kotlinOptions.allWarningsAsErrors = true + // Ignore on the SDK for the moment + kotlinOptions.allWarningsAsErrors = false } } diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index e1e2fd2c75e69ac5748b9c767a2b01d8e45f8b30..17ba19021b848c06cb45184d10d07ac47964d70d 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=13bf8d3cf8eeeb5770d19741a59bde9bd966dd78d17f1bbad787a05ef19d1c2d -distributionUrl=https\://services.gradle.org/distributions/gradle-7.0.2-all.zip +distributionSha256Sum=9bb8bc05f562f2d42bdf1ba8db62f6b6fa1c3bf6c392228802cc7cb0578fe7e0 +distributionUrl=https\://services.gradle.org/distributions/gradle-7.1.1-all.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/matrix-sdk-android/build.gradle b/matrix-sdk-android/build.gradle index fbd7f57ccdace83068848f2e894e9daf338f3f46..23e087b7f1b6e9f58225db7572ff03c9b3013b55 100644 --- a/matrix-sdk-android/build.gradle +++ b/matrix-sdk-android/build.gradle @@ -10,7 +10,7 @@ buildscript { mavenCentral() } dependencies { - classpath "io.realm:realm-gradle-plugin:10.5.0" + classpath "io.realm:realm-gradle-plugin:10.6.1" } } @@ -22,7 +22,7 @@ android { minSdkVersion 21 targetSdkVersion 30 versionCode 1 - versionName "1.1.9" + versionName "1.2.0" // Multidex is useful for tests multiDexEnabled true testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" @@ -73,12 +73,12 @@ android { } compileOptions { - sourceCompatibility JavaVersion.VERSION_1_8 - targetCompatibility JavaVersion.VERSION_1_8 + sourceCompatibility JavaVersion.VERSION_11 + targetCompatibility JavaVersion.VERSION_11 } kotlinOptions { - jvmTarget = "1.8" + jvmTarget = "11" } sourceSets { @@ -113,7 +113,7 @@ dependencies { def lifecycle_version = '2.2.0' def arch_version = '2.1.0' def markwon_version = '3.1.0' - def daggerVersion = '2.36' + def daggerVersion = '2.38' def work_version = '2.5.0' def retrofit_version = '2.9.0' @@ -121,8 +121,8 @@ dependencies { implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:$kotlin_coroutines_version" implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$kotlin_coroutines_version" - implementation "androidx.appcompat:appcompat:1.3.0" - implementation "androidx.core:core-ktx:1.5.0" + implementation "androidx.appcompat:appcompat:1.3.1" + implementation "androidx.core:core-ktx:1.6.0" implementation "androidx.lifecycle:lifecycle-extensions:$lifecycle_version" implementation "androidx.lifecycle:lifecycle-common-java8:$lifecycle_version" @@ -163,39 +163,39 @@ dependencies { kapt "com.google.dagger:dagger-compiler:$daggerVersion" // Logging - implementation 'com.jakewharton.timber:timber:4.7.1' + implementation 'com.jakewharton.timber:timber:5.0.1' implementation 'com.facebook.stetho:stetho-okhttp3:1.6.0' // Video compression implementation 'com.otaliastudios:transcoder:0.10.3' // Phone number https://github.com/google/libphonenumber - implementation 'com.googlecode.libphonenumber:libphonenumber:8.12.24' + implementation 'com.googlecode.libphonenumber:libphonenumber:8.12.28' testImplementation 'junit:junit:4.13.2' testImplementation 'org.robolectric:robolectric:4.5.1' //testImplementation 'org.robolectric:shadows-support-v4:3.0' // Note: version sticks to 1.9.2 due to https://github.com/mockk/mockk/issues/281 - testImplementation 'io.mockk:mockk:1.11.0' - testImplementation 'org.amshove.kluent:kluent-android:1.65' + testImplementation 'io.mockk:mockk:1.12.0' + testImplementation 'org.amshove.kluent:kluent-android:1.68' testImplementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$kotlin_coroutines_version" // Plant Timber tree for test testImplementation 'net.lachlanmckee:timber-junit-rule:1.0.1' kaptAndroidTest "com.google.dagger:dagger-compiler:$daggerVersion" - androidTestImplementation 'androidx.test:core:1.3.0' - androidTestImplementation 'androidx.test:runner:1.3.0' - androidTestImplementation 'androidx.test:rules:1.3.0' - androidTestImplementation 'androidx.test.ext:junit:1.1.2' - androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0' - androidTestImplementation 'org.amshove.kluent:kluent-android:1.65' - androidTestImplementation 'io.mockk:mockk-android:1.11.0' + androidTestImplementation 'androidx.test:core:1.4.0' + androidTestImplementation 'androidx.test:runner:1.4.0' + androidTestImplementation 'androidx.test:rules:1.4.0' + androidTestImplementation 'androidx.test.ext:junit:1.1.3' + androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0' + androidTestImplementation 'org.amshove.kluent:kluent-android:1.68' + androidTestImplementation 'io.mockk:mockk-android:1.12.0' androidTestImplementation "androidx.arch.core:core-testing:$arch_version" androidTestImplementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$kotlin_coroutines_version" // Plant Timber tree for test androidTestImplementation 'net.lachlanmckee:timber-junit-rule:1.0.1' - androidTestUtil 'androidx.test:orchestrator:1.3.0' + androidTestUtil 'androidx.test:orchestrator:1.4.0' } project.afterEvaluate { diff --git a/matrix-sdk-android/src/androidTest/AndroidManifest.xml b/matrix-sdk-android/src/androidTest/AndroidManifest.xml new file mode 100644 index 0000000000000000000000000000000000000000..274bd8c87b14e39bc108254a879aff19425b5bf3 --- /dev/null +++ b/matrix-sdk-android/src/androidTest/AndroidManifest.xml @@ -0,0 +1,18 @@ +<manifest xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:tools="http://schemas.android.com/tools" + package="org.matrix.android.sdk"> + + <application> + + <!-- + This is mandatory to run integration tests + --> + <provider + android:name="androidx.work.impl.WorkManagerInitializer" + android:authorities="${applicationId}.workmanager-init" + android:exported="false" + tools:node="remove" /> + + </application> + +</manifest> diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/SpaceOrderTest.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/SpaceOrderTest.kt new file mode 100644 index 0000000000000000000000000000000000000000..3270dfb7574246c85ecf1a7b6a5b64a171c6d86c --- /dev/null +++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/SpaceOrderTest.kt @@ -0,0 +1,260 @@ +/* + * Copyright 2021 The Matrix.org Foundation C.I.C. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.matrix.android.sdk + +import org.amshove.kluent.internal.assertEquals +import org.junit.Assert +import org.junit.Test +import org.matrix.android.sdk.api.session.space.SpaceOrderUtils + +class SpaceOrderTest { + + @Test + fun testOrderBetweenNodesWithOrder() { + val orderedSpaces = listOf( + "roomId1" to "a", + "roomId2" to "m", + "roomId3" to "z" + ).assertSpaceOrdered() + + val orderCommand = SpaceOrderUtils.orderCommandsForMove(orderedSpaces, "roomId1", 1) + + Assert.assertTrue("Only one order should be changed", orderCommand.size == 1) + Assert.assertTrue("Moved space order should change", orderCommand.first().spaceId == "roomId1") + + Assert.assertTrue("m" < orderCommand[0].order) + Assert.assertTrue(orderCommand[0].order < "z") + } + + @Test + fun testMoveLastBetweenNodesWithOrder() { + val orderedSpaces = listOf( + "roomId1" to "a", + "roomId2" to "m", + "roomId3" to "z" + ).assertSpaceOrdered() + + val orderCommand = SpaceOrderUtils.orderCommandsForMove(orderedSpaces, "roomId1", 2) + + Assert.assertTrue("Only one order should be changed", orderCommand.size == 1) + Assert.assertTrue("Moved space order should change", orderCommand.first().spaceId == "roomId1") + + Assert.assertTrue("z" < orderCommand[0].order) + } + + @Test + fun testMoveUpNoOrder() { + val orderedSpaces = listOf( + "roomId1" to null, + "roomId2" to null, + "roomId3" to null + ).assertSpaceOrdered() + + val orderCommand = SpaceOrderUtils.orderCommandsForMove(orderedSpaces, "roomId1", 1) + + Assert.assertTrue("2 orders change should be needed", orderCommand.size == 2) + + val reOrdered = reOrderWithCommands(orderedSpaces, orderCommand) + + Assert.assertEquals("roomId2", reOrdered[0].first) + Assert.assertEquals("roomId1", reOrdered[1].first) + Assert.assertEquals("roomId3", reOrdered[2].first) + } + + @Test + fun testMoveUpNotEnoughSpace() { + val orderedSpaces = listOf( + "roomId1" to "a", + "roomId2" to "j", + "roomId3" to "k" + ).assertSpaceOrdered() + + val orderCommand = SpaceOrderUtils.orderCommandsForMove(orderedSpaces, "roomId1", 1) + + Assert.assertTrue("more orders change should be needed", orderCommand.size > 1) + + val reOrdered = reOrderWithCommands(orderedSpaces, orderCommand) + + Assert.assertEquals("roomId2", reOrdered[0].first) + Assert.assertEquals("roomId1", reOrdered[1].first) + Assert.assertEquals("roomId3", reOrdered[2].first) + } + + @Test + fun testMoveEndNoOrder() { + val orderedSpaces = listOf( + "roomId1" to null, + "roomId2" to null, + "roomId3" to null + ).assertSpaceOrdered() + + val orderCommand = SpaceOrderUtils.orderCommandsForMove(orderedSpaces, "roomId1", 2) + + // Actually 2 could be enough... as it's last it can stays with null + Assert.assertEquals("3 orders change should be needed", 3, orderCommand.size) + + val reOrdered = reOrderWithCommands(orderedSpaces, orderCommand) + + Assert.assertEquals("roomId2", reOrdered[0].first) + Assert.assertEquals("roomId3", reOrdered[1].first) + Assert.assertEquals("roomId1", reOrdered[2].first) + } + + @Test + fun testMoveUpBiggerOrder() { + val orderedSpaces = listOf( + "roomId1" to "aaaa", + "roomId2" to "ffff", + "roomId3" to "pppp", + "roomId4" to null, + "roomId5" to null, + "roomId6" to null + ).assertSpaceOrdered() + + val orderCommand = SpaceOrderUtils.orderCommandsForMove(orderedSpaces, "roomId2", 3) + + // Actually 2 could be enough... as it's last it can stays with null + Assert.assertEquals("3 orders change should be needed", 3, orderCommand.size) + + val reOrdered = reOrderWithCommands(orderedSpaces, orderCommand) + + Assert.assertEquals("roomId1", reOrdered[0].first) + Assert.assertEquals("roomId3", reOrdered[1].first) + Assert.assertEquals("roomId4", reOrdered[2].first) + Assert.assertEquals("roomId5", reOrdered[3].first) + Assert.assertEquals("roomId2", reOrdered[4].first) + Assert.assertEquals("roomId6", reOrdered[5].first) + } + + @Test + fun testMoveDownBetweenNodesWithOrder() { + val orderedSpaces = listOf( + "roomId1" to "a", + "roomId2" to "m", + "roomId3" to "z" + ).assertSpaceOrdered() + + val orderCommand = SpaceOrderUtils.orderCommandsForMove(orderedSpaces, "roomId3", -1) + + Assert.assertTrue("Only one order should be changed", orderCommand.size == 1) + Assert.assertTrue("Moved space order should change", orderCommand.first().spaceId == "roomId3") + + val reOrdered = reOrderWithCommands(orderedSpaces, orderCommand) + + Assert.assertEquals("roomId1", reOrdered[0].first) + Assert.assertEquals("roomId3", reOrdered[1].first) + Assert.assertEquals("roomId2", reOrdered[2].first) + } + + @Test + fun testMoveDownNoOrder() { + val orderedSpaces = listOf( + "roomId1" to null, + "roomId2" to null, + "roomId3" to null + ).assertSpaceOrdered() + + val orderCommand = SpaceOrderUtils.orderCommandsForMove(orderedSpaces, "roomId3", -1) + + Assert.assertTrue("2 orders change should be needed", orderCommand.size == 2) + + val reOrdered = reOrderWithCommands(orderedSpaces, orderCommand) + + Assert.assertEquals("roomId1", reOrdered[0].first) + Assert.assertEquals("roomId3", reOrdered[1].first) + Assert.assertEquals("roomId2", reOrdered[2].first) + } + + @Test + fun testMoveDownBiggerOrder() { + val orderedSpaces = listOf( + "roomId1" to "aaaa", + "roomId2" to "ffff", + "roomId3" to "pppp", + "roomId4" to null, + "roomId5" to null, + "roomId6" to null + ).assertSpaceOrdered() + + val orderCommand = SpaceOrderUtils.orderCommandsForMove(orderedSpaces, "roomId5", -4) + + Assert.assertEquals("1 order change should be needed", 1, orderCommand.size) + + val reOrdered = reOrderWithCommands(orderedSpaces, orderCommand) + + Assert.assertEquals("roomId5", reOrdered[0].first) + Assert.assertEquals("roomId1", reOrdered[1].first) + Assert.assertEquals("roomId2", reOrdered[2].first) + Assert.assertEquals("roomId3", reOrdered[3].first) + Assert.assertEquals("roomId4", reOrdered[4].first) + Assert.assertEquals("roomId6", reOrdered[5].first) + } + + @Test + fun testMultipleMoveOrder() { + val orderedSpaces = listOf( + "roomId1" to null, + "roomId2" to null, + "roomId3" to null, + "roomId4" to null, + "roomId5" to null, + "roomId6" to null + ).assertSpaceOrdered() + + // move 5 to Top + val fiveToTop = SpaceOrderUtils.orderCommandsForMove(orderedSpaces, "roomId5", -4) + + val fiveTopReOrder = reOrderWithCommands(orderedSpaces, fiveToTop) + + // now move 4 to second + val orderCommand = SpaceOrderUtils.orderCommandsForMove(fiveTopReOrder, "roomId4", -3) + + val reOrdered = reOrderWithCommands(fiveTopReOrder, orderCommand) + // second order should cost 1 re-order + Assert.assertEquals(1, orderCommand.size) + + Assert.assertEquals("roomId5", reOrdered[0].first) + Assert.assertEquals("roomId4", reOrdered[1].first) + Assert.assertEquals("roomId1", reOrdered[2].first) + Assert.assertEquals("roomId2", reOrdered[3].first) + Assert.assertEquals("roomId3", reOrdered[4].first) + Assert.assertEquals("roomId6", reOrdered[5].first) + } + + @Test + fun testComparator() { + listOf( + "roomId2" to "a", + "roomId1" to "b", + "roomId3" to null, + "roomId4" to null + ).assertSpaceOrdered() + } + + private fun reOrderWithCommands(orderedSpaces: List<Pair<String, String?>>, orderCommand: List<SpaceOrderUtils.SpaceReOrderCommand>) = + orderedSpaces.map { orderInfo -> + orderInfo.first to (orderCommand.find { it.spaceId == orderInfo.first }?.order ?: orderInfo.second) + } + .sortedWith(testSpaceComparator) + + private fun List<Pair<String, String?>>.assertSpaceOrdered(): List<Pair<String, String?>> { + assertEquals(this, this.sortedWith(testSpaceComparator)) + return this + } + + private val testSpaceComparator = compareBy<Pair<String, String?>, String?>(nullsLast()) { it.second }.thenBy { it.first } +} diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/StringOrderTest.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/StringOrderTest.kt new file mode 100644 index 0000000000000000000000000000000000000000..a625362c0413d766bdc4f9567c5f755afc315378 --- /dev/null +++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/StringOrderTest.kt @@ -0,0 +1,110 @@ +/* + * Copyright 2021 The Matrix.org Foundation C.I.C. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.matrix.android.sdk + +import org.amshove.kluent.internal.assertEquals +import org.junit.Assert +import org.junit.Test +import org.matrix.android.sdk.api.MatrixPatterns +import org.matrix.android.sdk.api.util.StringOrderUtils + +class StringOrderTest { + + @Test + fun testbasing() { + assertEquals("a", StringOrderUtils.baseToString(StringOrderUtils.stringToBase("a", StringOrderUtils.DEFAULT_ALPHABET), StringOrderUtils.DEFAULT_ALPHABET)) + assertEquals("element", StringOrderUtils.baseToString(StringOrderUtils.stringToBase("element", StringOrderUtils.DEFAULT_ALPHABET), StringOrderUtils.DEFAULT_ALPHABET)) + assertEquals("matrix", StringOrderUtils.baseToString(StringOrderUtils.stringToBase("matrix", StringOrderUtils.DEFAULT_ALPHABET), StringOrderUtils.DEFAULT_ALPHABET)) + } + + @Test + fun testValid() { + println(StringOrderUtils.DEFAULT_ALPHABET.joinToString(",")) + + assert(MatrixPatterns.isValidOrderString("a")) + assert(MatrixPatterns.isValidOrderString(" ")) + assert(MatrixPatterns.isValidOrderString("abc")) + assert(!MatrixPatterns.isValidOrderString("abcê")) + assert(!MatrixPatterns.isValidOrderString("")) + assert(MatrixPatterns.isValidOrderString("!")) + assert(MatrixPatterns.isValidOrderString("!\"#\$%&'()*+,012")) + assert(!MatrixPatterns.isValidOrderString(Char(' '.code - 1).toString())) + + assert(!MatrixPatterns.isValidOrderString( + buildString { + for (i in 0..49) { + append(StringOrderUtils.DEFAULT_ALPHABET.random()) + } + } + )) + + assert(MatrixPatterns.isValidOrderString( + buildString { + for (i in 0..48) { + append(StringOrderUtils.DEFAULT_ALPHABET.random()) + } + } + )) + } + + @Test + fun testAverage() { + assertAverage("${StringOrderUtils.DEFAULT_ALPHABET.first()}", "m") + assertAverage("aa", "aab") + assertAverage("matrix", "element") + assertAverage("mmm", "mmmmm") + assertAverage("aab", "aa") + assertAverage("", "aa") + assertAverage("a", "z") + assertAverage("ground", "sky") + } + + @Test + fun testMidPoints() { + val orders = StringOrderUtils.midPoints("element", "matrix", 4) + assertEquals(4, orders!!.size) + assert("element" < orders[0]) + assert(orders[0] < orders[1]) + assert(orders[1] < orders[2]) + assert(orders[2] < orders[3]) + assert(orders[3] < "matrix") + + println("element < ${orders.joinToString(" < ") { "[$it]" }} < matrix") + + val orders2 = StringOrderUtils.midPoints("a", "d", 4) + assertEquals(null, orders2) + } + + @Test + fun testRenumberNeeded() { + assertEquals(null, StringOrderUtils.average("a", "a")) + assertEquals(null, StringOrderUtils.average("", "")) + assertEquals(null, StringOrderUtils.average("a", "b")) + assertEquals(null, StringOrderUtils.average("b", "a")) + assertEquals(null, StringOrderUtils.average("mmmm", "mmmm")) + assertEquals(null, StringOrderUtils.average("a${Char(0)}", "a")) + } + + private fun assertAverage(first: String, second: String) { + val left = first.takeIf { first < second } ?: second + val right = first.takeIf { first > second } ?: second + val av1 = StringOrderUtils.average(left, right)!! + println("[$left] < [$av1] < [$right]") + Assert.assertTrue(left < av1) + Assert.assertTrue(av1 < right) + } +} diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/common/CommonTestHelper.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/common/CommonTestHelper.kt index 287a4233d990715176a0655c767a60a30cbf3bfc..6e07223ac7105c1598520d0e97c12b099d624210 100644 --- a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/common/CommonTestHelper.kt +++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/common/CommonTestHelper.kt @@ -78,7 +78,7 @@ class CommonTestHelper(context: Context) { } /** - * Create a Home server configuration, with Http connection allowed for test + * Create a homeserver configuration, with Http connection allowed for test */ fun createHomeServerConfig(): HomeServerConnectionConfig { return HomeServerConnectionConfig.Builder() diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/keysbackup/KeysBackupTest.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/keysbackup/KeysBackupTest.kt index eb8b8b97303c37570ea6107f2a5de381df67258c..89d297c5928b816bce535ee35a841ece3eee762e 100644 --- a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/keysbackup/KeysBackupTest.kt +++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/keysbackup/KeysBackupTest.kt @@ -816,7 +816,7 @@ class KeysBackupTest : InstrumentedTest { // - Do an e2e backup to the homeserver mKeysBackupTestHelper.prepareAndCreateKeysBackupData(keysBackup) - // Get key backup version from the home server + // Get key backup version from the homeserver val keysVersionResult = mTestHelper.doSync<KeysVersionResult?> { keysBackup.getCurrentVersion(it) } diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/ssss/QuadSTests.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/ssss/QuadSTests.kt index 25c22bca57ddf74d1bc29b2326a99706ca4f1d7b..d14de30c9007560fbdeaee89146be2ccf5c5e10c 100644 --- a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/ssss/QuadSTests.kt +++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/ssss/QuadSTests.kt @@ -33,7 +33,7 @@ import org.matrix.android.sdk.common.TestConstants import org.matrix.android.sdk.internal.crypto.SSSS_ALGORITHM_AES_HMAC_SHA2 import org.matrix.android.sdk.internal.crypto.crosssigning.toBase64NoPadding import org.matrix.android.sdk.internal.crypto.secrets.DefaultSharedSecretStorageService -import org.matrix.android.sdk.api.session.accountdata.AccountDataEvent +import org.matrix.android.sdk.api.session.accountdata.UserAccountDataEvent import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.launch @@ -73,12 +73,12 @@ class QuadSTests : InstrumentedTest { // Assert Account data is updated val accountDataLock = CountDownLatch(1) - var accountData: AccountDataEvent? = null + var accountData: UserAccountDataEvent? = null val liveAccountData = runBlocking(Dispatchers.Main) { - aliceSession.userAccountDataService().getLiveAccountDataEvent("${DefaultSharedSecretStorageService.KEY_ID_BASE}.$TEST_KEY_ID") + aliceSession.accountDataService().getLiveUserAccountDataEvent("${DefaultSharedSecretStorageService.KEY_ID_BASE}.$TEST_KEY_ID") } - val accountDataObserver = Observer<Optional<AccountDataEvent>?> { t -> + val accountDataObserver = Observer<Optional<UserAccountDataEvent>?> { t -> if (t?.getOrNull()?.type == "${DefaultSharedSecretStorageService.KEY_ID_BASE}.$TEST_KEY_ID") { accountData = t.getOrNull() accountDataLock.countDown() @@ -100,13 +100,13 @@ class QuadSTests : InstrumentedTest { quadS.setDefaultKey(TEST_KEY_ID) } - var defaultKeyAccountData: AccountDataEvent? = null + var defaultKeyAccountData: UserAccountDataEvent? = null val defaultDataLock = CountDownLatch(1) val liveDefAccountData = runBlocking(Dispatchers.Main) { - aliceSession.userAccountDataService().getLiveAccountDataEvent(DefaultSharedSecretStorageService.DEFAULT_KEY_ID) + aliceSession.accountDataService().getLiveUserAccountDataEvent(DefaultSharedSecretStorageService.DEFAULT_KEY_ID) } - val accountDefDataObserver = Observer<Optional<AccountDataEvent>?> { t -> + val accountDefDataObserver = Observer<Optional<UserAccountDataEvent>?> { t -> if (t?.getOrNull()?.type == DefaultSharedSecretStorageService.DEFAULT_KEY_ID) { defaultKeyAccountData = t.getOrNull()!! defaultDataLock.countDown() @@ -206,7 +206,7 @@ class QuadSTests : InstrumentedTest { ) } - val accountDataEvent = aliceSession.userAccountDataService().getAccountDataEvent("my.secret") + val accountDataEvent = aliceSession.accountDataService().getUserAccountDataEvent("my.secret") val encryptedContent = accountDataEvent?.content?.get("encrypted") as? Map<*, *> assertEquals("Content should contains two encryptions", 2, encryptedContent?.keys?.size ?: 0) @@ -275,14 +275,14 @@ class QuadSTests : InstrumentedTest { mTestHelper.signOutAndClose(aliceSession) } - private fun assertAccountData(session: Session, type: String): AccountDataEvent { + private fun assertAccountData(session: Session, type: String): UserAccountDataEvent { val accountDataLock = CountDownLatch(1) - var accountData: AccountDataEvent? = null + var accountData: UserAccountDataEvent? = null val liveAccountData = runBlocking(Dispatchers.Main) { - session.userAccountDataService().getLiveAccountDataEvent(type) + session.accountDataService().getLiveUserAccountDataEvent(type) } - val accountDataObserver = Observer<Optional<AccountDataEvent>?> { t -> + val accountDataObserver = Observer<Optional<UserAccountDataEvent>?> { t -> if (t?.getOrNull()?.type == type) { accountData = t.getOrNull() accountDataLock.countDown() diff --git a/matrix-sdk-android/src/main/AndroidManifest.xml b/matrix-sdk-android/src/main/AndroidManifest.xml index 220a168f60ef25659b96f50b9d4a91cb7c09b916..de0731422c11426399db25c4e274d733ba8096e5 100644 --- a/matrix-sdk-android/src/main/AndroidManifest.xml +++ b/matrix-sdk-android/src/main/AndroidManifest.xml @@ -10,15 +10,6 @@ <application android:networkSecurityConfig="@xml/network_security_config"> - <!-- - This is mandatory to run integration tests - --> - <provider - android:name="androidx.work.impl.WorkManagerInitializer" - android:authorities="${applicationId}.workmanager-init" - android:exported="false" - tools:node="remove" /> - <!-- The SDK offers a secured File provider to access downloaded files. Access to these file will be given via the FileService, with a temporary diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/MatrixPatterns.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/MatrixPatterns.kt index d8532c77c839b446efec20a53c016ee5e560f86d..9a5e40ffeba3b8c4a0fc1c1ba1dc8ffefed9d1b0 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/MatrixPatterns.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/MatrixPatterns.kt @@ -16,8 +16,12 @@ package org.matrix.android.sdk.api +import org.matrix.android.sdk.BuildConfig +import timber.log.Timber + /** * This class contains pattern to match the different Matrix ids + * Ref: https://matrix.org/docs/spec/appendices#identifier-grammar */ object MatrixPatterns { @@ -25,7 +29,7 @@ object MatrixPatterns { private const val DOMAIN_REGEX = ":[A-Z0-9.-]+(:[0-9]{2,5})?" // regex pattern to find matrix user ids in a string. - // See https://matrix.org/speculator/spec/HEAD/appendices.html#historical-user-ids + // See https://matrix.org/docs/spec/appendices#historical-user-ids private const val MATRIX_USER_IDENTIFIER_REGEX = "@[A-Z0-9\\x21-\\x39\\x3B-\\x7F]+$DOMAIN_REGEX" val PATTERN_CONTAIN_MATRIX_USER_IDENTIFIER = MATRIX_USER_IDENTIFIER_REGEX.toRegex(RegexOption.IGNORE_CASE) @@ -71,6 +75,9 @@ object MatrixPatterns { private const val LINK_TO_APP_ROOM_ALIAS_REGEXP = APP_BASE_REGEX + MATRIX_ROOM_ALIAS_REGEX + SEP_REGEX + MATRIX_EVENT_IDENTIFIER_REGEX private val PATTERN_CONTAIN_APP_LINK_PERMALINK_ROOM_ALIAS = LINK_TO_APP_ROOM_ALIAS_REGEXP.toRegex(RegexOption.IGNORE_CASE) + // ascii characters in the range \x20 (space) to \x7E (~) + val ORDER_STRING_REGEX = "[ -~]+".toRegex() + // list of patterns to find some matrix item. val MATRIX_PATTERNS = listOf( PATTERN_CONTAIN_MATRIX_TO_PERMALINK_ROOM_ID, @@ -146,4 +153,32 @@ object MatrixPatterns { fun extractServerNameFromId(matrixId: String?): String? { return matrixId?.substringAfter(":", missingDelimiterValue = "")?.takeIf { it.isNotEmpty() } } + + /** + * Orders which are not strings, or do not consist solely of ascii characters in the range \x20 (space) to \x7E (~), + * or consist of more than 50 characters, are forbidden and the field should be ignored if received. + */ + fun isValidOrderString(order: String?): Boolean { + return order != null && order.length < 50 && order matches ORDER_STRING_REGEX + } + + fun candidateAliasFromRoomName(name: String): String { + return Regex("\\s").replace(name.lowercase(), "_").let { + "[^a-z0-9._%#@=+-]".toRegex().replace(it, "") + } + } + + /** + * Return the domain form a userId + * Examples: + * - "@alice:domain.org".getDomain() will return "domain.org" + * - "@bob:domain.org:3455".getDomain() will return "domain.org:3455" + */ + fun String.getDomain(): String { + if (BuildConfig.DEBUG && !isUserId(this)) { + // They are some invalid userId localpart in the wild, but the domain part should be there anyway + Timber.w("Not a valid user ID: $this") + } + return substringAfter(":") + } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/data/HomeServerConnectionConfig.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/data/HomeServerConnectionConfig.kt index e1c5171bfc50fdc5835e9461f77c410c5978b244..215f0a0351a9442d3d52d43685273ed3aa4ee5b4 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/data/HomeServerConnectionConfig.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/data/HomeServerConnectionConfig.kt @@ -18,11 +18,11 @@ package org.matrix.android.sdk.api.auth.data import android.net.Uri import com.squareup.moshi.JsonClass +import okhttp3.CipherSuite +import okhttp3.TlsVersion import org.matrix.android.sdk.api.auth.data.HomeServerConnectionConfig.Builder import org.matrix.android.sdk.internal.network.ssl.Fingerprint import org.matrix.android.sdk.internal.util.ensureTrailingSlash -import okhttp3.CipherSuite -import okhttp3.TlsVersion /** * This data class holds how to connect to a specific Homeserver. @@ -31,7 +31,12 @@ import okhttp3.TlsVersion */ @JsonClass(generateAdapter = true) data class HomeServerConnectionConfig( + // This is the homeserver URL entered by the user val homeServerUri: Uri, + // This is the homeserver base URL for the client-server API. Default to homeServerUri, + // but can be updated with data from .Well-Known before login, and/or with the data + // included in the login response + val homeServerUriBase: Uri = homeServerUri, val identityServerUri: Uri? = null, val antiVirusServerUri: Uri? = null, val allowedFingerprints: List<Fingerprint> = emptyList(), @@ -47,7 +52,6 @@ data class HomeServerConnectionConfig( * This builder should be use to create a [HomeServerConnectionConfig] instance. */ class Builder { - private lateinit var homeServerUri: Uri private var identityServerUri: Uri? = null private var antiVirusServerUri: Uri? = null @@ -69,14 +73,14 @@ data class HomeServerConnectionConfig( */ fun withHomeServerUri(hsUri: Uri): Builder { if (hsUri.scheme != "http" && hsUri.scheme != "https") { - throw RuntimeException("Invalid home server URI: $hsUri") + throw RuntimeException("Invalid homeserver URI: $hsUri") } // ensure trailing / val hsString = hsUri.toString().ensureTrailingSlash() homeServerUri = try { Uri.parse(hsString) } catch (e: Exception) { - throw RuntimeException("Invalid home server URI: $hsUri") + throw RuntimeException("Invalid homeserver URI: $hsUri") } return this } @@ -134,7 +138,7 @@ data class HomeServerConnectionConfig( } /** - * Add an accepted TLS version for TLS connections with the home server. + * Add an accepted TLS version for TLS connections with the homeserver. * * @param tlsVersion the tls version to add to the set of TLS versions accepted. * @return this builder @@ -156,7 +160,7 @@ data class HomeServerConnectionConfig( } /** - * Add a TLS cipher suite to the list of accepted TLS connections with the home server. + * Add a TLS cipher suite to the list of accepted TLS connections with the homeserver. * * @param tlsCipherSuite the tls cipher suite to add. * @return this builder @@ -234,16 +238,16 @@ data class HomeServerConnectionConfig( */ fun build(): HomeServerConnectionConfig { return HomeServerConnectionConfig( - homeServerUri, - identityServerUri, - antiVirusServerUri, - allowedFingerprints, - shouldPin, - tlsVersions, - tlsCipherSuites, - shouldAcceptTlsExtensions, - allowHttpExtension, - forceUsageTlsVersions + homeServerUri = homeServerUri, + identityServerUri = identityServerUri, + antiVirusServerUri = antiVirusServerUri, + allowedFingerprints = allowedFingerprints, + shouldPin = shouldPin, + tlsVersions = tlsVersions, + tlsCipherSuites = tlsCipherSuites, + shouldAcceptTlsExtensions = shouldAcceptTlsExtensions, + allowHttpExtension = allowHttpExtension, + forceUsageTlsVersions = forceUsageTlsVersions ) } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/data/SessionParams.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/data/SessionParams.kt index b2a57c7f5ce39e093eb50be09797d46f775e474a..b490ac877ee4e455532d5566631f7753b169fa51 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/data/SessionParams.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/data/SessionParams.kt @@ -51,13 +51,18 @@ data class SessionParams( val deviceId = credentials.deviceId /** - * The current homeserver Url. It can be different that the homeserver url entered - * during login phase, because a redirection may have occurred + * The homeserver Url entered by the user during the login phase. */ val homeServerUrl = homeServerConnectionConfig.homeServerUri.toString() /** - * The current homeserver host + * The current homeserver Url for client-server API. It can be different that the homeserver url entered + * during login phase, because a redirection may have occurred + */ + val homeServerUrlBase = homeServerConnectionConfig.homeServerUriBase.toString() + + /** + * The current homeserver host, using what has been entered by the user during login phase */ val homeServerHost = homeServerConnectionConfig.homeServerUri.host diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/registration/RegistrationFlowResponse.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/registration/RegistrationFlowResponse.kt index 2b1c1c09b3d7b466d752ff3f7c89fbe4e1131a41..ac740ddab719a936933f87a1be69388547c3e2e1 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/registration/RegistrationFlowResponse.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/registration/RegistrationFlowResponse.kt @@ -38,7 +38,7 @@ data class RegistrationFlowResponse( val completedStages: List<String>? = null, /** - * The session identifier that the client must pass back to the home server, if one is provided, + * The session identifier that the client must pass back to the homeserver, if one is provided, * in subsequent attempts to authenticate in the same API call. */ @Json(name = "session") diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/wellknown/WellknownResult.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/wellknown/WellknownResult.kt index c68a9e9699aaa4e9226895818f8d4d4bffa7f89a..56257db79c42f2438639bed8eb0dd0cc36fe97b3 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/wellknown/WellknownResult.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/wellknown/WellknownResult.kt @@ -22,11 +22,6 @@ import org.matrix.android.sdk.api.auth.data.WellKnown * Ref: https://matrix.org/docs/spec/client_server/latest#well-known-uri */ sealed class WellknownResult { - /** - * The provided matrixId is no valid. Unable to extract a domain name. - */ - object InvalidMatrixId : WellknownResult() - /** * Retrieve the specific piece of information from the user in a way which fits within the existing client user experience, * if the client is inclined to do so. Failure can take place instead if no good user experience for this is possible at this point. diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/failure/MatrixIdFailure.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/failure/MatrixIdFailure.kt new file mode 100644 index 0000000000000000000000000000000000000000..8f7bca803dfa6f00877ae4066e3ac1afe8931178 --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/failure/MatrixIdFailure.kt @@ -0,0 +1,21 @@ +/* + * Copyright (c) 2021 The Matrix.org Foundation C.I.C. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.matrix.android.sdk.api.failure + +sealed class MatrixIdFailure : Failure.FeatureFailure() { + object InvalidMatrixId : MatrixIdFailure() +} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/logger/LoggerTag.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/logger/LoggerTag.kt new file mode 100644 index 0000000000000000000000000000000000000000..51f9b50699b6437f7e3e5a2349e024aa38db03b8 --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/logger/LoggerTag.kt @@ -0,0 +1,34 @@ +/* + * Copyright (c) 2021 The Matrix.org Foundation C.I.C. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.matrix.android.sdk.api.logger + +/** + * Parent class for custom logger tags. Can be used with Timber : + * + * val loggerTag = LoggerTag("MyTag", LoggerTag.VOIP) + * Timber.tag(loggerTag.value).v("My log message") + */ +open class LoggerTag(_value: String, parentTag: LoggerTag? = null) { + + object VOIP : LoggerTag("VOIP") + + val value: String = if (parentTag == null) { + _value + } else { + "${parentTag.value}/$_value" + } +} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/pushrules/PushRuleService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/pushrules/PushRuleService.kt index d9bf5cfd13b2b0bb3123df5943b82d5d1f30f46a..45343686798e21d4123d2f3f95834a5bf90bd05d 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/pushrules/PushRuleService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/pushrules/PushRuleService.kt @@ -31,7 +31,13 @@ interface PushRuleService { suspend fun addPushRule(kind: RuleKind, pushRule: PushRule) - suspend fun updatePushRuleActions(kind: RuleKind, oldPushRule: PushRule, newPushRule: PushRule) + /** + * Enables/Disables a push rule and updates the actions if necessary + * @param enable Enables/Disables the rule + * @param actions Actions to update if not null + */ + + suspend fun updatePushRuleActions(kind: RuleKind, ruleId: String, enable: Boolean, actions: List<Action>?) suspend fun removePushRule(kind: RuleKind, pushRule: PushRule) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/pushrules/rest/PushRule.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/pushrules/rest/PushRule.kt index 3a9fc4fb834a89e0e738842cc7e3eb3c6ce23bce..31d7770a9f4a62193825f39e81f97dc9f85ac6a7 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/pushrules/rest/PushRule.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/pushrules/rest/PushRule.kt @@ -18,6 +18,7 @@ package org.matrix.android.sdk.api.pushrules.rest import com.squareup.moshi.Json import com.squareup.moshi.JsonClass +import org.matrix.android.sdk.api.extensions.orFalse import org.matrix.android.sdk.api.pushrules.Action import org.matrix.android.sdk.api.pushrules.getActions import org.matrix.android.sdk.api.pushrules.toJson @@ -100,6 +101,13 @@ data class PushRule( ) } + /** + * Get the highlight status. As spec mentions assume false if no tweak present. + */ + fun getHighlight(): Boolean { + return getActions().filterIsInstance<Action.Highlight>().firstOrNull()?.highlight.orFalse() + } + /** * Set the notification status. * diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/raw/RawService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/raw/RawService.kt index f1722b2189a324a8294f79a318ff0c08b95eb9dc..3366d040f7fa1227a6b72cc9668fa411681fea88 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/raw/RawService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/raw/RawService.kt @@ -29,8 +29,10 @@ interface RawService { /** * Specific case for the well-known file. Cache validity is 8 hours + * @param domain the domain to get the .well-known file, for instance "matrix.org". + * The URL will be "https://{domain}/.well-known/matrix/client" */ - suspend fun getWellknown(userId: String): String + suspend fun getWellknown(domain: String): String /** * Clear all the cache data diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/Session.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/Session.kt index e888e5d2de748027166540353478115574d7ca8a..2f981ffbbedfbe7f58c9f51547816c46b1a2894d 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/Session.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/Session.kt @@ -24,7 +24,7 @@ import org.matrix.android.sdk.api.failure.GlobalError import org.matrix.android.sdk.api.federation.FederationService import org.matrix.android.sdk.api.pushrules.PushRuleService import org.matrix.android.sdk.api.session.account.AccountService -import org.matrix.android.sdk.api.session.accountdata.AccountDataService +import org.matrix.android.sdk.api.session.accountdata.SessionAccountDataService import org.matrix.android.sdk.api.session.cache.CacheService import org.matrix.android.sdk.api.session.call.CallSignalingService import org.matrix.android.sdk.api.session.content.ContentUploadStateTracker @@ -239,9 +239,9 @@ interface Session : fun openIdService(): OpenIdService /** - * Returns the user account data service associated with the session + * Returns the account data service associated with the session */ - fun userAccountDataService(): AccountDataService + fun accountDataService(): SessionAccountDataService /** * Add a listener to the session. diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/accountdata/SessionAccountDataService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/accountdata/SessionAccountDataService.kt new file mode 100644 index 0000000000000000000000000000000000000000..2ffb9112d1e8ee56bfaab92a491643bf8c15b96e --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/accountdata/SessionAccountDataService.kt @@ -0,0 +1,66 @@ +/* + * Copyright 2020 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.api.session.accountdata + +import androidx.lifecycle.LiveData +import org.matrix.android.sdk.api.session.events.model.Content +import org.matrix.android.sdk.api.session.room.accountdata.RoomAccountDataEvent +import org.matrix.android.sdk.api.util.Optional + +/** + * This service is attached globally to the session. + */ +interface SessionAccountDataService { + /** + * Retrieve the account data with the provided type or null if not found + */ + fun getUserAccountDataEvent(type: String): UserAccountDataEvent? + + /** + * Observe the account data with the provided type + */ + fun getLiveUserAccountDataEvent(type: String): LiveData<Optional<UserAccountDataEvent>> + + /** + * Retrieve the account data with the provided types. The return list can have a different size that + * the size of the types set, because some AccountData may not exist. + * If an empty set is provided, all the AccountData are retrieved + */ + fun getUserAccountDataEvents(types: Set<String>): List<UserAccountDataEvent> + + /** + * Observe the account data with the provided types. If an empty set is provided, all the AccountData are observed + */ + fun getLiveUserAccountDataEvents(types: Set<String>): LiveData<List<UserAccountDataEvent>> + + /** + * Retrieve the room account data with the provided types. The return list can have a different size that + * the size of the types set, because some AccountData may not exist. + * If an empty set is provided, all the room AccountData are retrieved + */ + fun getRoomAccountDataEvents(types: Set<String>): List<RoomAccountDataEvent> + + /** + * Observe the room account data with the provided types. If an empty set is provided, AccountData of every room are observed + */ + fun getLiveRoomAccountDataEvents(types: Set<String>): LiveData<List<RoomAccountDataEvent>> + + /** + * Update the account data with the provided type and the provided account data content + */ + suspend fun updateUserAccountData(type: String, content: Content) +} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/accountdata/AccountDataEvent.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/accountdata/UserAccountDataEvent.kt similarity index 91% rename from matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/accountdata/AccountDataEvent.kt rename to matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/accountdata/UserAccountDataEvent.kt index e5cbd07aafdeb3bab7b3790e6c5a9871b6933424..77381a28c43c8c78e44397cf5c6c82c2e904785e 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/accountdata/AccountDataEvent.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/accountdata/UserAccountDataEvent.kt @@ -22,10 +22,10 @@ import org.matrix.android.sdk.api.session.events.model.Content /** * This is a simplified Event with just a type and a content. - * Currently used types are defined in [UserAccountDataTypes]. + * Currently used types are defined in [UserAccountDataTypes] */ @JsonClass(generateAdapter = true) -data class AccountDataEvent( +data class UserAccountDataEvent( @Json(name = "type") val type: String, @Json(name = "content") val content: Content ) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/call/CallListener.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/call/CallListener.kt index 303add747f1715fb538978ea3998e45c3e1e8675..d17be59cd4d4cd5556b59b94dd7718dc1771b530 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/call/CallListener.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/call/CallListener.kt @@ -17,6 +17,7 @@ package org.matrix.android.sdk.api.session.call import org.matrix.android.sdk.api.session.room.model.call.CallAnswerContent +import org.matrix.android.sdk.api.session.room.model.call.CallAssertedIdentityContent import org.matrix.android.sdk.api.session.room.model.call.CallCandidatesContent import org.matrix.android.sdk.api.session.room.model.call.CallHangupContent import org.matrix.android.sdk.api.session.room.model.call.CallInviteContent @@ -61,4 +62,9 @@ interface CallListener { * Called when the call has been managed by an other session */ fun onCallManagedByOtherSession(callId: String) + + /** + * Called when an asserted identity event is received + */ + fun onCallAssertedIdentityReceived(callAssertedIdentityContent: CallAssertedIdentityContent) } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/call/CallState.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/call/CallState.kt index 2dbd1c9b01bf03bac7a6176552e69cb279fc8c46..47a63b4a251a48d260c8c4d081858adc1f729107 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/call/CallState.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/call/CallState.kt @@ -16,6 +16,8 @@ package org.matrix.android.sdk.api.session.call +import org.matrix.android.sdk.api.session.room.model.call.EndCallReason + sealed class CallState { /** Idle, setting up objects */ @@ -42,6 +44,6 @@ sealed class CallState { * */ data class Connected(val iceConnectionState: MxPeerConnectionState) : CallState() - /** Terminated. Incoming/Outgoing call, the call is terminated */ - object Terminated : CallState() + /** Ended. Incoming/Outgoing call, the call is terminated */ + data class Ended(val reason: EndCallReason? = null) : CallState() } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/call/MxCall.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/call/MxCall.kt index fcc9f7072dee4ae863648e7418bde20a633a3a16..dd23e81cc6a062697aa29bf1322727b6e6ed2697 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/call/MxCall.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/call/MxCall.kt @@ -18,7 +18,7 @@ package org.matrix.android.sdk.api.session.call import org.matrix.android.sdk.api.session.room.model.call.CallCandidate import org.matrix.android.sdk.api.session.room.model.call.CallCapabilities -import org.matrix.android.sdk.api.session.room.model.call.CallHangupContent +import org.matrix.android.sdk.api.session.room.model.call.EndCallReason import org.matrix.android.sdk.api.session.room.model.call.SdpType import org.matrix.android.sdk.api.util.Optional @@ -69,7 +69,7 @@ interface MxCall : MxCallDetail { /** * End the call */ - fun hangUp(reason: CallHangupContent.Reason? = null) + fun hangUp(reason: EndCallReason? = null) /** * Start a call diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/content/ContentAttachmentData.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/content/ContentAttachmentData.kt index 98a84b8b662176a71685f37e52fd8cfe83e18d37..7ee26de8db3063903fce7d1f282f560963b4a46d 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/content/ContentAttachmentData.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/content/ContentAttachmentData.kt @@ -35,7 +35,8 @@ data class ContentAttachmentData( val name: String? = null, val queryUri: Uri, val mimeType: String?, - val type: Type + val type: Type, + val waveform: List<Int>? = null ) : Parcelable { @JsonClass(generateAdapter = false) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/CryptoService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/CryptoService.kt index 1b7a5243e2ba00d191f21f6849f399ce164d9ef0..e3f00a24b669c6aebad3ff375700e8c26f37506f 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/CryptoService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/CryptoService.kt @@ -42,7 +42,6 @@ import org.matrix.android.sdk.internal.crypto.model.event.RoomKeyWithHeldContent import org.matrix.android.sdk.internal.crypto.model.rest.DeviceInfo import org.matrix.android.sdk.internal.crypto.model.rest.DevicesListResponse import org.matrix.android.sdk.internal.crypto.model.rest.RoomKeyRequestBody -import kotlin.jvm.Throws interface CryptoService { @@ -82,9 +81,11 @@ interface CryptoService { fun getDeviceTrackingStatus(userId: String): Int - fun importRoomKeys(roomKeysAsArray: ByteArray, password: String, progressListener: ProgressListener?, callback: MatrixCallback<ImportRoomKeysResult>) + suspend fun importRoomKeys(roomKeysAsArray: ByteArray, + password: String, + progressListener: ProgressListener?): ImportRoomKeysResult - fun exportRoomKeys(password: String, callback: MatrixCallback<ByteArray>) + suspend fun exportRoomKeys(password: String): ByteArray fun setRoomBlacklistUnverifiedDevices(roomId: String) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/keysbackup/KeysBackupService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/keysbackup/KeysBackupService.kt index 465d00168fd804b4cbeb7126e554a0b17e824eba..4464427b903ae748b91effc15043bd01656387a8 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/keysbackup/KeysBackupService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/keysbackup/KeysBackupService.kt @@ -28,7 +28,7 @@ import org.matrix.android.sdk.internal.crypto.store.SavedKeyBackupKeyInfo interface KeysBackupService { /** - * Retrieve the current version of the backup from the home server + * Retrieve the current version of the backup from the homeserver * * It can be different than keysBackupVersion. * @param callback onSuccess(null) will be called if there is no backup on the server diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/keysbackup/KeysBackupState.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/keysbackup/KeysBackupState.kt index 7d0f04ebcfc8147e636264622f87a22abc907e73..a4cc133398203ae4af23b47cc2d8343e4afd09db 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/keysbackup/KeysBackupState.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/keysbackup/KeysBackupState.kt @@ -54,7 +54,7 @@ enum class KeysBackupState { // Need to check the current backup version on the homeserver Unknown, - // Checking if backup is enabled on home server + // Checking if backup is enabled on homeserver CheckingBackUpOnHomeserver, // Backup has been stopped because a new backup version has been detected on the homeserver diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/events/model/Event.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/events/model/Event.kt index 6400dd644408f59b01bd2858bb0f4613e485fe57..3d82846e7e98fd9c2370dcfac5969267e7638b88 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/events/model/Event.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/events/model/Event.kt @@ -104,7 +104,7 @@ data class Event( /** * The `age` value transcoded in a timestamp based on the device clock when the SDK received - * the event from the home server. + * the event from the homeserver. * Unlike `age`, this value is static. */ @Transient diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/events/model/EventType.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/events/model/EventType.kt index 229a53fa9d71f231afe873a806f7d4fc6e72ab66..9c3fdd57daa4fd2c4ff7d851a53dd4a1fa535520 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/events/model/EventType.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/events/model/EventType.kt @@ -76,6 +76,8 @@ object EventType { const val CALL_NEGOTIATE = "m.call.negotiate" const val CALL_REJECT = "m.call.reject" const val CALL_HANGUP = "m.call.hangup" + const val CALL_ASSERTED_IDENTITY = "m.call.asserted_identity" + const val CALL_ASSERTED_IDENTITY_PREFIX = "org.matrix.call.asserted_identity" // This type is not processed by the client, just sent to the server const val CALL_REPLACES = "m.call.replaces" diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/homeserver/HomeServerCapabilities.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/homeserver/HomeServerCapabilities.kt index da99ab8d54e070ed4017b18adecc93bd30f0a0da..b49236c3382eba41deb76eef8a3c3e4c026d8d9d 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/homeserver/HomeServerCapabilities.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/homeserver/HomeServerCapabilities.kt @@ -32,9 +32,71 @@ data class HomeServerCapabilities( /** * Default identity server url, provided in Wellknown */ - val defaultIdentityServerUrl: String? = null + val defaultIdentityServerUrl: String? = null, + /** + * Room versions supported by the server + * This capability describes the default and available room versions a server supports, and at what level of stability. + * Clients should make use of this capability to determine if users need to be encouraged to upgrade their rooms. + */ + val roomVersions: RoomVersionCapabilities? = null ) { + + enum class RoomCapabilitySupport { + SUPPORTED, + SUPPORTED_UNSTABLE, + UNSUPPORTED, + UNKNOWN + } + + /** + * Check if a feature is supported by the homeserver. + * @return + * UNKNOWN if the server does not implement room caps + * UNSUPPORTED if this feature is not supported + * SUPPORTED if this feature is supported by a stable version + * SUPPORTED_UNSTABLE if this feature is supported by an unstable version + * (unstable version should only be used for dev/experimental purpose) + */ + fun isFeatureSupported(feature: String): RoomCapabilitySupport { + if (roomVersions?.capabilities == null) return RoomCapabilitySupport.UNKNOWN + val info = roomVersions.capabilities[feature] ?: return RoomCapabilitySupport.UNSUPPORTED + + val preferred = info.preferred ?: info.support.lastOrNull() + val versionCap = roomVersions.supportedVersion.firstOrNull { it.version == preferred } + + return when { + versionCap == null -> { + RoomCapabilitySupport.UNKNOWN + } + versionCap.status == RoomVersionStatus.STABLE -> { + RoomCapabilitySupport.SUPPORTED + } + else -> { + RoomCapabilitySupport.SUPPORTED_UNSTABLE + } + } + } + fun isFeatureSupported(feature: String, byRoomVersion: String): Boolean { + if (roomVersions?.capabilities == null) return false + val info = roomVersions.capabilities[feature] ?: return false + + return info.preferred == byRoomVersion || info.support.contains(byRoomVersion) + } + + /** + * Use this method to know if you should force a version when creating + * a room that requires this feature. + * You can also use #isFeatureSupported prior to this call to check if the + * feature is supported and report some feedback to user. + */ + fun versionOverrideForFeature(feature: String) : String? { + val cap = roomVersions?.capabilities?.get(feature) + return cap?.preferred ?: cap?.support?.lastOrNull() + } + companion object { const val MAX_UPLOAD_FILE_SIZE_UNKNOWN = -1L + const val ROOM_CAP_KNOCK = "knock" + const val ROOM_CAP_RESTRICTED = "restricted" } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/homeserver/RoomVersionModel.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/homeserver/RoomVersionModel.kt new file mode 100644 index 0000000000000000000000000000000000000000..9f8e9aa1d122a89423f440b5263fcb0773ed93cf --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/homeserver/RoomVersionModel.kt @@ -0,0 +1,39 @@ +/* + * Copyright 2021 The Matrix.org Foundation C.I.C. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.matrix.android.sdk.api.session.homeserver + +data class RoomVersionCapabilities( + val defaultRoomVersion: String, + val supportedVersion: List<RoomVersionInfo>, + // Keys are capabilities defined per spec, as for now knock or restricted + val capabilities: Map<String, RoomCapabilitySupport>? +) + +data class RoomVersionInfo( + val version: String, + val status: RoomVersionStatus +) + +data class RoomCapabilitySupport( + val preferred: String?, + val support: List<String> +) + +enum class RoomVersionStatus { + STABLE, + UNSTABLE +} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/identity/IdentityService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/identity/IdentityService.kt index 8f8967e8fb25994d35339f50b09fe348e8662d39..ae546b6cece199cb2cfc5adc847098b3a74f181f 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/identity/IdentityService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/identity/IdentityService.kt @@ -35,7 +35,7 @@ interface IdentityService { /** * Check if the identity server is valid * See https://matrix.org/docs/spec/identity_service/latest#status-check - * RiotX SDK only supports identity server API v2 + * Matrix Android SDK2 only supports identity server API v2 */ suspend fun isValidIdentityServer(url: String) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/AliasAvailabilityResult.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/AliasAvailabilityResult.kt new file mode 100644 index 0000000000000000000000000000000000000000..6f607569c0816e71dd57cd4a2041c59ab4450d9d --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/AliasAvailabilityResult.kt @@ -0,0 +1,24 @@ +/* + * Copyright 2021 The Matrix.org Foundation C.I.C. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.matrix.android.sdk.api.session.room + +import org.matrix.android.sdk.api.session.room.alias.RoomAliasError + +sealed class AliasAvailabilityResult { + object Available: AliasAvailabilityResult() + data class NotAvailable(val roomAliasError: RoomAliasError) : AliasAvailabilityResult() +} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/Room.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/Room.kt index 41bcbf8ff6f7541974e6ce377cde5cb2cbf3705d..ebe96b638298096ae360f11f0c46e6a512d1ced2 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/Room.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/Room.kt @@ -17,7 +17,7 @@ package org.matrix.android.sdk.api.session.room import androidx.lifecycle.LiveData -import org.matrix.android.sdk.api.session.accountdata.AccountDataService +import org.matrix.android.sdk.api.session.room.accountdata.RoomAccountDataService import org.matrix.android.sdk.api.session.room.alias.AliasService import org.matrix.android.sdk.api.session.room.call.RoomCallService import org.matrix.android.sdk.api.session.room.crypto.RoomCryptoService @@ -34,6 +34,7 @@ import org.matrix.android.sdk.api.session.room.tags.TagsService import org.matrix.android.sdk.api.session.room.timeline.TimelineService import org.matrix.android.sdk.api.session.room.typing.TypingService import org.matrix.android.sdk.api.session.room.uploads.UploadsService +import org.matrix.android.sdk.api.session.room.version.RoomVersionService import org.matrix.android.sdk.api.session.search.SearchResult import org.matrix.android.sdk.api.session.space.Space import org.matrix.android.sdk.api.util.Optional @@ -57,7 +58,8 @@ interface Room : RelationService, RoomCryptoService, RoomPushRuleService, - AccountDataService { + RoomAccountDataService, + RoomVersionService { /** * The roomId of this room diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/RoomDirectoryService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/RoomDirectoryService.kt index 176de8e408be3108d8307c5736a8e0c49bdacf4f..f3e3913bc124729dee05a6366d20069114c172fc 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/RoomDirectoryService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/RoomDirectoryService.kt @@ -40,4 +40,6 @@ interface RoomDirectoryService { * Set the visibility of a room in the directory */ suspend fun setRoomDirectoryVisibility(roomId: String, roomDirectoryVisibility: RoomDirectoryVisibility) + + suspend fun checkAliasAvailability(aliasLocalPart: String?) : AliasAvailabilityResult } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/RoomService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/RoomService.kt index 871c5378a6c19fcbbc76f3fdc403e55441d984d5..b7377df1b3da4e3e7c153524116b42fbf5f37d52 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/RoomService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/RoomService.kt @@ -125,6 +125,12 @@ interface RoomService { */ suspend fun deleteRoomAlias(roomAlias: String) + /** + * Return the current local changes membership for the given room. + * see [getChangeMembershipsLive] for more details. + */ + fun getChangeMemberships(roomIdOrAlias: String): ChangeMembershipState + /** * Return a live data of all local changes membership that happened since the session has been opened. * It allows you to track this in your client to known what is currently being processed by the SDK. diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/RoomSummaryQueryParams.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/RoomSummaryQueryParams.kt index 88ec2de768a4774e7ac19b8ae7de9d8db34eb067..b4408575187b8ec5d362980ada7d9e0aa135cef4 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/RoomSummaryQueryParams.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/RoomSummaryQueryParams.kt @@ -39,12 +39,6 @@ fun spaceSummaryQueryParams(init: (RoomSummaryQueryParams.Builder.() -> Unit) = .build() } -enum class RoomCategoryFilter { - ONLY_DM, - ONLY_ROOMS, - ALL -} - /** * This class can be used to filter room summaries to use with: * [org.matrix.android.sdk.api.session.room.Room] and [org.matrix.android.sdk.api.session.room.RoomService] @@ -59,11 +53,10 @@ data class RoomSummaryQueryParams( val excludeType: List<String?>?, val includeType: List<String?>?, val activeSpaceFilter: ActiveSpaceFilter?, - var activeGroupId: String? = null + val activeGroupId: String? = null ) { class Builder { - var roomId: QueryStringValue = QueryStringValue.IsNotEmpty var displayName: QueryStringValue = QueryStringValue.IsNotEmpty var canonicalAlias: QueryStringValue = QueryStringValue.NoCondition diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/accountdata/RoomAccountDataEvent.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/accountdata/RoomAccountDataEvent.kt new file mode 100644 index 0000000000000000000000000000000000000000..eb676ab5e7666c52c9fb9583ad8b53d6ca33d6fd --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/accountdata/RoomAccountDataEvent.kt @@ -0,0 +1,29 @@ +/* + * Copyright 2020 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.api.session.room.accountdata + +import org.matrix.android.sdk.api.session.events.model.Content + +/** + * This is a simplified Event with just a roomId, a type and a content. + * Currently used types are defined in [RoomAccountDataTypes]. + */ +data class RoomAccountDataEvent( + val roomId: String, + val type: String, + val content: Content +) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/accountdata/AccountDataService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/accountdata/RoomAccountDataService.kt similarity index 81% rename from matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/accountdata/AccountDataService.kt rename to matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/accountdata/RoomAccountDataService.kt index 77f3eb0cd92f673619fe6fe8b528cc78e78b0111..190749c85cee92957fe16e1729a898d51b0da7f2 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/accountdata/AccountDataService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/accountdata/RoomAccountDataService.kt @@ -14,37 +14,37 @@ * limitations under the License. */ -package org.matrix.android.sdk.api.session.accountdata +package org.matrix.android.sdk.api.session.room.accountdata import androidx.lifecycle.LiveData import org.matrix.android.sdk.api.session.events.model.Content import org.matrix.android.sdk.api.util.Optional /** - * This service can be attached globally to the session so it represents user data or attached to a single room. + * This service is attached to a single room. */ -interface AccountDataService { +interface RoomAccountDataService { /** * Retrieve the account data with the provided type or null if not found */ - fun getAccountDataEvent(type: String): AccountDataEvent? + fun getAccountDataEvent(type: String): RoomAccountDataEvent? /** * Observe the account data with the provided type */ - fun getLiveAccountDataEvent(type: String): LiveData<Optional<AccountDataEvent>> + fun getLiveAccountDataEvent(type: String): LiveData<Optional<RoomAccountDataEvent>> /** * Retrieve the account data with the provided types. The return list can have a different size that * the size of the types set, because some AccountData may not exist. * If an empty set is provided, all the AccountData are retrieved */ - fun getAccountDataEvents(types: Set<String>): List<AccountDataEvent> + fun getAccountDataEvents(types: Set<String>): List<RoomAccountDataEvent> /** * Observe the account data with the provided types. If an empty set is provided, all the AccountData are observed */ - fun getLiveAccountDataEvents(types: Set<String>): LiveData<List<AccountDataEvent>> + fun getLiveAccountDataEvents(types: Set<String>): LiveData<List<RoomAccountDataEvent>> /** * Update the account data with the provided type and the provided account data content diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/accountdata/RoomAccountDataTypes.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/accountdata/RoomAccountDataTypes.kt index 0e80c307b4f9a7fb9bfb94101642fcc75eb9c849..96eb86c0d65c7946a9f4a35efc57a40751bc4815 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/accountdata/RoomAccountDataTypes.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/accountdata/RoomAccountDataTypes.kt @@ -20,4 +20,5 @@ object RoomAccountDataTypes { const val EVENT_TYPE_VIRTUAL_ROOM = "im.vector.is_virtual_room" const val EVENT_TYPE_TAG = "m.tag" const val EVENT_TYPE_FULLY_READ = "m.fully_read" + const val EVENT_TYPE_SPACE_ORDER = "org.matrix.msc3230.space_order" // m.space_order } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/call/CallAssertedIdentityContent.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/call/CallAssertedIdentityContent.kt new file mode 100644 index 0000000000000000000000000000000000000000..4c5413425fc84a51bb4b74b8b68bb217ddd12b9a --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/call/CallAssertedIdentityContent.kt @@ -0,0 +1,57 @@ +/* + * Copyright 2021 The Matrix.org Foundation C.I.C. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.matrix.android.sdk.api.session.room.model.call + +import com.squareup.moshi.Json +import com.squareup.moshi.JsonClass + +/** + * This event is sent by the callee when they wish to answer the call. + */ +@JsonClass(generateAdapter = true) +data class CallAssertedIdentityContent( + /** + * Required. The ID of the call this event relates to. + */ + @Json(name = "call_id") override val callId: String, + /** + * Required. ID to let user identify remote echo of their own events + */ + @Json(name = "party_id") override val partyId: String? = null, + /** + * Required. The version of the VoIP specification this messages adheres to. + */ + @Json(name = "version") override val version: String?, + + /** + * Optional. Used to inform the transferee who they're now speaking to. + */ + @Json(name = "asserted_identity") val assertedIdentity: AssertedIdentity? = null +) : CallSignalingContent { + + /** + * A user ID may be included if relevant, but unlike target_user, it is purely informational. + * The asserted identity may not represent a matrix user at all, + * in which case just a display_name may be given, or a perhaps a display_name and avatar_url. + */ + @JsonClass(generateAdapter = true) + data class AssertedIdentity( + @Json(name = "id") val id: String? = null, + @Json(name = "display_name") val displayName: String? = null, + @Json(name = "avatar_url") val avatarUrl: String? = null + ) +} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/call/CallHangupContent.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/call/CallHangupContent.kt index 9d6e1a7eaeb09e39308235fdb543827a45694c70..31f801dd6f7b1e8615515920fbfef6c3bd451109 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/call/CallHangupContent.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/call/CallHangupContent.kt @@ -43,29 +43,5 @@ data class CallHangupContent( * or `invite_timeout` for when the other party did not answer in time. * One of: ["ice_failed", "invite_timeout"] */ - @Json(name = "reason") val reason: Reason? = null -) : CallSignalingContent { - @JsonClass(generateAdapter = false) - enum class Reason { - @Json(name = "ice_failed") - ICE_FAILED, - - @Json(name = "ice_timeout") - ICE_TIMEOUT, - - @Json(name = "user_hangup") - USER_HANGUP, - - @Json(name = "replaced") - REPLACED, - - @Json(name = "user_media_failed") - USER_MEDIA_FAILED, - - @Json(name = "invite_timeout") - INVITE_TIMEOUT, - - @Json(name = "unknown_error") - UNKWOWN_ERROR - } -} + @Json(name = "reason") val reason: EndCallReason? = null +) : CallSignalingContent diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/call/CallRejectContent.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/call/CallRejectContent.kt index ea412fbe3eba6fd353c671e6d3486020b79dfefd..1b9a7186e2d54c76aa821bce90c0df21821dc4f0 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/call/CallRejectContent.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/call/CallRejectContent.kt @@ -36,5 +36,10 @@ data class CallRejectContent( /** * Required. The version of the VoIP specification this message adheres to. */ - @Json(name = "version") override val version: String? + @Json(name = "version") override val version: String?, + + /** + * Optional error reason for the reject. + */ + @Json(name = "reason") val reason: EndCallReason? = null ) : CallSignalingContent diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/call/EndCallReason.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/call/EndCallReason.kt new file mode 100644 index 0000000000000000000000000000000000000000..60e038b2f939e8a2a13b73615a298dbad9b871ad --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/call/EndCallReason.kt @@ -0,0 +1,50 @@ +/* + * Copyright (c) 2021 The Matrix.org Foundation C.I.C. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.matrix.android.sdk.api.session.room.model.call + +import com.squareup.moshi.Json +import com.squareup.moshi.JsonClass + +@JsonClass(generateAdapter = false) +enum class EndCallReason { + @Json(name = "ice_failed") + ICE_FAILED, + + @Json(name = "ice_timeout") + ICE_TIMEOUT, + + @Json(name = "user_hangup") + USER_HANGUP, + + @Json(name = "replaced") + REPLACED, + + @Json(name = "user_media_failed") + USER_MEDIA_FAILED, + + @Json(name = "invite_timeout") + INVITE_TIMEOUT, + + @Json(name = "unknown_error") + UNKWOWN_ERROR, + + @Json(name = "user_busy") + USER_BUSY, + + @Json(name = "answered_elsewhere") + ANSWERED_ELSEWHERE +} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/create/CreateRoomParams.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/create/CreateRoomParams.kt index ca8c66bb3b5b9115a4811c2ab11255e05a139612..566790600036cd34616faeea7261b9148e367e4b 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/create/CreateRoomParams.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/create/CreateRoomParams.kt @@ -22,10 +22,8 @@ import org.matrix.android.sdk.api.session.room.model.GuestAccess import org.matrix.android.sdk.api.session.room.model.PowerLevelsContent import org.matrix.android.sdk.api.session.room.model.RoomDirectoryVisibility import org.matrix.android.sdk.api.session.room.model.RoomHistoryVisibility -import org.matrix.android.sdk.api.session.room.model.RoomJoinRulesAllowEntry import org.matrix.android.sdk.internal.crypto.MXCRYPTO_ALGORITHM_MEGOLM -// TODO Give a way to include other initial states open class CreateRoomParams { /** * A public visibility indicates that the room will be shown in the published room list. @@ -103,6 +101,13 @@ open class CreateRoomParams { */ val creationContent = mutableMapOf<String, Any>() + /** + * A list of state events to set in the new room. This allows the user to override the default state events + * set in the new room. The expected format of the state events are an object with type, state_key and content keys set. + * Takes precedence over events set by preset, but gets overridden by name and topic keys. + */ + val initialStates = mutableListOf<CreateRoomStateEvent>() + /** * Set to true to disable federation of this room. * Default: false @@ -156,7 +161,7 @@ open class CreateRoomParams { var roomVersion: String? = null - var joinRuleRestricted: List<RoomJoinRulesAllowEntry>? = null + var featurePreset: RoomFeaturePreset? = null companion object { private const val CREATION_CONTENT_KEY_M_FEDERATE = "m.federate" diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/create/CreateRoomStateEvent.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/create/CreateRoomStateEvent.kt new file mode 100644 index 0000000000000000000000000000000000000000..fcfdc3e33369bca09e0eb728fc4097dd32b39ee6 --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/create/CreateRoomStateEvent.kt @@ -0,0 +1,36 @@ +/* + * Copyright (c) 2021 The Matrix.org Foundation C.I.C. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.matrix.android.sdk.api.session.room.model.create + +import org.matrix.android.sdk.api.session.events.model.Content + +data class CreateRoomStateEvent( + /** + * Required. The type of event to send. + */ + val type: String, + + /** + * Required. The content of the event. + */ + val content: Content, + + /** + * The state_key of the state event. Defaults to an empty string. + */ + val stateKey: String = "" +) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/create/RoomFeaturePreset.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/create/RoomFeaturePreset.kt new file mode 100644 index 0000000000000000000000000000000000000000..f5f722d7833059eaa3d2d81d00f287249df0641b --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/create/RoomFeaturePreset.kt @@ -0,0 +1,56 @@ +/* + * Copyright 2020 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.api.session.room.model.create + +import org.matrix.android.sdk.api.session.events.model.Event +import org.matrix.android.sdk.api.session.events.model.EventType +import org.matrix.android.sdk.api.session.events.model.toContent +import org.matrix.android.sdk.api.session.homeserver.HomeServerCapabilities +import org.matrix.android.sdk.api.session.room.model.GuestAccess +import org.matrix.android.sdk.api.session.room.model.RoomHistoryVisibility +import org.matrix.android.sdk.api.session.room.model.RoomJoinRules +import org.matrix.android.sdk.api.session.room.model.RoomJoinRulesAllowEntry +import org.matrix.android.sdk.api.session.room.model.RoomJoinRulesContent + +interface RoomFeaturePreset { + + fun updateRoomParams(params: CreateRoomParams) + + fun setupInitialStates(): List<Event>? +} + +class RestrictedRoomPreset(val homeServerCapabilities: HomeServerCapabilities, val restrictedList: List<RoomJoinRulesAllowEntry>) : RoomFeaturePreset { + + override fun updateRoomParams(params: CreateRoomParams) { + params.historyVisibility = params.historyVisibility ?: RoomHistoryVisibility.SHARED + params.guestAccess = params.guestAccess ?: GuestAccess.Forbidden + params.roomVersion = homeServerCapabilities.versionOverrideForFeature(HomeServerCapabilities.ROOM_CAP_RESTRICTED) + } + + override fun setupInitialStates(): List<Event>? { + return listOf( + Event( + type = EventType.STATE_ROOM_JOIN_RULES, + stateKey = "", + content = RoomJoinRulesContent( + _joinRules = RoomJoinRules.RESTRICTED.value, + allowList = restrictedList + ).toContent() + ) + ) + } +} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/AudioInfo.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/AudioInfo.kt index b72c8e5dfd07f8e360cb18af295fb7b7da929b28..ea6bdfafadce97842964c8f2f30b105f85ad3c13 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/AudioInfo.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/AudioInfo.kt @@ -24,15 +24,15 @@ data class AudioInfo( /** * The mimetype of the audio e.g. "audio/aac". */ - @Json(name = "mimetype") val mimeType: String?, + @Json(name = "mimetype") val mimeType: String? = null, /** * The size of the audio clip in bytes. */ - @Json(name = "size") val size: Long = 0, + @Json(name = "size") val size: Long? = null, /** * The duration of the audio in milliseconds. */ - @Json(name = "duration") val duration: Int = 0 + @Json(name = "duration") val duration: Int? = null ) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/AudioWaveformInfo.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/AudioWaveformInfo.kt new file mode 100644 index 0000000000000000000000000000000000000000..d576f1057a29fa8a42e4b66f375b6f7acc5a62a4 --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/AudioWaveformInfo.kt @@ -0,0 +1,36 @@ +/* + * Copyright 2020 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.api.session.room.model.message + +import com.squareup.moshi.Json +import com.squareup.moshi.JsonClass + +/** + * See https://github.com/matrix-org/matrix-doc/blob/travis/msc/audio-waveform/proposals/3246-audio-waveform.md + */ +@JsonClass(generateAdapter = true) +data class AudioWaveformInfo( + @Json(name = "duration") + val duration: Int? = null, + + /** + * The array should have no less than 30 elements and no more than 120. + * List of integers between zero and 1024, inclusive. + */ + @Json(name = "waveform") + val waveform: List<Int>? = null +) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/MessageAudioContent.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/MessageAudioContent.kt index fcf3d73cbea13dcedd4deeef9ec0263c2c68af74..1bcb10d88cf753987af8c51e7acf373df5833737 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/MessageAudioContent.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/MessageAudioContent.kt @@ -20,6 +20,7 @@ import com.squareup.moshi.Json import com.squareup.moshi.JsonClass import org.matrix.android.sdk.api.session.events.model.Content import org.matrix.android.sdk.api.session.room.model.relation.RelationDefaultContent +import org.matrix.android.sdk.api.util.JsonDict import org.matrix.android.sdk.internal.crypto.model.rest.EncryptedFileInfo @JsonClass(generateAdapter = true) @@ -50,9 +51,19 @@ data class MessageAudioContent( /** * Required if the file is encrypted. Information on the encrypted file, as specified in End-to-end encryption. */ - @Json(name = "file") override val encryptedFileInfo: EncryptedFileInfo? = null + @Json(name = "file") override val encryptedFileInfo: EncryptedFileInfo? = null, + + /** + * Encapsulates waveform and duration of the audio. + */ + @Json(name = "org.matrix.msc1767.audio") val audioWaveformInfo: AudioWaveformInfo? = null, + + /** + * Indicates that is a voice message. + */ + @Json(name = "org.matrix.msc3245.voice") val voiceMessageIndicator: JsonDict? = null ) : MessageWithAttachmentContent { override val mimeType: String? - get() = encryptedFileInfo?.mimetype ?: audioInfo?.mimeType + get() = audioInfo?.mimeType } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/MessageFileContent.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/MessageFileContent.kt index d93f115322acc5acc0efa4e977c125bc015e001e..96877b4d9f12cce11be1ae623ab45a6806894c99 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/MessageFileContent.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/MessageFileContent.kt @@ -60,8 +60,7 @@ data class MessageFileContent( ) : MessageWithAttachmentContent { override val mimeType: String? - get() = encryptedFileInfo?.mimetype - ?: info?.mimeType + get() = info?.mimeType ?: MimeTypeMap.getFileExtensionFromUrl(filename ?: body)?.let { extension -> MimeTypeMap.getSingleton().getMimeTypeFromExtension(extension) } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/MessageImageContent.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/MessageImageContent.kt index 73e27b64e31925bcbae609535c7339f59f8873b2..73fd1eab56522bf0a83278ba66ace436b1b3b5ca 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/MessageImageContent.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/MessageImageContent.kt @@ -20,7 +20,6 @@ import com.squareup.moshi.Json import com.squareup.moshi.JsonClass import org.matrix.android.sdk.api.session.events.model.Content import org.matrix.android.sdk.api.session.room.model.relation.RelationDefaultContent -import org.matrix.android.sdk.api.util.MimeTypes import org.matrix.android.sdk.internal.crypto.model.rest.EncryptedFileInfo @JsonClass(generateAdapter = true) @@ -55,5 +54,5 @@ data class MessageImageContent( @Json(name = "file") override val encryptedFileInfo: EncryptedFileInfo? = null ) : MessageImageInfoContent { override val mimeType: String? - get() = encryptedFileInfo?.mimetype ?: info?.mimeType ?: MimeTypes.Images + get() = info?.mimeType } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/MessageStickerContent.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/MessageStickerContent.kt index 280316d4b5208ee1280cbb5740d9826460b80b93..8e1d4d3d7589f286643740faddec1b1220720f04 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/MessageStickerContent.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/MessageStickerContent.kt @@ -55,5 +55,5 @@ data class MessageStickerContent( @Json(name = "file") override val encryptedFileInfo: EncryptedFileInfo? = null ) : MessageImageInfoContent { override val mimeType: String? - get() = encryptedFileInfo?.mimetype ?: info?.mimeType + get() = info?.mimeType } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/MessageVideoContent.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/MessageVideoContent.kt index b7581c9fbf8fcf81be490eb84fef0b89b23b9f10..3f5d2dab2e770d189b538b71d16d07fef5c1627c 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/MessageVideoContent.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/MessageVideoContent.kt @@ -53,5 +53,5 @@ data class MessageVideoContent( @Json(name = "file") override val encryptedFileInfo: EncryptedFileInfo? = null ) : MessageWithAttachmentContent { override val mimeType: String? - get() = encryptedFileInfo?.mimetype ?: videoInfo?.mimeType + get() = videoInfo?.mimeType } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/state/StateService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/state/StateService.kt index e614ea91d649b8cd712926d78c0a1a6176bdb2a8..4d3f95233d05d54e8202c1a48084b9913ecb001b 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/state/StateService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/state/StateService.kt @@ -23,6 +23,7 @@ import org.matrix.android.sdk.api.session.events.model.Event import org.matrix.android.sdk.api.session.room.model.GuestAccess import org.matrix.android.sdk.api.session.room.model.RoomHistoryVisibility import org.matrix.android.sdk.api.session.room.model.RoomJoinRules +import org.matrix.android.sdk.api.session.room.model.RoomJoinRulesAllowEntry import org.matrix.android.sdk.api.util.JsonDict import org.matrix.android.sdk.api.util.Optional @@ -53,7 +54,7 @@ interface StateService { /** * Update the join rule and/or the guest access */ - suspend fun updateJoinRule(joinRules: RoomJoinRules?, guestAccess: GuestAccess?) + suspend fun updateJoinRule(joinRules: RoomJoinRules?, guestAccess: GuestAccess?, allowList: List<RoomJoinRulesAllowEntry>? = null) /** * Update the avatar of the room @@ -91,4 +92,8 @@ interface StateService { * @param eventTypes Set of eventType to observe. If empty, all state events will be observed */ fun getStateEventsLive(eventTypes: Set<String>, stateKey: QueryStringValue = QueryStringValue.NoCondition): LiveData<List<Event>> + + suspend fun setJoinRulePublic() + suspend fun setJoinRuleInviteOnly() + suspend fun setJoinRuleRestricted(allowList: List<String>) } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/version/RoomVersionService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/version/RoomVersionService.kt new file mode 100644 index 0000000000000000000000000000000000000000..ea67b55174c1fa9609d9932724266dd869b6e6fd --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/version/RoomVersionService.kt @@ -0,0 +1,45 @@ +/* + * Copyright 2021 The Matrix.org Foundation C.I.C. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.matrix.android.sdk.api.session.room.version + +interface RoomVersionService { + /** + * Return the room version of this room + */ + fun getRoomVersion(): String + + /** + * Upgrade to the given room version + * @return the replacement room id + */ + suspend fun upgradeToVersion(version: String): String + + /** + * Get the recommended room version for the current homeserver + */ + fun getRecommendedVersion() : String + + /** + * Ask if the user has enough power level to upgrade the room + */ + fun userMayUpgradeRoom(userId: String): Boolean + + /** + * Return true if the current room version is declared unstable by the homeserver + */ + fun isUsingUnstableRoomVersion(): Boolean +} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/space/Space.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/space/Space.kt index db25762c2f2b7436e71a9f25754e1bc1b7b1d22e..3bae6126e0722e5c12be00c82664865a2bdacd6a 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/space/Space.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/space/Space.kt @@ -18,6 +18,7 @@ package org.matrix.android.sdk.api.session.space import org.matrix.android.sdk.api.session.room.Room import org.matrix.android.sdk.api.session.room.model.RoomSummary +import org.matrix.android.sdk.api.session.space.model.SpaceChildContent interface Space { @@ -38,6 +39,8 @@ interface Space { autoJoin: Boolean = false, suggested: Boolean? = false) + fun getChildInfo(roomId: String): SpaceChildContent? + suspend fun removeChildren(roomId: String) @Throws diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/space/SpaceOrderUtils.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/space/SpaceOrderUtils.kt new file mode 100644 index 0000000000000000000000000000000000000000..844a5adcb4ae2a49c6cbf8bdb9cc77b0049c9fa5 --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/space/SpaceOrderUtils.kt @@ -0,0 +1,105 @@ +/* + * Copyright 2021 The Matrix.org Foundation C.I.C. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.matrix.android.sdk.api.session.space + +import org.matrix.android.sdk.api.util.StringOrderUtils + +/** + * Adds some utilities to compute correct string orders when ordering spaces. + * After moving a space (e.g via DnD), client should limit the number of room account data update. + * For example if the space is moved between two other spaces with orders, just update the moved space order by computing + * a mid point between the surrounding orders. + * If the space is moved after a space with no order, all the previous spaces should be then ordered, + * and the computed orders should be chosen so that there is enough gaps in between them to facilitate future re-order. + * Re numbering (i.e change all spaces m.space.order account data) should be avoided as much as possible, + * as the updates might not be atomic for other clients and would makes spaces jump around. + */ +object SpaceOrderUtils { + + data class SpaceReOrderCommand( + val spaceId: String, + val order: String + ) + + /** + * Returns a minimal list of order change in order to re order the space list as per given move. + */ + fun orderCommandsForMove(orderedSpacesToOrderMap: List<Pair<String, String?>>, movedSpaceId: String, delta: Int): List<SpaceReOrderCommand> { + val movedIndex = orderedSpacesToOrderMap.indexOfFirst { it.first == movedSpaceId } + if (movedIndex == -1) return emptyList() + if (delta == 0) return emptyList() + + val targetIndex = if (delta > 0) movedIndex + delta else (movedIndex + delta - 1) + + val nodesToReNumber = mutableListOf<String>() + var lowerBondOrder: String? = null + var index = targetIndex + while (index >= 0 && lowerBondOrder == null) { + val node = orderedSpacesToOrderMap.getOrNull(index) + if (node != null /*null when adding at the end*/) { + val nodeOrder = node.second + if (node.first == movedSpaceId) break + if (nodeOrder == null) { + nodesToReNumber.add(0, node.first) + } else { + lowerBondOrder = nodeOrder + } + } + index-- + } + nodesToReNumber.add(movedSpaceId) + val afterSpace: Pair<String, String?>? = if (orderedSpacesToOrderMap.indices.contains(targetIndex + 1)) { + orderedSpacesToOrderMap[targetIndex + 1] + } else null + + val defaultMaxOrder = CharArray(4) { StringOrderUtils.DEFAULT_ALPHABET.last() } + .joinToString("") + + val defaultMinOrder = CharArray(4) { StringOrderUtils.DEFAULT_ALPHABET.first() } + .joinToString("") + + val afterOrder = afterSpace?.second ?: defaultMaxOrder + + val beforeOrder = lowerBondOrder ?: defaultMinOrder + + val newOrder = StringOrderUtils.midPoints(beforeOrder, afterOrder, nodesToReNumber.size) + + if (newOrder.isNullOrEmpty()) { + // re order all? + val expectedList = orderedSpacesToOrderMap.toMutableList() + expectedList.removeAt(movedIndex).let { + expectedList.add(movedIndex + delta, it) + } + + return StringOrderUtils.midPoints(defaultMinOrder, defaultMaxOrder, orderedSpacesToOrderMap.size)?.let { orders -> + expectedList.mapIndexed { index, pair -> + SpaceReOrderCommand( + pair.first, + orders[index] + ) + } + } ?: emptyList() + } else { + return nodesToReNumber.mapIndexed { i, s -> + SpaceReOrderCommand( + s, + newOrder[i] + ) + } + } + } +} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/space/SpaceService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/space/SpaceService.kt index fedf38fe065053b30f22f9e0487889aa9595813b..e5288e4045bd2316f4f6897a0367e1b9d7e5cb3f 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/space/SpaceService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/space/SpaceService.kt @@ -36,7 +36,11 @@ interface SpaceService { /** * Just a shortcut for space creation for ease of use */ - suspend fun createSpace(name: String, topic: String?, avatarUri: Uri?, isPublic: Boolean): String + suspend fun createSpace(name: String, + topic: String?, + avatarUri: Uri?, + isPublic: Boolean, + roomAliasLocalPart: String? = null): String /** * Get a space from a roomId diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/space/model/SpaceOrderContent.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/space/model/SpaceOrderContent.kt new file mode 100644 index 0000000000000000000000000000000000000000..a8578347c87de3d05f0a5d7619175e08fa14b880 --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/space/model/SpaceOrderContent.kt @@ -0,0 +1,37 @@ +/* + * Copyright 2021 The Matrix.org Foundation C.I.C. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.matrix.android.sdk.api.session.space.model + +import com.squareup.moshi.JsonClass +import org.matrix.android.sdk.api.MatrixPatterns + +/** + * { + * "type": "m.space_order", + * "content": { + * "order": "..." + * } + * } + */ +@JsonClass(generateAdapter = true) +data class SpaceOrderContent( + val order: String? = null +) { + fun safeOrder(): String? { + return order?.takeIf { MatrixPatterns.isValidOrderString(it) } + } +} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/space/model/TopLevelSpaceComparator.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/space/model/TopLevelSpaceComparator.kt new file mode 100644 index 0000000000000000000000000000000000000000..8af4f3a149ae9ae89fa3206f75c6ad8f5ffd32c1 --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/space/model/TopLevelSpaceComparator.kt @@ -0,0 +1,44 @@ +/* + * Copyright 2021 The Matrix.org Foundation C.I.C. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.matrix.android.sdk.api.session.space.model + +import org.matrix.android.sdk.api.session.room.model.RoomSummary + +// Can't use regular compare by because Null is considered less than any value, and for space order it's the opposite +class TopLevelSpaceComparator(val orders: Map<String, String?>) : Comparator<RoomSummary> { + + override fun compare(left: RoomSummary?, right: RoomSummary?): Int { + val leftOrder = left?.roomId?.let { orders[it] } + val rightOrder = right?.roomId?.let { orders[it] } + return if (leftOrder != null && rightOrder != null) { + leftOrder.compareTo(rightOrder) + } else { + if (leftOrder == null) { + if (rightOrder == null) { + compareValues(left?.roomId, right?.roomId) + } else { + 1 + } + } else { + -1 + } + } +// .also { +// Timber.w("VAL: compare(${left?.displayName} | $leftOrder ,${right?.displayName} | $rightOrder) = $it") +// } + } +} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/util/MimeTypes.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/util/MimeTypes.kt index 182b37f2ad018e609a1d480fabb3659c427c59de..ef47775f1bec34033b2e42ab9207ed67de510c06 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/util/MimeTypes.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/util/MimeTypes.kt @@ -31,6 +31,8 @@ object MimeTypes { const val Jpeg = "image/jpeg" const val Gif = "image/gif" + const val Ogg = "audio/ogg" + fun String?.normalizeMimeType() = if (this == BadJpg) Jpeg else this fun String?.isMimeTypeImage() = this?.startsWith("image/").orFalse() diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/util/StringOrderUtils.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/util/StringOrderUtils.kt new file mode 100644 index 0000000000000000000000000000000000000000..83c858594167aa658d166b268c1830dc76fc4152 --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/util/StringOrderUtils.kt @@ -0,0 +1,87 @@ +/* + * Copyright 2021 The Matrix.org Foundation C.I.C. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.matrix.android.sdk.api.util + +import java.math.BigInteger + +object StringOrderUtils { + + val DEFAULT_ALPHABET = buildString { + for (i in 0x20..0x7E) { + append(Char(i)) + } + }.toCharArray() + + // /=Range(0x20, 0x7E) + + fun average(left: String, right: String, alphabet: CharArray = DEFAULT_ALPHABET): String? { + return midPoints(left, right, 1, alphabet)?.firstOrNull() + } + + fun midPoints(left: String, right: String, count: Int, alphabet: CharArray = DEFAULT_ALPHABET): List<String>? { + if (left == right) return null // no space in between.. + if (left > right) return midPoints(right, left, count, alphabet) + val size = maxOf(left.length, right.length) + val leftPadded = pad(left, size, alphabet.first()) + val rightPadded = pad(right, size, alphabet.first()) + val b1 = stringToBase(leftPadded, alphabet) + val b2 = stringToBase(rightPadded, alphabet) + val step = (b2.minus(b1)).div(BigInteger("${count + 1}")) + val orders = mutableListOf<String>() + var previous = left + for (i in 0 until count) { + val newOrder = baseToString(b1.add(step.multiply(BigInteger("${i + 1}"))), alphabet) + orders.add(newOrder) + // ensure there was enought precision + if (previous >= newOrder) return null + previous = newOrder + } + return orders.takeIf { orders.last() < right } + } + + private fun pad(string: String, size: Int, padding: Char): String { + val raw = string.toCharArray() + return CharArray(size).also { + for (index in it.indices) { + if (index < raw.size) { + it[index] = raw[index] + } else { + it[index] = padding + } + } + }.joinToString("") + } + + fun baseToString(x: BigInteger, alphabet: CharArray): String { + val len = alphabet.size.toBigInteger() + if (x < len) { + return alphabet[x.toInt()].toString() + } else { + return baseToString(x.div(len), alphabet) + alphabet[x.rem(len).toInt()].toString() + } + } + + fun stringToBase(x: String, alphabet: CharArray): BigInteger { + if (x.isEmpty()) throw IllegalArgumentException() + val len = alphabet.size.toBigInteger() + var result = BigInteger("0") + x.reversed().forEachIndexed { index, c -> + result += (alphabet.indexOf(c).toBigInteger() * len.pow(index)) + } + return result + } +} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/SessionManager.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/SessionManager.kt index 441232f57f375598b64ac2e88437f7c53bb84e92..c746ad863a276fe189cb684fea9688cdd5f1a72e 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/SessionManager.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/SessionManager.kt @@ -51,7 +51,7 @@ internal class SessionManager @Inject constructor(private val matrixComponent: M } } - private fun getOrCreateSessionComponent(sessionParams: SessionParams): SessionComponent { + fun getOrCreateSessionComponent(sessionParams: SessionParams): SessionComponent { return sessionComponents.getOrPut(sessionParams.credentials.sessionId()) { DaggerSessionComponent .factory() diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/AuthAPI.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/AuthAPI.kt index 5a9fa9edf65263338e8ba47845099cba1914d3bd..50d9e5a06c88a875824eba715b445d04e24bf85a 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/AuthAPI.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/AuthAPI.kt @@ -21,8 +21,8 @@ import org.matrix.android.sdk.api.util.JsonDict import org.matrix.android.sdk.internal.auth.data.Availability import org.matrix.android.sdk.internal.auth.data.LoginFlowResponse import org.matrix.android.sdk.internal.auth.data.PasswordLoginParams -import org.matrix.android.sdk.internal.auth.data.RiotConfig import org.matrix.android.sdk.internal.auth.data.TokenLoginParams +import org.matrix.android.sdk.internal.auth.data.WebClientConfig import org.matrix.android.sdk.internal.auth.login.ResetPasswordMailConfirmed import org.matrix.android.sdk.internal.auth.registration.AddThreePidRegistrationParams import org.matrix.android.sdk.internal.auth.registration.AddThreePidRegistrationResponse @@ -44,16 +44,16 @@ import retrofit2.http.Url */ internal interface AuthAPI { /** - * Get a Riot config file, using the name including the domain + * Get a Web client config file, using the name including the domain */ @GET("config.{domain}.json") - suspend fun getRiotConfigDomain(@Path("domain") domain: String): RiotConfig + suspend fun getWebClientConfigDomain(@Path("domain") domain: String): WebClientConfig /** - * Get a Riot config file + * Get a Web client default config file */ @GET("config.json") - suspend fun getRiotConfig(): RiotConfig + suspend fun getWebClientConfig(): WebClientConfig /** * Get the version information of the homeserver diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/DefaultAuthenticationService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/DefaultAuthenticationService.kt index 20ce438d8e8a44fb4fbb1a1201f95b0d43d1cad7..e76dc28734ae7bd43b67129c103876eab0d52368 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/DefaultAuthenticationService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/DefaultAuthenticationService.kt @@ -19,6 +19,8 @@ package org.matrix.android.sdk.internal.auth import android.net.Uri import dagger.Lazy import okhttp3.OkHttpClient +import org.matrix.android.sdk.api.MatrixPatterns +import org.matrix.android.sdk.api.MatrixPatterns.getDomain import org.matrix.android.sdk.api.auth.AuthenticationService import org.matrix.android.sdk.api.auth.data.Credentials import org.matrix.android.sdk.api.auth.data.HomeServerConnectionConfig @@ -28,10 +30,11 @@ import org.matrix.android.sdk.api.auth.login.LoginWizard import org.matrix.android.sdk.api.auth.registration.RegistrationWizard import org.matrix.android.sdk.api.auth.wellknown.WellknownResult import org.matrix.android.sdk.api.failure.Failure +import org.matrix.android.sdk.api.failure.MatrixIdFailure import org.matrix.android.sdk.api.session.Session import org.matrix.android.sdk.api.util.appendParamToUrl import org.matrix.android.sdk.internal.SessionManager -import org.matrix.android.sdk.internal.auth.data.RiotConfig +import org.matrix.android.sdk.internal.auth.data.WebClientConfig import org.matrix.android.sdk.internal.auth.db.PendingSessionData import org.matrix.android.sdk.internal.auth.login.DefaultLoginWizard import org.matrix.android.sdk.internal.auth.login.DirectLoginTask @@ -122,7 +125,7 @@ internal class DefaultAuthenticationService @Inject constructor( private fun getHomeServerUrlBase(): String? { return pendingSessionData ?.homeServerConnectionConfig - ?.homeServerUri + ?.homeServerUriBase ?.toString() ?.trim { it == '/' } } @@ -143,9 +146,9 @@ internal class DefaultAuthenticationService @Inject constructor( return result.fold( { // The homeserver exists and up to date, keep the config - // Homeserver url may have been changed, if it was a Riot url + // Homeserver url may have been changed, if it was a Web client url val alteredHomeServerConnectionConfig = homeServerConnectionConfig.copy( - homeServerUri = Uri.parse(it.homeServerUrl) + homeServerUriBase = Uri.parse(it.homeServerUrl) ) pendingSessionData = PendingSessionData(alteredHomeServerConnectionConfig) @@ -154,7 +157,7 @@ internal class DefaultAuthenticationService @Inject constructor( }, { if (it is UnrecognizedCertificateException) { - throw Failure.UnrecognizedCertificateFailure(homeServerConnectionConfig.homeServerUri.toString(), it.fingerprint) + throw Failure.UnrecognizedCertificateFailure(homeServerConnectionConfig.homeServerUriBase.toString(), it.fingerprint) } else { throw it } @@ -165,46 +168,57 @@ internal class DefaultAuthenticationService @Inject constructor( private suspend fun getLoginFlowInternal(homeServerConnectionConfig: HomeServerConnectionConfig): LoginFlowResult { val authAPI = buildAuthAPI(homeServerConnectionConfig) - // First check the homeserver version - return runCatching { - executeRequest(null) { - authAPI.versions() - } - } - .map { versions -> - // Ok, it seems that the homeserver url is valid - getLoginFlowResult(authAPI, versions, homeServerConnectionConfig.homeServerUri.toString()) + // First check if there is a well-known file + return try { + getWellknownLoginFlowInternal(homeServerConnectionConfig) + } catch (failure: Throwable) { + if (failure is Failure.OtherServerError + && failure.httpCode == HttpsURLConnection.HTTP_NOT_FOUND /* 404 */) { + // 404, no well-known data, try direct access to the API + // First check the homeserver version + return runCatching { + executeRequest(null) { + authAPI.versions() + } } - .fold( - { - it - }, - { - if (it is Failure.OtherServerError - && it.httpCode == HttpsURLConnection.HTTP_NOT_FOUND /* 404 */) { - // It's maybe a Riot url? - getRiotDomainLoginFlowInternal(homeServerConnectionConfig) - } else { - throw it - } + .map { versions -> + // Ok, it seems that the homeserver url is valid + getLoginFlowResult(authAPI, versions, homeServerConnectionConfig.homeServerUriBase.toString()) } - ) + .fold( + { + it + }, + { + if (it is Failure.OtherServerError + && it.httpCode == HttpsURLConnection.HTTP_NOT_FOUND /* 404 */) { + // It's maybe a Web client url? + getWebClientDomainLoginFlowInternal(homeServerConnectionConfig) + } else { + throw it + } + } + ) + } else { + throw failure + } + } } - private suspend fun getRiotDomainLoginFlowInternal(homeServerConnectionConfig: HomeServerConnectionConfig): LoginFlowResult { + private suspend fun getWebClientDomainLoginFlowInternal(homeServerConnectionConfig: HomeServerConnectionConfig): LoginFlowResult { val authAPI = buildAuthAPI(homeServerConnectionConfig) val domain = homeServerConnectionConfig.homeServerUri.host - ?: return getRiotLoginFlowInternal(homeServerConnectionConfig) + ?: return getWebClientLoginFlowInternal(homeServerConnectionConfig) - // Ok, try to get the config.domain.json file of a RiotWeb client + // Ok, try to get the config.domain.json file of a Web client return runCatching { executeRequest(null) { - authAPI.getRiotConfigDomain(domain) + authAPI.getWebClientConfigDomain(domain) } } - .map { riotConfig -> - onRiotConfigRetrieved(homeServerConnectionConfig, riotConfig) + .map { webClientConfig -> + onWebClientConfigRetrieved(homeServerConnectionConfig, webClientConfig) } .fold( { @@ -214,7 +228,7 @@ internal class DefaultAuthenticationService @Inject constructor( if (it is Failure.OtherServerError && it.httpCode == HttpsURLConnection.HTTP_NOT_FOUND /* 404 */) { // Try with config.json - getRiotLoginFlowInternal(homeServerConnectionConfig) + getWebClientLoginFlowInternal(homeServerConnectionConfig) } else { throw it } @@ -222,40 +236,24 @@ internal class DefaultAuthenticationService @Inject constructor( ) } - private suspend fun getRiotLoginFlowInternal(homeServerConnectionConfig: HomeServerConnectionConfig): LoginFlowResult { + private suspend fun getWebClientLoginFlowInternal(homeServerConnectionConfig: HomeServerConnectionConfig): LoginFlowResult { val authAPI = buildAuthAPI(homeServerConnectionConfig) - // Ok, try to get the config.json file of a RiotWeb client - return runCatching { - executeRequest(null) { - authAPI.getRiotConfig() - } + // Ok, try to get the config.json file of a Web client + return executeRequest(null) { + authAPI.getWebClientConfig() } - .map { riotConfig -> - onRiotConfigRetrieved(homeServerConnectionConfig, riotConfig) + .let { webClientConfig -> + onWebClientConfigRetrieved(homeServerConnectionConfig, webClientConfig) } - .fold( - { - it - }, - { - if (it is Failure.OtherServerError - && it.httpCode == HttpsURLConnection.HTTP_NOT_FOUND /* 404 */) { - // Try with wellknown - getWellknownLoginFlowInternal(homeServerConnectionConfig) - } else { - throw it - } - } - ) } - private suspend fun onRiotConfigRetrieved(homeServerConnectionConfig: HomeServerConnectionConfig, riotConfig: RiotConfig): LoginFlowResult { - val defaultHomeServerUrl = riotConfig.getPreferredHomeServerUrl() + private suspend fun onWebClientConfigRetrieved(homeServerConnectionConfig: HomeServerConnectionConfig, webClientConfig: WebClientConfig): LoginFlowResult { + val defaultHomeServerUrl = webClientConfig.getPreferredHomeServerUrl() if (defaultHomeServerUrl?.isNotEmpty() == true) { // Ok, good sign, we got a default hs url val newHomeServerConnectionConfig = homeServerConnectionConfig.copy( - homeServerUri = Uri.parse(defaultHomeServerUrl) + homeServerUriBase = Uri.parse(defaultHomeServerUrl) ) val newAuthAPI = buildAuthAPI(newHomeServerConnectionConfig) @@ -275,15 +273,13 @@ internal class DefaultAuthenticationService @Inject constructor( val domain = homeServerConnectionConfig.homeServerUri.host ?: throw Failure.OtherServerError("", HttpsURLConnection.HTTP_NOT_FOUND /* 404 */) - // Create a fake userId, for the getWellknown task - val fakeUserId = "@alice:$domain" - val wellknownResult = getWellknownTask.execute(GetWellknownTask.Params(fakeUserId, homeServerConnectionConfig)) + val wellknownResult = getWellknownTask.execute(GetWellknownTask.Params(domain, homeServerConnectionConfig)) return when (wellknownResult) { is WellknownResult.Prompt -> { val newHomeServerConnectionConfig = homeServerConnectionConfig.copy( - homeServerUri = Uri.parse(wellknownResult.homeServerUrl), - identityServerUri = wellknownResult.identityServerUrl?.let { Uri.parse(it) } + homeServerUriBase = Uri.parse(wellknownResult.homeServerUrl), + identityServerUri = wellknownResult.identityServerUrl?.let { Uri.parse(it) } ?: homeServerConnectionConfig.identityServerUri ) val newAuthAPI = buildAuthAPI(newHomeServerConnectionConfig) @@ -379,7 +375,14 @@ internal class DefaultAuthenticationService @Inject constructor( override suspend fun getWellKnownData(matrixId: String, homeServerConnectionConfig: HomeServerConnectionConfig?): WellknownResult { - return getWellknownTask.execute(GetWellknownTask.Params(matrixId, homeServerConnectionConfig)) + if (!MatrixPatterns.isUserId(matrixId)) { + throw MatrixIdFailure.InvalidMatrixId + } + + return getWellknownTask.execute(GetWellknownTask.Params( + domain = matrixId.getDomain(), + homeServerConnectionConfig = homeServerConnectionConfig) + ) } override suspend fun directAuthentication(homeServerConnectionConfig: HomeServerConnectionConfig, @@ -390,7 +393,7 @@ internal class DefaultAuthenticationService @Inject constructor( } private fun buildAuthAPI(homeServerConnectionConfig: HomeServerConnectionConfig): AuthAPI { - val retrofit = retrofitFactory.create(buildClient(homeServerConnectionConfig), homeServerConnectionConfig.homeServerUri.toString()) + val retrofit = retrofitFactory.create(buildClient(homeServerConnectionConfig), homeServerConnectionConfig.homeServerUriBase.toString()) return retrofit.create(AuthAPI::class.java) } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/IsValidClientServerApiTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/IsValidClientServerApiTask.kt index 867cf46b8dbef8fd47e4601da74345c3bda21ad8..bc3d8870007f32b6bf98761d28baaedb0e47052c 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/IsValidClientServerApiTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/IsValidClientServerApiTask.kt @@ -42,7 +42,7 @@ internal class DefaultIsValidClientServerApiTask @Inject constructor( override suspend fun execute(params: IsValidClientServerApiTask.Params): Boolean { val client = buildClient(params.homeServerConnectionConfig) - val homeServerUrl = params.homeServerConnectionConfig.homeServerUri.toString() + val homeServerUrl = params.homeServerConnectionConfig.homeServerUriBase.toString() val authAPI = retrofitFactory.create(client, homeServerUrl) .create(AuthAPI::class.java) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/SessionCreator.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/SessionCreator.kt index 7c4a0c38ec5d3aed2cbdbe36ca2a6ccd899c8049..cc00c963ea1b2550254ccf5346a36f7f1cdbab4f 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/SessionCreator.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/SessionCreator.kt @@ -38,7 +38,7 @@ internal class DefaultSessionCreator @Inject constructor( ) : SessionCreator { /** - * Credentials can affect the homeServerConnectionConfig, override home server url and/or + * Credentials can affect the homeServerConnectionConfig, override homeserver url and/or * identity server url if provided in the credentials */ override suspend fun createSession(credentials: Credentials, homeServerConnectionConfig: HomeServerConnectionConfig): Session { @@ -49,6 +49,8 @@ internal class DefaultSessionCreator @Inject constructor( // remove trailing "/" ?.trim { it == '/' } ?.takeIf { it.isNotBlank() } + // It can be the same value, so in this case, do not check again the validity + ?.takeIf { it != homeServerConnectionConfig.homeServerUriBase.toString() } ?.also { Timber.d("Overriding homeserver url to $it (will check if valid)") } ?.let { Uri.parse(it) } ?.takeIf { @@ -56,7 +58,7 @@ internal class DefaultSessionCreator @Inject constructor( tryOrNull { isValidClientServerApiTask.execute( IsValidClientServerApiTask.Params( - homeServerConnectionConfig.copy(homeServerUri = it) + homeServerConnectionConfig.copy(homeServerUriBase = it) ) ) .also { Timber.d("Overriding homeserver url: $it") } @@ -66,7 +68,7 @@ internal class DefaultSessionCreator @Inject constructor( val sessionParams = SessionParams( credentials = credentials, homeServerConnectionConfig = homeServerConnectionConfig.copy( - homeServerUri = overriddenUrl ?: homeServerConnectionConfig.homeServerUri, + homeServerUriBase = overriddenUrl ?: homeServerConnectionConfig.homeServerUriBase, identityServerUri = credentials.discoveryInformation?.identityServer?.baseURL // remove trailing "/" ?.trim { it == '/' } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/data/RiotConfig.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/data/WebClientConfig.kt similarity index 84% rename from matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/data/RiotConfig.kt rename to matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/data/WebClientConfig.kt index e61358a67b2ec2ae2f3352be8fc9a8aa21e20732..65c3dc64a69cbaeea8cfe2005bdac9fc38ac4beb 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/data/RiotConfig.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/data/WebClientConfig.kt @@ -20,7 +20,7 @@ import com.squareup.moshi.Json import com.squareup.moshi.JsonClass @JsonClass(generateAdapter = true) -internal data class RiotConfig( +internal data class WebClientConfig( /** * This is now deprecated, but still used first, rather than value from "default_server_config" */ @@ -28,7 +28,7 @@ internal data class RiotConfig( val defaultHomeServerUrl: String?, @Json(name = "default_server_config") - val defaultServerConfig: RiotConfigDefaultServerConfig? + val defaultServerConfig: WebClientConfigDefaultServerConfig? ) { fun getPreferredHomeServerUrl(): String? { return defaultHomeServerUrl @@ -38,13 +38,13 @@ internal data class RiotConfig( } @JsonClass(generateAdapter = true) -internal data class RiotConfigDefaultServerConfig( +internal data class WebClientConfigDefaultServerConfig( @Json(name = "m.homeserver") - val homeServer: RiotConfigBaseConfig? = null + val homeServer: WebClientConfigBaseConfig? = null ) @JsonClass(generateAdapter = true) -internal data class RiotConfigBaseConfig( +internal data class WebClientConfigBaseConfig( @Json(name = "base_url") val baseURL: String? = null ) 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 bb2667228bb10a101b22bce1d66c7f30ce1e2660..c2104690b32dfc187ff7d16c71cf44f79f9f9438 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,17 +16,19 @@ 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 io.realm.DynamicRealm -import io.realm.RealmMigration import timber.log.Timber internal object AuthRealmMigration : RealmMigration { // Current schema version - const val SCHEMA_VERSION = 3L + const val SCHEMA_VERSION = 4L override fun migrate(realm: DynamicRealm, oldVersion: Long, newVersion: Long) { Timber.d("Migrating Auth Realm from $oldVersion to $newVersion") @@ -34,6 +36,7 @@ internal object AuthRealmMigration : RealmMigration { 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) { @@ -81,4 +84,34 @@ internal object AuthRealmMigration : RealmMigration { } ?.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)) + } + } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/login/DirectLoginTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/login/DirectLoginTask.kt index 77bbb8096fe943c1a5e7b4cbb8f5bf5f1beb638c..3888633723bd2fbe8eabb122c633ef62a0901ddf 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/login/DirectLoginTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/login/DirectLoginTask.kt @@ -50,7 +50,7 @@ internal class DefaultDirectLoginTask @Inject constructor( override suspend fun execute(params: DirectLoginTask.Params): Session { val client = buildClient(params.homeServerConnectionConfig) - val homeServerUrl = params.homeServerConnectionConfig.homeServerUri.toString() + val homeServerUrl = params.homeServerConnectionConfig.homeServerUriBase.toString() val authAPI = retrofitFactory.create(client, homeServerUrl) .create(AuthAPI::class.java) 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 e114f86a9956c614497337875f91df73eb6cf452..84d4fef5afdf0609b0c28d5880b5fdb26bd2666f 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,6 @@ internal abstract class CryptoModule { @SessionScope fun providesRealmConfiguration(@SessionFilesDirectory directory: File, @UserMd5 userMd5: String, - realmCryptoStoreMigration: RealmCryptoStoreMigration, realmKeysUtils: RealmKeysUtils): RealmConfiguration { return RealmConfiguration.Builder() .directory(directory) @@ -123,7 +122,7 @@ internal abstract class CryptoModule { .modules(RealmCryptoStoreModule()) .allowWritesOnUiThread(true) .schemaVersion(RealmCryptoStoreMigration.CRYPTO_STORE_SCHEMA_VERSION) - .migration(realmCryptoStoreMigration) + .migration(RealmCryptoStoreMigration) .build() } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/DefaultCryptoService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/DefaultCryptoService.kt index 7f5cfe8df103cfa036eacae1bd760855dd2006eb..563c89095040dc6c3a6d2b9bdae6140e547ace80 100755 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/DefaultCryptoService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/DefaultCryptoService.kt @@ -314,6 +314,12 @@ internal class DefaultCryptoService @Inject constructor( cryptoCoroutineScope.launchToCallback(coroutineDispatchers.crypto, NoOpMatrixCallback()) { // Open the store cryptoStore.open() + + if (!cryptoStore.areDeviceKeysUploaded()) { + // Schedule upload of OTK + oneTimeKeysUploader.updateOneTimeKeyCount(0) + } + // this can throw if no network tryOrNull { uploadDeviceKeys() @@ -388,7 +394,7 @@ internal class DefaultCryptoService @Inject constructor( cryptoStore.close() } - // Aways enabled on RiotX + // Always enabled on Matrix Android SDK2 override fun isCryptoEnabled() = true /** @@ -905,7 +911,7 @@ internal class DefaultCryptoService @Inject constructor( * Upload my user's device keys. */ private suspend fun uploadDeviceKeys() { - if (cryptoStore.getDeviceKeysUploaded()) { + if (cryptoStore.areDeviceKeysUploaded()) { Timber.d("Keys already uploaded, nothing to do") return } @@ -928,14 +934,10 @@ internal class DefaultCryptoService @Inject constructor( * Export the crypto keys * * @param password the password - * @param callback the exported keys + * @return the exported keys */ - override fun exportRoomKeys(password: String, callback: MatrixCallback<ByteArray>) { - cryptoCoroutineScope.launch(coroutineDispatchers.main) { - runCatching { - exportRoomKeys(password, MXMegolmExportEncryption.DEFAULT_ITERATION_COUNT) - }.foldToCallback(callback) - } + override suspend fun exportRoomKeys(password: String): ByteArray { + return exportRoomKeys(password, MXMegolmExportEncryption.DEFAULT_ITERATION_COUNT) } /** @@ -963,42 +965,37 @@ internal class DefaultCryptoService @Inject constructor( * @param roomKeysAsArray the room keys as array. * @param password the password * @param progressListener the progress listener - * @param callback the asynchronous callback. + * @return the result ImportRoomKeysResult */ - override fun importRoomKeys(roomKeysAsArray: ByteArray, - password: String, - progressListener: ProgressListener?, - callback: MatrixCallback<ImportRoomKeysResult>) { - cryptoCoroutineScope.launch(coroutineDispatchers.main) { - runCatching { - withContext(coroutineDispatchers.crypto) { - Timber.v("## CRYPTO | importRoomKeys starts") + override suspend fun importRoomKeys(roomKeysAsArray: ByteArray, + password: String, + progressListener: ProgressListener?): ImportRoomKeysResult { + return withContext(coroutineDispatchers.crypto) { + Timber.v("## CRYPTO | importRoomKeys starts") - val t0 = System.currentTimeMillis() - val roomKeys = MXMegolmExportEncryption.decryptMegolmKeyFile(roomKeysAsArray, password) - val t1 = System.currentTimeMillis() + val t0 = System.currentTimeMillis() + val roomKeys = MXMegolmExportEncryption.decryptMegolmKeyFile(roomKeysAsArray, password) + val t1 = System.currentTimeMillis() - Timber.v("## CRYPTO | importRoomKeys : decryptMegolmKeyFile done in ${t1 - t0} ms") + Timber.v("## CRYPTO | importRoomKeys : decryptMegolmKeyFile done in ${t1 - t0} ms") - val importedSessions = MoshiProvider.providesMoshi() - .adapter<List<MegolmSessionData>>(Types.newParameterizedType(List::class.java, MegolmSessionData::class.java)) - .fromJson(roomKeys) + val importedSessions = MoshiProvider.providesMoshi() + .adapter<List<MegolmSessionData>>(Types.newParameterizedType(List::class.java, MegolmSessionData::class.java)) + .fromJson(roomKeys) - val t2 = System.currentTimeMillis() + val t2 = System.currentTimeMillis() - Timber.v("## CRYPTO | importRoomKeys : JSON parsing ${t2 - t1} ms") + Timber.v("## CRYPTO | importRoomKeys : JSON parsing ${t2 - t1} ms") - if (importedSessions == null) { - throw Exception("Error") - } + if (importedSessions == null) { + throw Exception("Error") + } - megolmSessionDataImporter.handle( - megolmSessionsData = importedSessions, - fromBackup = false, - progressListener = progressListener - ) - } - }.foldToCallback(callback) + megolmSessionDataImporter.handle( + megolmSessionsData = importedSessions, + fromBackup = false, + progressListener = progressListener + ) } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/DeviceListManager.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/DeviceListManager.kt index 63f15aaf6e6ca0ce3555b566746f282191194696..79910c6de29b9c40b6af67ea47be2a6a9c65422a 100755 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/DeviceListManager.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/DeviceListManager.kt @@ -16,6 +16,7 @@ package org.matrix.android.sdk.internal.crypto +import kotlinx.coroutines.CancellationException import kotlinx.coroutines.launch import org.matrix.android.sdk.api.MatrixPatterns import org.matrix.android.sdk.api.auth.data.Credentials @@ -336,7 +337,12 @@ internal class DeviceListManager @Inject constructor(private val cryptoStore: IM downloadKeysForUsersTask.execute(params) } catch (throwable: Throwable) { Timber.e(throwable, "## CRYPTO | doKeyDownloadForUsers(): error") - onKeysDownloadFailed(filteredUsers) + if (throwable is CancellationException) { + // the crypto module is getting closed, so we cannot access the DB anymore + Timber.w("The crypto module is closed, ignoring this error") + } else { + onKeysDownloadFailed(filteredUsers) + } throw throwable } Timber.v("## CRYPTO | doKeyDownloadForUsers() : Got keys for " + filteredUsers.size + " users") diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/OneTimeKeysUploader.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/OneTimeKeysUploader.kt index 6695234d621940d0dd24a7299b2077f07bf65837..c4b62fe9fee560c047786e10499b13eb817156ed 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/OneTimeKeysUploader.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/OneTimeKeysUploader.kt @@ -16,6 +16,7 @@ package org.matrix.android.sdk.internal.crypto +import org.matrix.android.sdk.api.extensions.tryOrNull import org.matrix.android.sdk.internal.crypto.model.MXKey import org.matrix.android.sdk.internal.crypto.model.rest.KeysUploadResponse import org.matrix.android.sdk.internal.crypto.tasks.UploadKeysTask @@ -77,6 +78,10 @@ internal class OneTimeKeysUploader @Inject constructor( // discard the oldest private keys first. This will eventually clean // out stale private keys that won't receive a message. val keyLimit = floor(maxOneTimeKeys / 2.0).toInt() + if (oneTimeKeyCount == null) { + // Ask the server how many otk he has + oneTimeKeyCount = fetchOtkCount() + } val oneTimeKeyCountFromSync = oneTimeKeyCount if (oneTimeKeyCountFromSync != null) { // We need to keep a pool of one time public keys on the server so that @@ -90,17 +95,22 @@ internal class OneTimeKeysUploader @Inject constructor( // private keys clogging up our local storage. // So we need some kind of engineering compromise to balance all of // these factors. - try { + tryOrNull("Unable to upload OTK") { val uploadedKeys = uploadOTK(oneTimeKeyCountFromSync, keyLimit) Timber.v("## uploadKeys() : success, $uploadedKeys key(s) sent") - } finally { - oneTimeKeyCheckInProgress = false } } else { Timber.w("maybeUploadOneTimeKeys: waiting to know the number of OTK from the sync") - oneTimeKeyCheckInProgress = false lastOneTimeKeyCheck = 0 } + oneTimeKeyCheckInProgress = false + } + + private suspend fun fetchOtkCount(): Int? { + return tryOrNull("Unable to get OTK count") { + val result = uploadKeysTask.execute(UploadKeysTask.Params(null, null)) + result.oneTimeKeyCountsForAlgorithm(MXKey.KEY_SIGNED_CURVE_25519_TYPE) + } } /** diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/megolm/MXMegolmDecryption.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/megolm/MXMegolmDecryption.kt index a29ac457fb956178d2be73f9b13c53b112f0fc5e..70d202229932fbae5fd89994abb22ee8805ed993 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/megolm/MXMegolmDecryption.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/megolm/MXMegolmDecryption.kt @@ -155,7 +155,7 @@ internal class MXMegolmDecryption(private val userId: String, withHeldInfo.code?.value ?: "", withHeldInfo.reason) } else { - // This is un-used in riotX SDK, not sure if needed + // This is un-used in Matrix Android SDK2, not sure if needed // addEventToPendingList(event, timeline) if (requestKeysOnFail) { requestKeysForEvent(event, false) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/attachments/MXEncryptedAttachments.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/attachments/MXEncryptedAttachments.kt index 5a9852b6db71742355f051008ffe82415059cbd8..70730326da12d90fa0397e12a9adf0777b571a3e 100755 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/attachments/MXEncryptedAttachments.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/attachments/MXEncryptedAttachments.kt @@ -39,7 +39,9 @@ internal object MXEncryptedAttachments { private const val SECRET_KEY_SPEC_ALGORITHM = "AES" private const val MESSAGE_DIGEST_ALGORITHM = "SHA-256" - fun encrypt(clearStream: InputStream, mimetype: String?, outputFile: File, progress: ((current: Int, total: Int) -> Unit)): EncryptedFileInfo { + fun encrypt(clearStream: InputStream, + outputFile: File, + progress: ((current: Int, total: Int) -> Unit)): EncryptedFileInfo { val t0 = System.currentTimeMillis() val secureRandom = SecureRandom() val initVectorBytes = ByteArray(16) { 0.toByte() } @@ -86,7 +88,6 @@ internal object MXEncryptedAttachments { return EncryptedFileInfo( url = null, - mimetype = mimetype, key = EncryptedFileKey( alg = "A256CTR", ext = true, @@ -155,10 +156,9 @@ internal object MXEncryptedAttachments { * Encrypt an attachment stream. * DO NOT USE for big files, it will load all in memory * @param attachmentStream the attachment stream. Will be closed after this method call. - * @param mimetype the mime type * @return the encryption file info */ - fun encryptAttachment(attachmentStream: InputStream, mimetype: String?): EncryptionResult { + fun encryptAttachment(attachmentStream: InputStream): EncryptionResult { val t0 = System.currentTimeMillis() val secureRandom = SecureRandom() @@ -207,7 +207,6 @@ internal object MXEncryptedAttachments { return EncryptionResult( encryptedFileInfo = EncryptedFileInfo( url = null, - mimetype = mimetype, key = EncryptedFileKey( alg = "A256CTR", ext = true, @@ -232,7 +231,9 @@ internal object MXEncryptedAttachments { * @param outputStream the outputStream where the decrypted attachment will be write. * @return true in case of success, false in case of error */ - fun decryptAttachment(attachmentStream: InputStream?, elementToDecrypt: ElementToDecrypt?, outputStream: OutputStream): Boolean { + fun decryptAttachment(attachmentStream: InputStream?, + elementToDecrypt: ElementToDecrypt?, + outputStream: OutputStream): Boolean { // sanity checks if (null == attachmentStream || elementToDecrypt == null) { Timber.e("## decryptAttachment() : null stream") diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/CryptoDeviceInfo.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/CryptoDeviceInfo.kt index 4004294d973da3fdfa47dda69d1e9055e29c7522..5e7744853aa464a9515fb1fb9c807e1c5321617d 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/CryptoDeviceInfo.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/CryptoDeviceInfo.kt @@ -18,8 +18,6 @@ package org.matrix.android.sdk.internal.crypto.model import org.matrix.android.sdk.internal.crypto.crosssigning.DeviceTrustLevel import org.matrix.android.sdk.internal.crypto.model.rest.DeviceKeys import org.matrix.android.sdk.internal.crypto.model.rest.UnsignedDeviceInfo -import org.matrix.android.sdk.internal.crypto.store.db.model.CryptoMapper -import org.matrix.android.sdk.internal.crypto.store.db.model.DeviceInfoEntity data class CryptoDeviceInfo( val deviceId: String, @@ -77,7 +75,3 @@ data class CryptoDeviceInfo( internal fun CryptoDeviceInfo.toRest(): DeviceKeys { return CryptoInfoMapper.map(this) } - -internal fun CryptoDeviceInfo.toEntity(): DeviceInfoEntity { - return CryptoMapper.mapToEntity(this) -} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/MXDeviceInfo.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/MXDeviceInfo.kt index 00b8bde5d999aea1a843e537bfedab6ca7ce048f..68cc41005eff82afab592e3a1695a4d4db9e11cc 100755 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/MXDeviceInfo.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/MXDeviceInfo.kt @@ -56,7 +56,7 @@ data class MXDeviceInfo( val signatures: Map<String, Map<String, String>>? = null, /* - * Additional data from the home server. + * Additional data from the homeserver. */ @Json(name = "unsigned") val unsigned: JsonDict? = null, diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/rest/EncryptedFileInfo.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/rest/EncryptedFileInfo.kt index 0ed6c0dac938882028a90fc1d53849ed0d8ab613..4fc3adb42cc43de630d10c250a3063d4261b3e3e 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/rest/EncryptedFileInfo.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/rest/EncryptedFileInfo.kt @@ -29,12 +29,6 @@ data class EncryptedFileInfo( @Json(name = "url") val url: String? = null, - /** - * Not documented - */ - @Json(name = "mimetype") - val mimetype: String? = null, - /** * Required. A JSON Web Key object. */ diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/secrets/DefaultSharedSecretStorageService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/secrets/DefaultSharedSecretStorageService.kt index 1f80ce2c812ab1b359fad9dd355c7a252060534e..fb10cf4482b90bc64724b037db262dac9e41b0de 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/secrets/DefaultSharedSecretStorageService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/secrets/DefaultSharedSecretStorageService.kt @@ -18,7 +18,7 @@ package org.matrix.android.sdk.internal.crypto.secrets import org.matrix.android.sdk.api.extensions.orFalse import org.matrix.android.sdk.api.listeners.ProgressListener -import org.matrix.android.sdk.api.session.accountdata.AccountDataService +import org.matrix.android.sdk.api.session.accountdata.SessionAccountDataService import org.matrix.android.sdk.api.session.events.model.toContent import org.matrix.android.sdk.api.session.securestorage.EncryptedSecretContent import org.matrix.android.sdk.api.session.securestorage.IntegrityResult @@ -56,7 +56,7 @@ import kotlin.experimental.and internal class DefaultSharedSecretStorageService @Inject constructor( @UserId private val userId: String, - private val accountDataService: AccountDataService, + private val accountDataService: SessionAccountDataService, private val outgoingGossipingRequestManager: OutgoingGossipingRequestManager, private val coroutineDispatchers: MatrixCoroutineDispatchers, private val cryptoCoroutineScope: CoroutineScope @@ -84,7 +84,7 @@ internal class DefaultSharedSecretStorageService @Inject constructor( ) } ?: storageKeyContent - accountDataService.updateAccountData("$KEY_ID_BASE.$keyId", signedContent.toContent()) + accountDataService.updateUserAccountData("$KEY_ID_BASE.$keyId", signedContent.toContent()) SsssKeyCreationInfo( keyId = keyId, content = storageKeyContent, @@ -113,7 +113,7 @@ internal class DefaultSharedSecretStorageService @Inject constructor( ) } ?: storageKeyContent - accountDataService.updateAccountData( + accountDataService.updateUserAccountData( "$KEY_ID_BASE.$keyId", signedContent.toContent() ) @@ -127,11 +127,11 @@ internal class DefaultSharedSecretStorageService @Inject constructor( } override fun hasKey(keyId: String): Boolean { - return accountDataService.getAccountDataEvent("$KEY_ID_BASE.$keyId") != null + return accountDataService.getUserAccountDataEvent("$KEY_ID_BASE.$keyId") != null } override fun getKey(keyId: String): KeyInfoResult { - val accountData = accountDataService.getAccountDataEvent("$KEY_ID_BASE.$keyId") + val accountData = accountDataService.getUserAccountDataEvent("$KEY_ID_BASE.$keyId") ?: return KeyInfoResult.Error(SharedSecretStorageError.UnknownKey(keyId)) return SecretStorageKeyContent.fromJson(accountData.content)?.let { KeyInfoResult.Success( @@ -143,14 +143,14 @@ internal class DefaultSharedSecretStorageService @Inject constructor( override suspend fun setDefaultKey(keyId: String) { val existingKey = getKey(keyId) if (existingKey is KeyInfoResult.Success) { - accountDataService.updateAccountData(DEFAULT_KEY_ID, mapOf("key" to keyId)) + accountDataService.updateUserAccountData(DEFAULT_KEY_ID, mapOf("key" to keyId)) } else { throw SharedSecretStorageError.UnknownKey(keyId) } } override fun getDefaultKey(): KeyInfoResult { - val accountData = accountDataService.getAccountDataEvent(DEFAULT_KEY_ID) + val accountData = accountDataService.getUserAccountDataEvent(DEFAULT_KEY_ID) ?: return KeyInfoResult.Error(SharedSecretStorageError.UnknownKey(DEFAULT_KEY_ID)) val keyId = accountData.content["key"] as? String ?: return KeyInfoResult.Error(SharedSecretStorageError.UnknownKey(DEFAULT_KEY_ID)) @@ -178,7 +178,7 @@ internal class DefaultSharedSecretStorageService @Inject constructor( } } - accountDataService.updateAccountData( + accountDataService.updateUserAccountData( type = name, content = mapOf("encrypted" to encryptedContents) ) @@ -288,7 +288,7 @@ internal class DefaultSharedSecretStorageService @Inject constructor( } override fun getAlgorithmsForSecret(name: String): List<KeyInfoResult> { - val accountData = accountDataService.getAccountDataEvent(name) + val accountData = accountDataService.getUserAccountDataEvent(name) ?: return listOf(KeyInfoResult.Error(SharedSecretStorageError.UnknownSecret(name))) val encryptedContent = accountData.content[ENCRYPTED] as? Map<*, *> ?: return listOf(KeyInfoResult.Error(SharedSecretStorageError.SecretNotEncrypted(name))) @@ -303,7 +303,7 @@ internal class DefaultSharedSecretStorageService @Inject constructor( } override suspend fun getSecret(name: String, keyId: String?, secretKey: SsssKeySpec): String { - val accountData = accountDataService.getAccountDataEvent(name) ?: throw SharedSecretStorageError.UnknownSecret(name) + val accountData = accountDataService.getUserAccountDataEvent(name) ?: throw SharedSecretStorageError.UnknownSecret(name) val encryptedContent = accountData.content[ENCRYPTED] as? Map<*, *> ?: throw SharedSecretStorageError.SecretNotEncrypted(name) val key = keyId?.let { getKey(it) } as? KeyInfoResult.Success ?: getDefaultKey() as? KeyInfoResult.Success ?: throw SharedSecretStorageError.UnknownKey(name) @@ -368,7 +368,7 @@ internal class DefaultSharedSecretStorageService @Inject constructor( } secretNames.forEach { secretName -> - val secretEvent = accountDataService.getAccountDataEvent(secretName) + val secretEvent = accountDataService.getUserAccountDataEvent(secretName) ?: return IntegrityResult.Error(SharedSecretStorageError.UnknownSecret(secretName)) if ((secretEvent.content["encrypted"] as? Map<*, *>)?.get(keyInfo.id) == null) { return IntegrityResult.Error(SharedSecretStorageError.SecretNotEncryptedWithKey(secretName, keyInfo.id)) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/IMXCryptoStore.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/IMXCryptoStore.kt index 181bd94cc7e832718a8aa3595108c03e092040cb..3d12e74fcdd061833de6a12d988ee5abab3e3432 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/IMXCryptoStore.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/IMXCryptoStore.kt @@ -475,7 +475,7 @@ internal interface IMXCryptoStore { fun getGossipingEvents(): List<Event> fun setDeviceKeysUploaded(uploaded: Boolean) - fun getDeviceKeysUploaded(): Boolean + fun areDeviceKeysUploaded(): Boolean fun tidyUpDataBase() fun logDbUsageInfo() } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/RealmCryptoStore.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/RealmCryptoStore.kt index 9ae93d61eb88be0b71cd6aeedf42a058ff123ff3..d99799883658e236bba62e3509a75d927f169a4f 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/RealmCryptoStore.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/RealmCryptoStore.kt @@ -51,7 +51,6 @@ import org.matrix.android.sdk.internal.crypto.model.OutboundGroupSessionWrapper import org.matrix.android.sdk.internal.crypto.model.event.RoomKeyWithHeldContent import org.matrix.android.sdk.internal.crypto.model.rest.DeviceInfo import org.matrix.android.sdk.internal.crypto.model.rest.RoomKeyRequestBody -import org.matrix.android.sdk.internal.crypto.model.toEntity import org.matrix.android.sdk.internal.crypto.store.IMXCryptoStore import org.matrix.android.sdk.internal.crypto.store.PrivateKeysInfo import org.matrix.android.sdk.internal.crypto.store.SavedKeyBackupKeyInfo @@ -280,24 +279,37 @@ internal class RealmCryptoStore @Inject constructor( override fun storeUserDevices(userId: String, devices: Map<String, CryptoDeviceInfo>?) { doRealmTransaction(realmConfiguration) { realm -> if (devices == null) { + Timber.d("Remove user $userId") // Remove the user UserEntity.delete(realm, userId) } else { - UserEntity.getOrCreate(realm, userId) - .let { u -> - // Add the devices - val currentKnownDevices = u.devices.toList() - val new = devices.map { entry -> entry.value.toEntity() } - new.forEach { entity -> - // Maintain first time seen - val existing = currentKnownDevices.firstOrNull { it.deviceId == entity.deviceId && it.identityKey == entity.identityKey } - entity.firstTimeSeenLocalTs = existing?.firstTimeSeenLocalTs ?: System.currentTimeMillis() - realm.insertOrUpdate(entity) - } - // Ensure all other devices are deleted - u.devices.clearWith { it.deleteOnCascade() } - u.devices.addAll(new) + val userEntity = UserEntity.getOrCreate(realm, userId) + // First delete the removed devices + val deviceIds = devices.keys + userEntity.devices.toTypedArray().iterator().let { + while (it.hasNext()) { + val deviceInfoEntity = it.next() + if (deviceInfoEntity.deviceId !in deviceIds) { + Timber.d("Remove device ${deviceInfoEntity.deviceId} of user $userId") + deviceInfoEntity.deleteOnCascade() } + } + } + // Then update existing devices or add new one + devices.values.forEach { cryptoDeviceInfo -> + val existingDeviceInfoEntity = userEntity.devices.firstOrNull { it.deviceId == cryptoDeviceInfo.deviceId } + if (existingDeviceInfoEntity == null) { + // Add the device + Timber.d("Add device ${cryptoDeviceInfo.deviceId} of user $userId") + val newEntity = CryptoMapper.mapToEntity(cryptoDeviceInfo) + newEntity.firstTimeSeenLocalTs = System.currentTimeMillis() + userEntity.devices.add(newEntity) + } else { + // Update the device + Timber.d("Update device ${cryptoDeviceInfo.deviceId} of user $userId") + CryptoMapper.updateDeviceInfoEntity(existingDeviceInfoEntity, cryptoDeviceInfo) + } + } } } } @@ -937,7 +949,7 @@ internal class RealmCryptoStore @Inject constructor( } } - override fun getDeviceKeysUploaded(): Boolean { + override fun areDeviceKeysUploaded(): Boolean { return doWithRealm(realmConfiguration) { it.where<CryptoMetadataEntity>().findFirst()?.deviceKeysSentToServer } ?: false 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 bca79143885373c71a286b2b5cd5c276551a1b39..2846be993255241c15d9f5279373581645acecad 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 @@ -18,6 +18,9 @@ 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 @@ -35,29 +38,24 @@ import org.matrix.android.sdk.internal.crypto.store.db.model.KeysBackupDataEntit 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 io.realm.DynamicRealm -import io.realm.RealmMigration -import io.realm.RealmObjectSchema -import org.matrix.android.sdk.internal.crypto.store.db.model.OutboundGroupSessionInfoEntityFields import org.matrix.androidsdk.crypto.data.MXOlmInboundGroupSession2 import timber.log.Timber -import javax.inject.Inject import org.matrix.androidsdk.crypto.data.MXDeviceInfo as LegacyMXDeviceInfo -internal class RealmCryptoStoreMigration @Inject constructor(private val crossSigningKeysMapper: CrossSigningKeysMapper) : RealmMigration { +internal object RealmCryptoStoreMigration : RealmMigration { - companion object { - // 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 = 12L - } + // 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 = 13L private fun RealmObjectSchema.addFieldIfNotExists(fieldName: String, fieldType: Class<*>): RealmObjectSchema { if (!hasField(fieldName)) { @@ -95,6 +93,7 @@ internal class RealmCryptoStoreMigration @Inject constructor(private val crossSi if (oldVersion <= 9) migrateTo10(realm) if (oldVersion <= 10) migrateTo11(realm) if (oldVersion <= 11) migrateTo12(realm) + if (oldVersion <= 12) migrateTo13(realm) } private fun migrateTo1Legacy(realm: DynamicRealm) { @@ -384,6 +383,8 @@ internal class RealmCryptoStoreMigration @Inject constructor(private val crossSi 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 { @@ -497,4 +498,60 @@ internal class RealmCryptoStoreMigration @Inject constructor(private val crossSi 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...") + } + } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/model/CryptoMapper.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/model/CryptoMapper.kt index 37d144169055cf3a30d9c195a882211c3f44212e..7ba986699ab60bf26acf5e2891225096a0fb6897 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/model/CryptoMapper.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/model/CryptoMapper.kt @@ -44,23 +44,32 @@ object CryptoMapper { )) internal fun mapToEntity(deviceInfo: CryptoDeviceInfo): DeviceInfoEntity { - return DeviceInfoEntity( - primaryKey = DeviceInfoEntity.createPrimaryKey(deviceInfo.userId, deviceInfo.deviceId), - userId = deviceInfo.userId, - deviceId = deviceInfo.deviceId, - algorithmListJson = listMigrationAdapter.toJson(deviceInfo.algorithms), - keysMapJson = mapMigrationAdapter.toJson(deviceInfo.keys), - signatureMapJson = mapMigrationAdapter.toJson(deviceInfo.signatures), - isBlocked = deviceInfo.isBlocked, - trustLevelEntity = deviceInfo.trustLevel?.let { - TrustLevelEntity( - crossSignedVerified = it.crossSigningVerified, - locallyVerified = it.locallyVerified - ) - }, - // We store the device name if present now - unsignedMapJson = deviceInfo.unsigned?.deviceDisplayName - ) + return DeviceInfoEntity(primaryKey = DeviceInfoEntity.createPrimaryKey(deviceInfo.userId, deviceInfo.deviceId)) + .also { updateDeviceInfoEntity(it, deviceInfo) } + } + + internal fun updateDeviceInfoEntity(entity: DeviceInfoEntity, deviceInfo: CryptoDeviceInfo) { + entity.userId = deviceInfo.userId + entity.deviceId = deviceInfo.deviceId + entity.algorithmListJson = listMigrationAdapter.toJson(deviceInfo.algorithms) + entity.keysMapJson = mapMigrationAdapter.toJson(deviceInfo.keys) + entity.signatureMapJson = mapMigrationAdapter.toJson(deviceInfo.signatures) + entity.isBlocked = deviceInfo.isBlocked + val deviceInfoTrustLevel = deviceInfo.trustLevel + if (deviceInfoTrustLevel == null) { + entity.trustLevelEntity?.deleteFromRealm() + entity.trustLevelEntity = null + } else { + if (entity.trustLevelEntity == null) { + // Create a new TrustLevelEntity object + entity.trustLevelEntity = TrustLevelEntity() + } + // Update the existing TrustLevelEntity object + entity.trustLevelEntity?.crossSignedVerified = deviceInfoTrustLevel.crossSigningVerified + entity.trustLevelEntity?.locallyVerified = deviceInfoTrustLevel.locallyVerified + } + // We store the device name if present now + entity.unsignedMapJson = deviceInfo.unsigned?.deviceDisplayName } internal fun mapToModel(deviceInfoEntity: DeviceInfoEntity): CryptoDeviceInfo { diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/tasks/InitializeCrossSigningTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/tasks/InitializeCrossSigningTask.kt index f8a8354e482e0c4ad2aa3b5c144c8e767545d541..1d40e5defd3b02f70fc31479b776b85558b3bacd 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/tasks/InitializeCrossSigningTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/tasks/InitializeCrossSigningTask.kt @@ -97,7 +97,7 @@ internal class DefaultInitializeCrossSigningTask @Inject constructor( Timber.v("## CrossSigning - sskPublicKey:$sskPublicKey") - // Sign userSigningKey with master + // Sign selfSigningKey with master val signedSSK = CryptoCrossSigningKey.Builder(userId, KeyUsage.SELF_SIGNING) .key(sskPublicKey) .build() diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/verification/VerificationTransportToDevice.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/verification/VerificationTransportToDevice.kt index 0dbbe656c71bb7fac8e473c4e8ee8ab04bba1d7a..45f81439377b97a00ae26d332b86ecddbea8144c 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/verification/VerificationTransportToDevice.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/verification/VerificationTransportToDevice.kt @@ -68,7 +68,7 @@ internal class VerificationTransportToDevice( contentMap.setObject(otherUserId, it, keyReq) } sendToDeviceTask - .configureWith(SendToDeviceTask.Params(MessageType.MSGTYPE_VERIFICATION_REQUEST, contentMap, localId)) { + .configureWith(SendToDeviceTask.Params(MessageType.MSGTYPE_VERIFICATION_REQUEST, contentMap)) { this.callback = object : MatrixCallback<Unit> { override fun onSuccess(data: Unit) { Timber.v("## verification [$tx.transactionId] send toDevice request success") @@ -124,7 +124,7 @@ internal class VerificationTransportToDevice( contentMap.setObject(tx.otherUserId, tx.otherDeviceId, toSendToDeviceObject) sendToDeviceTask - .configureWith(SendToDeviceTask.Params(type, contentMap, tx.transactionId)) { + .configureWith(SendToDeviceTask.Params(type, contentMap)) { this.callback = object : MatrixCallback<Unit> { override fun onSuccess(data: Unit) { Timber.v("## SAS verification [$tx.transactionId] toDevice type '$type' success.") @@ -155,7 +155,7 @@ internal class VerificationTransportToDevice( val contentMap = MXUsersDevicesMap<Any>() contentMap.setObject(otherUserId, otherUserDeviceId, cancelMessage) sendToDeviceTask - .configureWith(SendToDeviceTask.Params(EventType.KEY_VERIFICATION_DONE, contentMap, transactionId)) { + .configureWith(SendToDeviceTask.Params(EventType.KEY_VERIFICATION_DONE, contentMap)) { this.callback = object : MatrixCallback<Unit> { override fun onSuccess(data: Unit) { onDone?.invoke() @@ -176,7 +176,7 @@ internal class VerificationTransportToDevice( val contentMap = MXUsersDevicesMap<Any>() contentMap.setObject(otherUserId, otherUserDeviceId, cancelMessage) sendToDeviceTask - .configureWith(SendToDeviceTask.Params(EventType.KEY_VERIFICATION_CANCEL, contentMap, transactionId)) { + .configureWith(SendToDeviceTask.Params(EventType.KEY_VERIFICATION_CANCEL, contentMap)) { this.callback = object : MatrixCallback<Unit> { override fun onSuccess(data: Unit) { Timber.v("## SAS verification [$transactionId] canceled for reason ${code.value}") 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 2b3c3b28eebfca2f6da87efd999ca7cfadc51398..28ae4d8bfd48aa8b1f14eb06c6b72398625e65db 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 @@ -19,9 +19,10 @@ 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.room.model.VersioningState 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.internal.database.model.CurrentStateEventEntityFields @@ -40,14 +41,12 @@ import org.matrix.android.sdk.internal.database.model.SpaceChildSummaryEntityFie import org.matrix.android.sdk.internal.database.model.SpaceParentSummaryEntityFields import org.matrix.android.sdk.internal.database.model.TimelineEventEntityFields import org.matrix.android.sdk.internal.di.MoshiProvider +import org.matrix.android.sdk.internal.query.process import timber.log.Timber -import javax.inject.Inject -class RealmSessionStoreMigration @Inject constructor() : RealmMigration { +internal object RealmSessionStoreMigration : RealmMigration { - companion object { - const val SESSION_STORE_SCHEMA_VERSION = 14L - } + const val SESSION_STORE_SCHEMA_VERSION = 16L override fun migrate(realm: DynamicRealm, oldVersion: Long, newVersion: Long) { Timber.v("Migrating Realm Session from $oldVersion to $newVersion") @@ -66,6 +65,8 @@ class RealmSessionStoreMigration @Inject constructor() : RealmMigration { 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) } private fun migrateTo1(realm: DynamicRealm) { @@ -292,7 +293,7 @@ class RealmSessionStoreMigration @Inject constructor() : RealmMigration { 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) + .addField(RoomAccountDataEntityFields.TYPE, String::class.java, FieldAttribute.INDEXED) realm.schema.get("RoomEntity") ?.addRealmListField(RoomEntityFields.ACCOUNT_DATA.`$`, roomAccountDataSchema) @@ -306,4 +307,27 @@ class RealmSessionStoreMigration @Inject constructor() : RealmMigration { 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) + } + } } 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 244fe3432acf6c26e3c8944e7ad0fbdbca9a0c95..1771c5b202f8363fdbf79c6e1820fb5f072b771d 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 @@ -43,7 +43,6 @@ internal class SessionRealmConfigurationFactory @Inject constructor( @SessionFilesDirectory val directory: File, @SessionId val sessionId: String, @UserMd5 val userMd5: String, - val migration: RealmSessionStoreMigration, context: Context) { // Keep legacy preferences name for compatibility reason @@ -72,7 +71,7 @@ internal class SessionRealmConfigurationFactory @Inject constructor( .allowWritesOnUiThread(true) .modules(SessionRealmModule()) .schemaVersion(RealmSessionStoreMigration.SESSION_STORE_SCHEMA_VERSION) - .migration(migration) + .migration(RealmSessionStoreMigration) .build() // Try creating a realm instance and if it succeeds we can clear the flag diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/mapper/AccountDataMapper.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/mapper/AccountDataMapper.kt index 4edfdad8970e9a855eacf8aee26e5cca123f1c64..dca0f927ad08e811271aa3c92b36259fc3cfb217 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/mapper/AccountDataMapper.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/mapper/AccountDataMapper.kt @@ -17,7 +17,8 @@ package org.matrix.android.sdk.internal.database.mapper import com.squareup.moshi.Moshi -import org.matrix.android.sdk.api.session.accountdata.AccountDataEvent +import org.matrix.android.sdk.api.session.accountdata.UserAccountDataEvent +import org.matrix.android.sdk.api.session.room.accountdata.RoomAccountDataEvent import org.matrix.android.sdk.api.util.JSON_DICT_PARAMETERIZED_TYPE import org.matrix.android.sdk.internal.database.model.RoomAccountDataEntity import org.matrix.android.sdk.internal.database.model.UserAccountDataEntity @@ -27,15 +28,16 @@ internal class AccountDataMapper @Inject constructor(moshi: Moshi) { private val adapter = moshi.adapter<Map<String, Any>>(JSON_DICT_PARAMETERIZED_TYPE) - fun map(entity: UserAccountDataEntity): AccountDataEvent { - return AccountDataEvent( + fun map(entity: UserAccountDataEntity): UserAccountDataEvent { + return UserAccountDataEvent( type = entity.type ?: "", content = entity.contentStr?.let { adapter.fromJson(it) }.orEmpty() ) } - fun map(entity: RoomAccountDataEntity): AccountDataEvent { - return AccountDataEvent( + fun map(roomId: String, entity: RoomAccountDataEntity): RoomAccountDataEvent { + return RoomAccountDataEvent( + roomId = roomId, type = entity.type ?: "", content = entity.contentStr?.let { adapter.fromJson(it) }.orEmpty() ) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/mapper/HomeServerCapabilitiesMapper.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/mapper/HomeServerCapabilitiesMapper.kt index b18c67294fdd31528fa33dde16aa28b0007546f0..8b6d263f8c7e7bbf6ffe3bd4ed58e689d36451f6 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/mapper/HomeServerCapabilitiesMapper.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/mapper/HomeServerCapabilitiesMapper.kt @@ -16,8 +16,16 @@ package org.matrix.android.sdk.internal.database.mapper +import org.matrix.android.sdk.api.extensions.tryOrNull import org.matrix.android.sdk.api.session.homeserver.HomeServerCapabilities +import org.matrix.android.sdk.api.session.homeserver.RoomCapabilitySupport +import org.matrix.android.sdk.api.session.homeserver.RoomVersionCapabilities +import org.matrix.android.sdk.api.session.homeserver.RoomVersionInfo +import org.matrix.android.sdk.api.session.homeserver.RoomVersionStatus import org.matrix.android.sdk.internal.database.model.HomeServerCapabilitiesEntity +import org.matrix.android.sdk.internal.di.MoshiProvider +import org.matrix.android.sdk.internal.session.homeserver.RoomVersions +import org.matrix.android.sdk.internal.session.room.version.DefaultRoomVersionService /** * HomeServerCapabilitiesEntity -> HomeSeverCapabilities @@ -29,7 +37,39 @@ internal object HomeServerCapabilitiesMapper { canChangePassword = entity.canChangePassword, maxUploadFileSize = entity.maxUploadFileSize, lastVersionIdentityServerSupported = entity.lastVersionIdentityServerSupported, - defaultIdentityServerUrl = entity.defaultIdentityServerUrl + defaultIdentityServerUrl = entity.defaultIdentityServerUrl, + roomVersions = mapRoomVersion(entity.roomVersionsJson) ) } + + private fun mapRoomVersion(roomVersionsJson: String?): RoomVersionCapabilities? { + roomVersionsJson ?: return null + + return tryOrNull { + MoshiProvider.providesMoshi().adapter(RoomVersions::class.java).fromJson(roomVersionsJson)?.let { roomVersions -> + RoomVersionCapabilities( + defaultRoomVersion = roomVersions.default ?: DefaultRoomVersionService.DEFAULT_ROOM_VERSION, + supportedVersion = roomVersions.available?.entries?.map { entry -> + RoomVersionInfo(entry.key, RoomVersionStatus.STABLE + .takeIf { entry.value == "stable" } + ?: RoomVersionStatus.UNSTABLE) + }.orEmpty(), + capabilities = roomVersions.roomCapabilities?.entries?.mapNotNull { entry -> + (entry.value as? Map<*, *>)?.let { + val preferred = it["preferred"] as? String ?: return@mapNotNull null + val support = (it["support"] as? List<*>)?.filterIsInstance<String>() + entry.key to RoomCapabilitySupport(preferred, support.orEmpty()) + } + }?.toMap() + // Just for debug purpose +// ?: mapOf( +// HomeServerCapabilities.ROOM_CAP_RESTRICTED to RoomCapabilitySupport( +// preferred = null, +// support = listOf("org.matrix.msc3083") +// ) +// ) + ) + } + } + } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/HomeServerCapabilitiesEntity.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/HomeServerCapabilitiesEntity.kt index 763dcf80a20996bb5350956fdf442c67275ee53d..980449ddfbe4235c60044370ea6c43da42eac691 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/HomeServerCapabilitiesEntity.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/HomeServerCapabilitiesEntity.kt @@ -21,6 +21,7 @@ import org.matrix.android.sdk.api.session.homeserver.HomeServerCapabilities internal open class HomeServerCapabilitiesEntity( var canChangePassword: Boolean = true, + var roomVersionsJson: String? = null, var maxUploadFileSize: Long = HomeServerCapabilities.MAX_UPLOAD_FILE_SIZE_UNKNOWN, var lastVersionIdentityServerSupported: Boolean = false, var defaultIdentityServerUrl: String? = null, diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/UserAccountDataEntity.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/UserAccountDataEntity.kt index cfdb84d0335304484bab4d5611361ea004c9f6e5..e258c20df5aa238c201d445ba6e716080716d0c8 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/UserAccountDataEntity.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/UserAccountDataEntity.kt @@ -20,7 +20,7 @@ import io.realm.RealmObject import io.realm.annotations.Index /** - * Clients can store custom config data for their account on their HomeServer. + * Clients can store custom config data for their account on their homeserver. * This account data will be synced between different devices and can persist across installations on a particular device. * Users may only view the account data for their own account. * The account_data may be either global or scoped to a particular rooms. diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/federation/FederationModule.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/federation/FederationModule.kt index 320bf1d4454df3ec98294597e50191480671d707..a4eef80c58eb0e63f9da9723c04c11b12514df90 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/federation/FederationModule.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/federation/FederationModule.kt @@ -37,7 +37,8 @@ internal abstract class FederationModule { fun providesFederationAPI(@Unauthenticated okHttpClient: Lazy<OkHttpClient>, sessionParams: SessionParams, retrofitFactory: RetrofitFactory): FederationAPI { - return retrofitFactory.create(okHttpClient, sessionParams.homeServerUrl).create(FederationAPI::class.java) + return retrofitFactory.create(okHttpClient, sessionParams.homeServerUrlBase) + .create(FederationAPI::class.java) } } 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 e0e2f96fa92f547eda719aa6c08dfa278194db03..ad2aff4c9d94d922054986ffbd656d03d217d29e 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,6 @@ 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 realmCryptoStoreMigration: RealmCryptoStoreMigration, private val realmKeysUtils: RealmKeysUtils ) : LegacySessionImporter { @@ -153,7 +152,7 @@ internal class DefaultLegacySessionImporter @Inject constructor( } private fun importCryptoDb(legacyConfig: LegacyHomeServerConnectionConfig) { - // Here we migrate the DB, we copy the crypto DB to the location specific to RiotX, and we encrypt it. + // Here we migrate the DB, we copy the crypto DB to the location specific to Matrix SDK2, and we encrypt it. val userMd5 = legacyConfig.credentials.userId.md5() val sessionId = legacyConfig.credentials.let { (if (it.deviceId.isNullOrBlank()) it.userId else "${it.userId}|${it.deviceId}").md5() } @@ -172,17 +171,17 @@ internal class DefaultLegacySessionImporter @Inject constructor( .name("crypto_store.realm") .modules(RealmCryptoStoreModule()) .schemaVersion(RealmCryptoStoreMigration.CRYPTO_STORE_SCHEMA_VERSION) - .migration(realmCryptoStoreMigration) + .migration(RealmCryptoStoreMigration) .build() Timber.d("Migration: copy DB to encrypted DB") Realm.getInstance(realmConfiguration).use { - // Move the DB to the new location, handled by RiotX + // Move the DB to the new location, handled by Matrix SDK2 it.writeEncryptedCopyTo(File(newLocation, realmConfiguration.realmFileName), realmKeysUtils.getRealmEncryptionKey(keyAlias)) } } - // Delete all the files created by Riot Android which will not be used anymore by RiotX + // Delete all the files created by Riot Android which will not be used anymore by Element private fun clearFileSystem(legacyConfig: LegacyHomeServerConnectionConfig) { val cryptoFolder = legacyConfig.credentials.userId.md5() diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/legacy/riot/Credentials.java b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/legacy/riot/Credentials.java index 6844744044e562c4ba32d892f60c46e044d98ff1..0ca0c7db85f622ac132ffc9c533572c42c8bd7ff 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/legacy/riot/Credentials.java +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/legacy/riot/Credentials.java @@ -41,7 +41,7 @@ public class Credentials { public String deviceId; - // Optional data that may contain info to override home server and/or identity server + // Optional data that may contain info to override homeserver and/or identity server public WellKnown wellKnown; public JSONObject toJson() throws JSONException { diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/legacy/riot/HomeServerConnectionConfig.java b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/legacy/riot/HomeServerConnectionConfig.java index 21d069f2957977f460a778dde8c6e37e2717755a..75fc187c45814da75484ef703817f6363101ee77 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/legacy/riot/HomeServerConnectionConfig.java +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/legacy/riot/HomeServerConnectionConfig.java @@ -44,7 +44,7 @@ import timber.log.Timber; */ public class HomeServerConnectionConfig { - // the home server URI + // the homeserver URI private Uri mHomeServerUri; // the jitsi server URI. Can be null @Nullable @@ -82,7 +82,7 @@ public class HomeServerConnectionConfig { } /** - * Update the home server URI. + * Update the homeserver URI. * * @param uri the new HS uri */ @@ -91,7 +91,7 @@ public class HomeServerConnectionConfig { } /** - * @return the home server uri + * @return the homeserver uri */ public Uri getHomeserverUri() { return mHomeServerUri; @@ -145,7 +145,7 @@ public class HomeServerConnectionConfig { public void setCredentials(Credentials credentials) { mCredentials = credentials; - // Override home server url and/or identity server url if provided + // Override homeserver url and/or identity server url if provided if (credentials.wellKnown != null) { if (credentials.wellKnown.homeServer != null) { String homeServerUrl = credentials.wellKnown.homeServer.baseURL; @@ -200,7 +200,7 @@ public class HomeServerConnectionConfig { } /** - * TLS versions accepted for TLS connections with the home server. + * TLS versions accepted for TLS connections with the homeserver. */ @Nullable public List<TlsVersion> getAcceptedTlsVersions() { @@ -208,7 +208,7 @@ public class HomeServerConnectionConfig { } /** - * TLS cipher suites accepted for TLS connections with the home server. + * TLS cipher suites accepted for TLS connections with the homeserver. */ @Nullable public List<CipherSuite> getAcceptedTlsCipherSuites() { @@ -426,7 +426,7 @@ public class HomeServerConnectionConfig { */ public Builder withHomeServerUri(final Uri homeServerUri) { if (homeServerUri == null || (!"http".equals(homeServerUri.getScheme()) && !"https".equals(homeServerUri.getScheme()))) { - throw new RuntimeException("Invalid home server URI: " + homeServerUri); + throw new RuntimeException("Invalid homeserver URI: " + homeServerUri); } // remove trailing / @@ -435,7 +435,7 @@ public class HomeServerConnectionConfig { String url = homeServerUri.toString(); mHomeServerConnectionConfig.mHomeServerUri = Uri.parse(url.substring(0, url.length() - 1)); } catch (Exception e) { - throw new RuntimeException("Invalid home server URI: " + homeServerUri); + throw new RuntimeException("Invalid homeserver URI: " + homeServerUri); } } else { mHomeServerConnectionConfig.mHomeServerUri = homeServerUri; @@ -549,7 +549,7 @@ public class HomeServerConnectionConfig { } /** - * Add an accepted TLS version for TLS connections with the home server. + * Add an accepted TLS version for TLS connections with the homeserver. * * @param tlsVersion the tls version to add to the set of TLS versions accepted. * @return this builder @@ -577,7 +577,7 @@ public class HomeServerConnectionConfig { } /** - * Add a TLS cipher suite to the list of accepted TLS connections with the home server. + * Add a TLS cipher suite to the list of accepted TLS connections with the homeserver. * * @param tlsCipherSuite the tls cipher suite to add. * @return this builder @@ -666,7 +666,7 @@ public class HomeServerConnectionConfig { public HomeServerConnectionConfig build() { // Check mandatory parameters if (mHomeServerConnectionConfig.mHomeServerUri == null) { - throw new RuntimeException("Home server URI not set"); + throw new RuntimeException("Homeserver URI not set"); } return mHomeServerConnectionConfig; diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/legacy/riot/LoginStorage.java b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/legacy/riot/LoginStorage.java index 516007524e5f4af2cd23f30fa98d7bb8382cffaa..2820b6688617a236a627f6709f959294ca94c90c 100755 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/legacy/riot/LoginStorage.java +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/legacy/riot/LoginStorage.java @@ -38,7 +38,7 @@ import timber.log.Timber; public class LoginStorage { private static final String PREFS_LOGIN = "Vector.LoginStorage"; - // multi accounts + home server config + // multi accounts + homeserver config private static final String PREFS_KEY_CONNECTION_CONFIGS = "PREFS_KEY_CONNECTION_CONFIGS"; private final Context mContext; @@ -49,7 +49,7 @@ public class LoginStorage { } /** - * @return the list of home server configurations. + * @return the list of homeserver configurations. */ public List<HomeServerConnectionConfig> getCredentialsList() { SharedPreferences prefs = mContext.getSharedPreferences(PREFS_LOGIN, Context.MODE_PRIVATE); @@ -85,7 +85,7 @@ public class LoginStorage { /** * Add a credentials to the credentials list * - * @param config the home server config to add. + * @param config the homeserver config to add. */ public void addCredentials(HomeServerConnectionConfig config) { if (null != config && config.getCredentials() != null) { @@ -203,4 +203,4 @@ public class LoginStorage { //Need to commit now because called before forcing an app restart editor.commit(); } -} \ No newline at end of file +} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/network/UserAgentHolder.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/network/UserAgentHolder.kt index 973c120f59abd4204920352cd24b501466d20e71..1a884041280c3ca872b191587591dcba593d81d1 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/network/UserAgentHolder.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/network/UserAgentHolder.kt @@ -36,7 +36,7 @@ internal class UserAgentHolder @Inject constructor(private val context: Context, /** * Create an user agent with the application version. - * Ex: RiotX/1.0.0 (Linux; U; Android 6.0.1; SM-A510F Build/MMB29; Flavour GPlay; MatrixAndroidSDK_X 1.0) + * Ex: Element/1.0.0 (Linux; U; Android 6.0.1; SM-A510F Build/MMB29; Flavour GPlay; MatrixAndroidSDK_X 1.0) * * @param flavorDescription the flavor description */ diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/network/ssl/CertUtil.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/network/ssl/CertUtil.kt index 9d7263f56a2db108c6835d29d53433b09563a3c9..976751446b1afad754df909b06b518adab48d28b 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/network/ssl/CertUtil.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/network/ssl/CertUtil.kt @@ -253,7 +253,7 @@ internal object CertUtil { val list = ArrayList<ConnectionSpec>() list.add(builder.build()) // TODO: we should display a warning if user enter an http url - if (hsConfig.allowHttpExtension || hsConfig.homeServerUri.toString().startsWith("http://")) { + if (hsConfig.allowHttpExtension || hsConfig.homeServerUriBase.toString().startsWith("http://")) { list.add(ConnectionSpec.CLEARTEXT) } return list diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/raw/DefaultRawService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/raw/DefaultRawService.kt index 42b826de16240918edc9443a93a239c812da2d7d..bca1e498de029df928eb8b386b16316497f75ead 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/raw/DefaultRawService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/raw/DefaultRawService.kt @@ -29,10 +29,9 @@ internal class DefaultRawService @Inject constructor( return getUrlTask.execute(GetUrlTask.Params(url, cacheStrategy)) } - override suspend fun getWellknown(userId: String): String { - val homeServerDomain = userId.substringAfter(":") + override suspend fun getWellknown(domain: String): String { return getUrl( - "https://$homeServerDomain/.well-known/matrix/client", + "https://$domain/.well-known/matrix/client", CacheStrategy.TtlCache(TimeUnit.HOURS.toMillis(8), false) ) } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/DefaultFileService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/DefaultFileService.kt index a284d976d0525187ce18e5306e488f7a29f2cd6f..414c018074dad12c116ad4c4594a47d1fd14dc5e 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/DefaultFileService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/DefaultFileService.kt @@ -25,6 +25,7 @@ import kotlinx.coroutines.completeWith import kotlinx.coroutines.withContext import okhttp3.OkHttpClient import okhttp3.Request +import org.matrix.android.sdk.api.failure.Failure import org.matrix.android.sdk.api.session.content.ContentUrlResolver import org.matrix.android.sdk.api.session.file.FileService import org.matrix.android.sdk.internal.crypto.attachments.ElementToDecrypt @@ -33,6 +34,7 @@ import org.matrix.android.sdk.internal.di.SessionDownloadsDirectory import org.matrix.android.sdk.internal.di.UnauthenticatedWithCertificateWithProgress import org.matrix.android.sdk.internal.session.download.DownloadProgressInterceptor.Companion.DOWNLOAD_PROGRESS_INTERCEPTOR_HEADER import org.matrix.android.sdk.internal.util.MatrixCoroutineDispatchers +import org.matrix.android.sdk.internal.util.file.AtomicFileCreator import org.matrix.android.sdk.internal.util.md5 import org.matrix.android.sdk.internal.util.writeToFile import timber.log.Timber @@ -96,6 +98,9 @@ internal class DefaultFileService @Inject constructor( } } + var atomicFileDownload: AtomicFileCreator? = null + var atomicFileDecrypt: AtomicFileCreator? = null + if (existingDownload != null) { // FIXME If the first downloader cancels then we'll unfortunately be cancelled too. return existingDownload.await() @@ -120,19 +125,30 @@ internal class DefaultFileService @Inject constructor( .header(DOWNLOAD_PROGRESS_INTERCEPTOR_HEADER, url) .build() - val response = okHttpClient.newCall(request).execute() + val response = try { + okHttpClient.newCall(request).execute() + } catch (failure: Throwable) { + throw if (failure is IOException) { + Failure.NetworkConnection(failure) + } else { + failure + } + } if (!response.isSuccessful) { - throw IOException() + throw Failure.NetworkConnection(IOException()) } - val source = response.body?.source() ?: throw IOException() + val source = response.body?.source() ?: throw Failure.NetworkConnection(IOException()) Timber.v("Response size ${response.body?.contentLength()} - Stream available: ${!source.exhausted()}") // Write the file to cache (encrypted version if the file is encrypted) - writeToFile(source.inputStream(), cachedFiles.file) + // Write to a part file first, so if we abort before done, we don't have a broken cached file + val atomicFileCreator = AtomicFileCreator(cachedFiles.file).also { atomicFileDownload = it } + writeToFile(source.inputStream(), atomicFileCreator.partFile) response.close() + atomicFileCreator.commit() } else { Timber.v("## FileService: cache hit for $url") } @@ -145,8 +161,10 @@ internal class DefaultFileService @Inject constructor( Timber.v("## FileService: decrypt file") // Ensure the parent folder exists cachedFiles.decryptedFile.parentFile?.mkdirs() + // Write to a part file first, so if we abort before done, we don't have a broken cached file + val atomicFileCreator = AtomicFileCreator(cachedFiles.decryptedFile).also { atomicFileDecrypt = it } val decryptSuccess = cachedFiles.file.inputStream().use { inputStream -> - cachedFiles.decryptedFile.outputStream().buffered().use { outputStream -> + atomicFileCreator.partFile.outputStream().buffered().use { outputStream -> MXEncryptedAttachments.decryptAttachment( inputStream, elementToDecrypt, @@ -154,6 +172,7 @@ internal class DefaultFileService @Inject constructor( ) } } + atomicFileCreator.commit() if (!decryptSuccess) { throw IllegalStateException("Decryption error") } @@ -174,6 +193,11 @@ internal class DefaultFileService @Inject constructor( } toNotify?.completeWith(result) + result.onFailure { + atomicFileDownload?.cancel() + atomicFileDecrypt?.cancel() + } + return result.getOrThrow() } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/DefaultSession.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/DefaultSession.kt index 1f4797819892a8455d862832d00b416b25323cf9..c2bd1e24edea5dd2f9148fb72c3966283b137ae3 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/DefaultSession.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/DefaultSession.kt @@ -29,7 +29,7 @@ import org.matrix.android.sdk.api.pushrules.PushRuleService import org.matrix.android.sdk.api.session.Session import org.matrix.android.sdk.api.session.SessionLifecycleObserver import org.matrix.android.sdk.api.session.account.AccountService -import org.matrix.android.sdk.api.session.accountdata.AccountDataService +import org.matrix.android.sdk.api.session.accountdata.SessionAccountDataService import org.matrix.android.sdk.api.session.cache.CacheService import org.matrix.android.sdk.api.session.call.CallSignalingService import org.matrix.android.sdk.api.session.content.ContentUploadStateTracker @@ -74,7 +74,6 @@ import org.matrix.android.sdk.internal.session.identity.DefaultIdentityService import org.matrix.android.sdk.internal.session.sync.SyncTokenStore import org.matrix.android.sdk.internal.session.sync.job.SyncThread import org.matrix.android.sdk.internal.session.sync.job.SyncWorker -import org.matrix.android.sdk.internal.session.user.accountdata.UserAccountDataService import org.matrix.android.sdk.internal.util.createUIHandler import timber.log.Timber import javax.inject.Inject @@ -118,7 +117,7 @@ internal class DefaultSession @Inject constructor( private val contentDownloadStateTracker: ContentDownloadStateTracker, private val initialSyncProgressService: Lazy<InitialSyncProgressService>, private val homeServerCapabilitiesService: Lazy<HomeServerCapabilitiesService>, - private val accountDataService: Lazy<UserAccountDataService>, + private val accountDataService: Lazy<SessionAccountDataService>, private val _sharedSecretStorageService: Lazy<SharedSecretStorageService>, private val accountService: Lazy<AccountService>, private val eventService: Lazy<EventService>, @@ -293,7 +292,7 @@ internal class DefaultSession @Inject constructor( override fun openIdService(): OpenIdService = openIdService.get() - override fun userAccountDataService(): AccountDataService = accountDataService.get() + override fun accountDataService(): SessionAccountDataService = accountDataService.get() override fun getOkHttpClient(): OkHttpClient { return unauthenticatedWithCertificateOkHttpClient.get() @@ -314,7 +313,7 @@ internal class DefaultSession @Inject constructor( override fun getUiaSsoFallbackUrl(authenticationSessionId: String): String { val hsBas = sessionParams.homeServerConnectionConfig - .homeServerUri + .homeServerUriBase .toString() .trim { it == '/' } return buildString { diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/SessionModule.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/SessionModule.kt index 49ce92372e2381a984ab4516fc3efea1d811021f..cb29cb4819c07ca7a2c779b5156773b5f97bfe80 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/SessionModule.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/SessionModule.kt @@ -34,7 +34,7 @@ import org.matrix.android.sdk.api.auth.data.sessionId import org.matrix.android.sdk.api.crypto.MXCryptoConfig import org.matrix.android.sdk.api.session.Session import org.matrix.android.sdk.api.session.SessionLifecycleObserver -import org.matrix.android.sdk.api.session.accountdata.AccountDataService +import org.matrix.android.sdk.api.session.accountdata.SessionAccountDataService import org.matrix.android.sdk.api.session.events.EventService import org.matrix.android.sdk.api.session.homeserver.HomeServerCapabilitiesService import org.matrix.android.sdk.api.session.initsync.InitialSyncProgressService @@ -93,7 +93,7 @@ import org.matrix.android.sdk.internal.session.room.send.queue.EventSenderProces import org.matrix.android.sdk.internal.session.room.tombstone.RoomTombstoneEventProcessor import org.matrix.android.sdk.internal.session.securestorage.DefaultSecureStorageService import org.matrix.android.sdk.internal.session.typing.DefaultTypingUsersTracker -import org.matrix.android.sdk.internal.session.user.accountdata.UserAccountDataService +import org.matrix.android.sdk.internal.session.user.accountdata.DefaultSessionAccountDataService import org.matrix.android.sdk.internal.session.widgets.DefaultWidgetURLFormatter import org.matrix.android.sdk.internal.util.md5 import retrofit2.Retrofit @@ -261,7 +261,7 @@ internal abstract class SessionModule { sessionParams: SessionParams, retrofitFactory: RetrofitFactory): Retrofit { return retrofitFactory - .create(okHttpClient, sessionParams.homeServerConnectionConfig.homeServerUri.toString()) + .create(okHttpClient, sessionParams.homeServerConnectionConfig.homeServerUriBase.toString()) } @JvmStatic @@ -364,7 +364,7 @@ internal abstract class SessionModule { abstract fun bindHomeServerCapabilitiesService(service: DefaultHomeServerCapabilitiesService): HomeServerCapabilitiesService @Binds - abstract fun bindAccountDataService(service: UserAccountDataService): AccountDataService + abstract fun bindSessionAccountDataService(service: DefaultSessionAccountDataService): SessionAccountDataService @Binds abstract fun bindEventService(service: DefaultEventService): EventService diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/call/CallEventProcessor.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/call/CallEventProcessor.kt index a190ff62acebd4687b8e2b47dd5bff3d57215d5c..bdc254fc99cf20d4ffb75b650357e12fe6824d60 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/call/CallEventProcessor.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/call/CallEventProcessor.kt @@ -20,11 +20,14 @@ import io.realm.Realm import org.matrix.android.sdk.api.session.events.model.Event import org.matrix.android.sdk.api.session.events.model.EventType import org.matrix.android.sdk.internal.database.model.EventInsertType +import org.matrix.android.sdk.api.logger.LoggerTag import org.matrix.android.sdk.internal.session.EventInsertLiveProcessor import org.matrix.android.sdk.internal.session.SessionScope import timber.log.Timber import javax.inject.Inject +private val loggerTag = LoggerTag("CallEventProcessor", LoggerTag.VOIP) + @SessionScope internal class CallEventProcessor @Inject constructor(private val callSignalingHandler: CallSignalingHandler) : EventInsertLiveProcessor { @@ -37,7 +40,9 @@ internal class CallEventProcessor @Inject constructor(private val callSignalingH EventType.CALL_CANDIDATES, EventType.CALL_INVITE, EventType.CALL_HANGUP, - EventType.ENCRYPTED + EventType.ENCRYPTED, + EventType.CALL_ASSERTED_IDENTITY, + EventType.CALL_ASSERTED_IDENTITY_PREFIX ) private val eventsToPostProcess = mutableListOf<Event>() @@ -57,9 +62,8 @@ internal class CallEventProcessor @Inject constructor(private val callSignalingH return eventType == EventType.CALL_INVITE } - suspend fun processFastLane(event: Event) { - eventsToPostProcess.add(event) - onPostProcess() + fun processFastLane(event: Event) { + dispatchToCallSignalingHandlerIfNeeded(event) } override suspend fun onPostProcess() { @@ -70,15 +74,8 @@ internal class CallEventProcessor @Inject constructor(private val callSignalingH } private fun dispatchToCallSignalingHandlerIfNeeded(event: Event) { - val now = System.currentTimeMillis() - // TODO might check if an invite is not closed (hangup/answered) in the same event batch? event.roomId ?: return Unit.also { - Timber.w("Event with no room id ${event.eventId}") - } - val age = now - (event.ageLocalTs ?: now) - if (age > 40_000) { - // To old to ring? - return + Timber.tag(loggerTag.value).w("Event with no room id ${event.eventId}") } callSignalingHandler.onCallEvent(event) } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/call/CallListenersDispatcher.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/call/CallListenersDispatcher.kt index 1de2d8a106e66beeb21fec80d3abc7d85d88e2dd..dad17f4ac933627c8190225c9206c3afbf0593f9 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/call/CallListenersDispatcher.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/call/CallListenersDispatcher.kt @@ -20,6 +20,7 @@ import org.matrix.android.sdk.api.extensions.tryOrNull import org.matrix.android.sdk.api.session.call.CallListener import org.matrix.android.sdk.api.session.call.MxCall import org.matrix.android.sdk.api.session.room.model.call.CallAnswerContent +import org.matrix.android.sdk.api.session.room.model.call.CallAssertedIdentityContent import org.matrix.android.sdk.api.session.room.model.call.CallCandidatesContent import org.matrix.android.sdk.api.session.room.model.call.CallHangupContent import org.matrix.android.sdk.api.session.room.model.call.CallInviteContent @@ -64,6 +65,10 @@ internal class CallListenersDispatcher(private val listeners: Set<CallListener>) it.onCallNegotiateReceived(callNegotiateContent) } + override fun onCallAssertedIdentityReceived(callAssertedIdentityContent: CallAssertedIdentityContent) = dispatch { + it.onCallAssertedIdentityReceived(callAssertedIdentityContent) + } + private fun dispatch(lambda: (CallListener) -> Unit) { listeners.toList().forEach { tryOrNull { diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/call/CallSignalingHandler.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/call/CallSignalingHandler.kt index 61ea660b6045313708ca783a80078f9e74bb68ee..59058bf9767648cea08f3dea30bbfb7bfca60502 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/call/CallSignalingHandler.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/call/CallSignalingHandler.kt @@ -16,6 +16,7 @@ package org.matrix.android.sdk.internal.session.call +import org.matrix.android.sdk.api.logger.LoggerTag import org.matrix.android.sdk.api.session.call.CallListener import org.matrix.android.sdk.api.session.call.CallState import org.matrix.android.sdk.api.session.call.MxCall @@ -23,6 +24,7 @@ import org.matrix.android.sdk.api.session.events.model.Event import org.matrix.android.sdk.api.session.events.model.EventType import org.matrix.android.sdk.api.session.events.model.toModel import org.matrix.android.sdk.api.session.room.model.call.CallAnswerContent +import org.matrix.android.sdk.api.session.room.model.call.CallAssertedIdentityContent import org.matrix.android.sdk.api.session.room.model.call.CallCandidatesContent import org.matrix.android.sdk.api.session.room.model.call.CallHangupContent import org.matrix.android.sdk.api.session.room.model.call.CallInviteContent @@ -35,11 +37,15 @@ import org.matrix.android.sdk.internal.session.SessionScope import timber.log.Timber import javax.inject.Inject +private val loggerTag = LoggerTag("CallSignalingHandler", LoggerTag.VOIP) +private const val MAX_AGE_TO_RING = 40_000 + @SessionScope internal class CallSignalingHandler @Inject constructor(private val activeCallHandler: ActiveCallHandler, private val mxCallFactory: MxCallFactory, @UserId private val userId: String) { + private val invitedCallIds = mutableSetOf<String>() private val callListeners = mutableSetOf<CallListener>() private val callListenersDispatcher = CallListenersDispatcher(callListeners) @@ -53,30 +59,44 @@ internal class CallSignalingHandler @Inject constructor(private val activeCallHa fun onCallEvent(event: Event) { when (event.getClearType()) { - EventType.CALL_ANSWER -> { + EventType.CALL_ANSWER -> { handleCallAnswerEvent(event) } - EventType.CALL_INVITE -> { + EventType.CALL_INVITE -> { handleCallInviteEvent(event) } - EventType.CALL_HANGUP -> { + EventType.CALL_HANGUP -> { handleCallHangupEvent(event) } - EventType.CALL_REJECT -> { + EventType.CALL_REJECT -> { handleCallRejectEvent(event) } - EventType.CALL_CANDIDATES -> { + EventType.CALL_CANDIDATES -> { handleCallCandidatesEvent(event) } - EventType.CALL_SELECT_ANSWER -> { + EventType.CALL_SELECT_ANSWER -> { handleCallSelectAnswerEvent(event) } - EventType.CALL_NEGOTIATE -> { + EventType.CALL_NEGOTIATE -> { handleCallNegotiateEvent(event) } + EventType.CALL_ASSERTED_IDENTITY, + EventType.CALL_ASSERTED_IDENTITY_PREFIX -> { + handleCallAssertedIdentityEvent(event) + } } } + private fun handleCallAssertedIdentityEvent(event: Event) { + val content = event.getClearContent().toModel<CallAssertedIdentityContent>() ?: return + val call = content.getCall() ?: return + if (call.ourPartyId == content.partyId) { + // Ignore remote echo (not that we send asserted identity, but still...) + return + } + callListenersDispatcher.onCallAssertedIdentityReceived(content) + } + private fun handleCallNegotiateEvent(event: Event) { val content = event.getClearContent().toModel<CallNegotiateContent>() ?: return val call = content.getCall() ?: return @@ -95,12 +115,12 @@ internal class CallSignalingHandler @Inject constructor(private val activeCallHa return } if (call.isOutgoing) { - Timber.v("Got selectAnswer for an outbound call: ignoring") + Timber.tag(loggerTag.value).v("Got selectAnswer for an outbound call: ignoring") return } val selectedPartyId = content.selectedPartyId if (selectedPartyId == null) { - Timber.w("Got nonsensical select_answer with null selected_party_id: ignoring") + Timber.tag(loggerTag.value).w("Got nonsensical select_answer with null selected_party_id: ignoring") return } callListenersDispatcher.onCallSelectAnswerReceived(content) @@ -114,7 +134,7 @@ internal class CallSignalingHandler @Inject constructor(private val activeCallHa return } if (call.opponentPartyId != null && !call.partyIdsMatches(content)) { - Timber.v("Ignoring candidates from party ID ${content.partyId} we have chosen party ID ${call.opponentPartyId}") + Timber.tag(loggerTag.value).v("Ignoring candidates from party ID ${content.partyId} we have chosen party ID ${call.opponentPartyId}") return } callListenersDispatcher.onCallIceCandidateReceived(call, content) @@ -147,10 +167,10 @@ internal class CallSignalingHandler @Inject constructor(private val activeCallHa // party ID must match (our chosen partner hanging up the call) or be undefined (we haven't chosen // a partner yet but we're treating the hangup as a reject as per VoIP v0) if (call.opponentPartyId != null && !call.partyIdsMatches(content)) { - Timber.v("Ignoring hangup from party ID ${content.partyId} we have chosen party ID ${call.opponentPartyId}") + Timber.tag(loggerTag.value).v("Ignoring hangup from party ID ${content.partyId} we have chosen party ID ${call.opponentPartyId}") return } - if (call.state != CallState.Terminated) { + if (call.state !is CallState.Ended) { activeCallHandler.removeCall(content.callId) callListenersDispatcher.onCallHangupReceived(content) } @@ -164,20 +184,26 @@ internal class CallSignalingHandler @Inject constructor(private val activeCallHa if (event.roomId == null || event.senderId == null) { return } + val now = System.currentTimeMillis() + val age = now - (event.ageLocalTs ?: now) + if (age > MAX_AGE_TO_RING) { + Timber.tag(loggerTag.value).w("Call invite is too old to ring.") + return + } val content = event.getClearContent().toModel<CallInviteContent>() ?: return content.callId ?: return - if (activeCallHandler.getCallWithId(content.callId) != null) { + if (invitedCallIds.contains(content.callId)) { // Call is already known, maybe due to fast lane. Ignore - Timber.d("Ignoring already known call invite") + Timber.tag(loggerTag.value).d("Ignoring already known call invite") return } - val incomingCall = mxCallFactory.createIncomingCall( roomId = event.roomId, opponentUserId = event.senderId, content = content ) ?: return + invitedCallIds.add(content.callId) activeCallHandler.addCall(incomingCall) callListenersDispatcher.onCallInviteReceived(incomingCall, content) } @@ -198,7 +224,8 @@ internal class CallSignalingHandler @Inject constructor(private val activeCallHa callListenersDispatcher.onCallManagedByOtherSession(content.callId) } else { if (call.opponentPartyId != null) { - Timber.v("Ignoring answer from party ID ${content.partyId} we already have an answer from ${call.opponentPartyId}") + Timber.tag(loggerTag.value) + .v("Ignoring answer from party ID ${content.partyId} we already have an answer from ${call.opponentPartyId}") return } mxCallFactory.updateOutgoingCallWithOpponentData(call, event.senderId, content, content.capabilities) @@ -215,7 +242,7 @@ internal class CallSignalingHandler @Inject constructor(private val activeCallHa activeCallHandler.getCallWithId(it) } if (currentCall == null) { - Timber.v("Call with id $callId is null") + Timber.tag(loggerTag.value).v("Call with id $callId is null") } return currentCall } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/call/DefaultCallSignalingService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/call/DefaultCallSignalingService.kt index da1f84cc899f297a337de1c408af899f8c1ebf08..4a949e21a691c20fd8da9e51ee05e84f6b5ef882 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/call/DefaultCallSignalingService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/call/DefaultCallSignalingService.kt @@ -21,7 +21,6 @@ import org.matrix.android.sdk.api.session.call.CallSignalingService import org.matrix.android.sdk.api.session.call.MxCall import org.matrix.android.sdk.api.session.call.TurnServerResponse import org.matrix.android.sdk.internal.session.SessionScope -import timber.log.Timber import javax.inject.Inject @SessionScope @@ -51,7 +50,6 @@ internal class DefaultCallSignalingService @Inject constructor( } override fun getCallWithId(callId: String): MxCall? { - Timber.v("## VOIP getCallWithId $callId all calls ${activeCallHandler.getActiveCallsLiveData().value?.map { it.callId }}") return activeCallHandler.getCallWithId(callId) } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/call/model/MxCallImpl.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/call/model/MxCallImpl.kt index f101685a4b46b6f6248d79e635956bf139d8dda6..9fc84e6fe534644e8daa4236be64acf0c88438ee 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/call/model/MxCallImpl.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/call/model/MxCallImpl.kt @@ -17,6 +17,7 @@ package org.matrix.android.sdk.internal.session.call.model import org.matrix.android.sdk.api.MatrixConfiguration +import org.matrix.android.sdk.api.logger.LoggerTag import org.matrix.android.sdk.api.session.call.CallIdGenerator import org.matrix.android.sdk.api.session.call.CallState import org.matrix.android.sdk.api.session.call.MxCall @@ -38,6 +39,7 @@ import org.matrix.android.sdk.api.session.room.model.call.CallRejectContent import org.matrix.android.sdk.api.session.room.model.call.CallReplacesContent import org.matrix.android.sdk.api.session.room.model.call.CallSelectAnswerContent import org.matrix.android.sdk.api.session.room.model.call.CallSignalingContent +import org.matrix.android.sdk.api.session.room.model.call.EndCallReason import org.matrix.android.sdk.api.session.room.model.call.SdpType import org.matrix.android.sdk.api.util.Optional import org.matrix.android.sdk.internal.session.call.DefaultCallSignalingService @@ -47,6 +49,8 @@ import org.matrix.android.sdk.internal.session.room.send.queue.EventSenderProces import timber.log.Timber import java.math.BigDecimal +private val loggerTag = LoggerTag("MxCallImpl", LoggerTag.VOIP) + internal class MxCallImpl( override val callId: String, override val isOutgoing: Boolean, @@ -93,7 +97,7 @@ internal class MxCallImpl( try { it.onStateUpdate(this) } catch (failure: Throwable) { - Timber.d("dispatchStateChange failed for call $callId : ${failure.localizedMessage}") + Timber.tag(loggerTag.value).d("dispatchStateChange failed for call $callId : ${failure.localizedMessage}") } } } @@ -109,7 +113,7 @@ internal class MxCallImpl( override fun offerSdp(sdpString: String) { if (!isOutgoing) return - Timber.v("## VOIP offerSdp $callId") + Timber.tag(loggerTag.value).v("offerSdp $callId") state = CallState.Dialing CallInviteContent( callId = callId, @@ -124,7 +128,7 @@ internal class MxCallImpl( } override fun sendLocalCallCandidates(candidates: List<CallCandidate>) { - Timber.v("Send local call canditates $callId: $candidates") + Timber.tag(loggerTag.value).v("Send local call canditates $callId: $candidates") CallCandidatesContent( callId = callId, partyId = ourPartyId, @@ -141,11 +145,11 @@ internal class MxCallImpl( override fun reject() { if (opponentVersion < 1) { - Timber.v("Opponent version is less than 1 ($opponentVersion): sending hangup instead of reject") - hangUp() + Timber.tag(loggerTag.value).v("Opponent version is less than 1 ($opponentVersion): sending hangup instead of reject") + hangUp(EndCallReason.USER_HANGUP) return } - Timber.v("## VOIP reject $callId") + Timber.tag(loggerTag.value).v("reject $callId") CallRejectContent( callId = callId, partyId = ourPartyId, @@ -153,24 +157,24 @@ internal class MxCallImpl( ) .let { createEventAndLocalEcho(type = EventType.CALL_REJECT, roomId = roomId, content = it.toContent()) } .also { eventSenderProcessor.postEvent(it) } - state = CallState.Terminated + state = CallState.Ended(reason = EndCallReason.USER_HANGUP) } - override fun hangUp(reason: CallHangupContent.Reason?) { - Timber.v("## VOIP hangup $callId") + override fun hangUp(reason: EndCallReason?) { + Timber.tag(loggerTag.value).v("hangup $callId") CallHangupContent( callId = callId, partyId = ourPartyId, - reason = reason ?: CallHangupContent.Reason.USER_HANGUP, + reason = reason, version = MxCall.VOIP_PROTO_VERSION.toString() ) .let { createEventAndLocalEcho(type = EventType.CALL_HANGUP, roomId = roomId, content = it.toContent()) } .also { eventSenderProcessor.postEvent(it) } - state = CallState.Terminated + state = CallState.Ended(reason) } override fun accept(sdpString: String) { - Timber.v("## VOIP accept $callId") + Timber.tag(loggerTag.value).v("accept $callId") if (isOutgoing) return state = CallState.Answering CallAnswerContent( @@ -185,7 +189,7 @@ internal class MxCallImpl( } override fun negotiate(sdpString: String, type: SdpType) { - Timber.v("## VOIP negotiate $callId") + Timber.tag(loggerTag.value).v("negotiate $callId") CallNegotiateContent( callId = callId, partyId = ourPartyId, @@ -198,7 +202,7 @@ internal class MxCallImpl( } override fun selectAnswer() { - Timber.v("## VOIP select answer $callId") + Timber.tag(loggerTag.value).v("select answer $callId") if (isOutgoing) return state = CallState.Answering CallSelectAnswerContent( @@ -219,7 +223,7 @@ internal class MxCallImpl( val profileInfo = try { getProfileInfoTask.execute(profileInfoParams) } catch (failure: Throwable) { - Timber.v("Fail fetching profile info of $targetUserId while transferring call") + Timber.tag(loggerTag.value).v("Fail fetching profile info of $targetUserId while transferring call") null } CallReplacesContent( 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 b5c1e6a282742813f47dbadb6b5f13fd966c74aa..8870d92a35795d7f8e5a4a6bee104eb151a31744 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 @@ -51,15 +51,12 @@ internal class CleanupSession @Inject constructor( @UserMd5 private val userMd5: String ) { suspend fun handle() { - Timber.d("Cleanup: release session...") - sessionManager.releaseSession(sessionId) + Timber.d("Cleanup: delete session params...") + sessionParamsStore.delete(sessionId) Timber.d("Cleanup: cancel pending works...") workManagerProvider.cancelAllWorks() - Timber.d("Cleanup: delete session params...") - sessionParamsStore.delete(sessionId) - Timber.d("Cleanup: clear session data...") clearSessionDataTask.execute(Unit) @@ -74,6 +71,9 @@ internal class CleanupSession @Inject constructor( realmKeysUtils.clear(SessionModule.getKeyAlias(userMd5)) realmKeysUtils.clear(CryptoModule.getKeyAlias(userMd5)) + Timber.d("Cleanup: release session...") + sessionManager.releaseSession(sessionId) + // Sanity check if (BuildConfig.DEBUG) { Realm.getGlobalInstanceCount(realmSessionConfiguration) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/content/DefaultContentUrlResolver.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/content/DefaultContentUrlResolver.kt index f765056496f4fe020678ce8cbf6bbf131617035f..e4efdaa254f2909437eb7050170d1f9b45925eea 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/content/DefaultContentUrlResolver.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/content/DefaultContentUrlResolver.kt @@ -26,7 +26,7 @@ private const val MATRIX_CONTENT_URI_SCHEME = "mxc://" internal class DefaultContentUrlResolver @Inject constructor(homeServerConnectionConfig: HomeServerConnectionConfig) : ContentUrlResolver { - private val baseUrl = homeServerConnectionConfig.homeServerUri.toString().ensureTrailingSlash() + private val baseUrl = homeServerConnectionConfig.homeServerUriBase.toString().ensureTrailingSlash() override val uploadUrl = baseUrl + NetworkConstants.URI_API_MEDIA_PREFIX_PATH_R0 + "upload" diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/content/UploadContentWorker.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/content/UploadContentWorker.kt index 237411db53d72f614b887eb30af833a5a000706e..f14c85cf804c9f06122947f014cc777ab5ee7ac6 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/content/UploadContentWorker.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/content/UploadContentWorker.kt @@ -234,7 +234,7 @@ internal class UploadContentWorker(val context: Context, params: WorkerParameter .also { filesToDelete.add(it) } uploadedFileEncryptedFileInfo = - MXEncryptedAttachments.encrypt(fileToUpload.inputStream(), attachment.getSafeMimeType(), encryptedFile) { read, total -> + MXEncryptedAttachments.encrypt(fileToUpload.inputStream(), encryptedFile) { read, total -> notifyTracker(params) { contentUploadStateTracker.setEncrypting(it, read.toLong(), total.toLong()) } @@ -315,7 +315,7 @@ internal class UploadContentWorker(val context: Context, params: WorkerParameter if (params.isEncrypted) { Timber.v("Encrypt thumbnail") notifyTracker(params) { contentUploadStateTracker.setEncryptingThumbnail(it) } - val encryptionResult = MXEncryptedAttachments.encryptAttachment(thumbnailData.bytes.inputStream(), thumbnailData.mimeType) + val encryptionResult = MXEncryptedAttachments.encryptAttachment(thumbnailData.bytes.inputStream()) val contentUploadResponse = fileUploader.uploadByteArray( byteArray = encryptionResult.encryptedByteArray, filename = null, diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/events/DefaultEventService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/events/DefaultEventService.kt index d7e9ef2ee04b1801ee883e3b987a055a2484e988..9d80f27e0196eca61d2f04a1546b7911e0abb0b4 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/events/DefaultEventService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/events/DefaultEventService.kt @@ -29,7 +29,7 @@ internal class DefaultEventService @Inject constructor( override suspend fun getEvent(roomId: String, eventId: String): Event { val event = getEventTask.execute(GetEventTask.Params(roomId, eventId)) - + event.ageLocalTs = event.unsignedData?.age?.let { System.currentTimeMillis() - it } // Fast lane to the call event processors: try to make the incoming call ring faster if (callEventProcessor.shouldProcessFastLane(event.getClearType())) { callEventProcessor.processFastLane(event) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/homeserver/DefaultHomeServerCapabilitiesService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/homeserver/DefaultHomeServerCapabilitiesService.kt index 0ed690d972e829f85798daa7b27f8efc84e85e57..4c755b54b514cf8f8fd2d50c412e2b5439114814 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/homeserver/DefaultHomeServerCapabilitiesService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/homeserver/DefaultHomeServerCapabilitiesService.kt @@ -16,18 +16,12 @@ package org.matrix.android.sdk.internal.session.homeserver -import com.zhuinden.monarchy.Monarchy -import io.realm.Realm import org.matrix.android.sdk.api.session.homeserver.HomeServerCapabilities import org.matrix.android.sdk.api.session.homeserver.HomeServerCapabilitiesService -import org.matrix.android.sdk.internal.database.mapper.HomeServerCapabilitiesMapper -import org.matrix.android.sdk.internal.database.model.HomeServerCapabilitiesEntity -import org.matrix.android.sdk.internal.database.query.get -import org.matrix.android.sdk.internal.di.SessionDatabase import javax.inject.Inject internal class DefaultHomeServerCapabilitiesService @Inject constructor( - @SessionDatabase private val monarchy: Monarchy, + private val homeServerCapabilitiesDataSource: HomeServerCapabilitiesDataSource, private val getHomeServerCapabilitiesTask: GetHomeServerCapabilitiesTask ) : HomeServerCapabilitiesService { @@ -36,11 +30,7 @@ internal class DefaultHomeServerCapabilitiesService @Inject constructor( } override fun getHomeServerCapabilities(): HomeServerCapabilities { - return Realm.getInstance(monarchy.realmConfiguration).use { realm -> - HomeServerCapabilitiesEntity.get(realm)?.let { - HomeServerCapabilitiesMapper.map(it) - } - } + return homeServerCapabilitiesDataSource.getHomeServerCapabilities() ?: HomeServerCapabilities() } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/homeserver/GetCapabilitiesResult.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/homeserver/GetCapabilitiesResult.kt index ab029a0fce013e4a8a0eb8de1516766c6bb6e07f..1fe4f9d90a6f76c835fe5d13f9635af634163129 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/homeserver/GetCapabilitiesResult.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/homeserver/GetCapabilitiesResult.kt @@ -19,6 +19,7 @@ package org.matrix.android.sdk.internal.session.homeserver import com.squareup.moshi.Json import com.squareup.moshi.JsonClass import org.matrix.android.sdk.api.extensions.orTrue +import org.matrix.android.sdk.api.util.JsonDict /** * Ref: https://matrix.org/docs/spec/client_server/latest#get-matrix-client-r0-capabilities @@ -38,9 +39,14 @@ internal data class Capabilities( * Capability to indicate if the user can change their password. */ @Json(name = "m.change_password") - val changePassword: ChangePassword? = null + val changePassword: ChangePassword? = null, - // No need for m.room_versions for the moment + /** + * This capability describes the default and available room versions a server supports, and at what level of stability. + * Clients should make use of this capability to determine if users need to be encouraged to upgrade their rooms. + */ + @Json(name = "m.room_versions") + val roomVersions: RoomVersions? = null ) @JsonClass(generateAdapter = true) @@ -52,6 +58,36 @@ internal data class ChangePassword( val enabled: Boolean? ) +@JsonClass(generateAdapter = true) +internal data class RoomVersions( + /** + * Required. The default room version the server is using for new rooms. + */ + @Json(name = "default") + val default: String?, + + /** + * Required. A detailed description of the room versions the server supports. + */ + @Json(name = "available") + val available: JsonDict? = null, + + /** + * "room_capabilities": { + * "knock" : { + * "preferred": "7", + * "support" : ["7"] + * }, + * "restricted" : { + * "preferred": "9", + * "support" : ["8", "9"] + * } + * } + */ + @Json(name = "room_capabilities") + val roomCapabilities: JsonDict? = null +) + // The spec says: If not present, the client should assume that password changes are possible via the API internal fun GetCapabilitiesResult.canChangePassword(): Boolean { return capabilities?.changePassword?.enabled.orTrue() diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/homeserver/GetHomeServerCapabilitiesTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/homeserver/GetHomeServerCapabilitiesTask.kt index 740370123f877d92340e322b6a50c83eb4dd0803..612b98f8638d50cda4273328fea4d21a07061e17 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/homeserver/GetHomeServerCapabilitiesTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/homeserver/GetHomeServerCapabilitiesTask.kt @@ -17,6 +17,7 @@ package org.matrix.android.sdk.internal.session.homeserver import com.zhuinden.monarchy.Monarchy +import org.matrix.android.sdk.api.MatrixPatterns.getDomain import org.matrix.android.sdk.api.auth.data.HomeServerConnectionConfig import org.matrix.android.sdk.api.auth.wellknown.WellknownResult import org.matrix.android.sdk.api.session.homeserver.HomeServerCapabilities @@ -24,6 +25,7 @@ import org.matrix.android.sdk.internal.auth.version.Versions import org.matrix.android.sdk.internal.auth.version.isLoginAndRegistrationSupportedBySdk import org.matrix.android.sdk.internal.database.model.HomeServerCapabilitiesEntity import org.matrix.android.sdk.internal.database.query.getOrCreate +import org.matrix.android.sdk.internal.di.MoshiProvider import org.matrix.android.sdk.internal.di.SessionDatabase import org.matrix.android.sdk.internal.di.UserId import org.matrix.android.sdk.internal.network.GlobalErrorReceiver @@ -89,7 +91,10 @@ internal class DefaultGetHomeServerCapabilitiesTask @Inject constructor( }.getOrNull() val wellknownResult = runCatching { - getWellknownTask.execute(GetWellknownTask.Params(userId, homeServerConnectionConfig)) + getWellknownTask.execute(GetWellknownTask.Params( + domain = userId.getDomain(), + homeServerConnectionConfig = homeServerConnectionConfig + )) }.getOrNull() insertInDb(capabilities, mediaConfig, versions, wellknownResult) @@ -104,6 +109,10 @@ internal class DefaultGetHomeServerCapabilitiesTask @Inject constructor( if (getCapabilitiesResult != null) { homeServerCapabilitiesEntity.canChangePassword = getCapabilitiesResult.canChangePassword() + + homeServerCapabilitiesEntity.roomVersionsJson = getCapabilitiesResult.capabilities?.roomVersions?.let { + MoshiProvider.providesMoshi().adapter(RoomVersions::class.java).toJson(it) + } } if (getMediaConfigResult != null) { diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/homeserver/HomeServerCapabilitiesDataSource.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/homeserver/HomeServerCapabilitiesDataSource.kt new file mode 100644 index 0000000000000000000000000000000000000000..6c913fa41ee6b16feb8e9e0da0ed73f294cb422b --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/homeserver/HomeServerCapabilitiesDataSource.kt @@ -0,0 +1,38 @@ +/* + * Copyright (c) 2021 The Matrix.org Foundation C.I.C. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.matrix.android.sdk.internal.session.homeserver + +import com.zhuinden.monarchy.Monarchy +import io.realm.Realm +import org.matrix.android.sdk.api.session.homeserver.HomeServerCapabilities +import org.matrix.android.sdk.internal.database.mapper.HomeServerCapabilitiesMapper +import org.matrix.android.sdk.internal.database.model.HomeServerCapabilitiesEntity +import org.matrix.android.sdk.internal.database.query.get +import org.matrix.android.sdk.internal.di.SessionDatabase +import javax.inject.Inject + +internal class HomeServerCapabilitiesDataSource @Inject constructor( + @SessionDatabase private val monarchy: Monarchy +) { + fun getHomeServerCapabilities(): HomeServerCapabilities? { + return Realm.getInstance(monarchy.realmConfiguration).use { realm -> + HomeServerCapabilitiesEntity.get(realm)?.let { + HomeServerCapabilitiesMapper.map(it) + } + } + } +} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/identity/DefaultIdentityService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/identity/DefaultIdentityService.kt index 4f88d8eb9507b1ca1eccd9cea549a90d74f8efe6..48870b86b79cb46bb968ee3c7af36e8871a633bc 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/identity/DefaultIdentityService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/identity/DefaultIdentityService.kt @@ -191,7 +191,7 @@ internal class DefaultIdentityService @Inject constructor( } else { // Disconnect previous one if any, first, because the token will change. // In case of error when configuring the new identity server, this is not a big deal, - // we will ask for a new token on the previous Identity server + // we will ask for a new token on the previous identity server runCatching { identityDisconnectTask.execute(Unit) } .onFailure { Timber.w(it, "Unable to disconnect identity server") } @@ -241,7 +241,7 @@ internal class DefaultIdentityService @Inject constructor( override suspend fun getShareStatus(threePids: List<ThreePid>): Map<ThreePid, SharedState> { // Note: we do not require user consent here, because it is used for emails and phone numbers that the user has already sent - // to the home server, and not emails and phone numbers from the contact book of the user + // to the homeserver, and not emails and phone numbers from the contact book of the user if (threePids.isEmpty()) { return emptyMap() diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/identity/IdentityAuthAPI.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/identity/IdentityAuthAPI.kt index 9d990d4d8f9340a91bbb07dd8a792c8eb216a4ef..f77eb296aa9e48438206662fe1dee09d85718e77 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/identity/IdentityAuthAPI.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/identity/IdentityAuthAPI.kt @@ -42,7 +42,7 @@ internal interface IdentityAuthAPI { suspend fun ping() /** - * Ping v1 will be used to check outdated Identity server + * Ping v1 will be used to check outdated identity server */ @GET("_matrix/identity/api/v1") suspend fun pingV1() 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 7a39a333a59bd7b039ddf51915c807d5993f12e8..4d664b76bed8578403731bd209a13f41ee1f3946 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,7 +60,6 @@ internal abstract class IdentityModule { @SessionScope fun providesIdentityRealmConfiguration(realmKeysUtils: RealmKeysUtils, @SessionFilesDirectory directory: File, - migration: RealmIdentityStoreMigration, @UserMd5 userMd5: String): RealmConfiguration { return RealmConfiguration.Builder() .directory(directory) @@ -69,7 +68,7 @@ internal abstract class IdentityModule { realmKeysUtils.configureEncryption(this, SessionModule.getKeyAlias(userMd5)) } .schemaVersion(RealmIdentityStoreMigration.IDENTITY_STORE_SCHEMA_VERSION) - .migration(migration) + .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 6081dbab12e5a59fa77e667dec09f35c2cca1770..21c0f8eb9e6cf64c8bb7af563ad7bced77157d67 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 @@ -19,13 +19,10 @@ package org.matrix.android.sdk.internal.session.identity.db import io.realm.DynamicRealm import io.realm.RealmMigration import timber.log.Timber -import javax.inject.Inject -internal class RealmIdentityStoreMigration @Inject constructor() : RealmMigration { +internal object RealmIdentityStoreMigration : RealmMigration { - companion object { - const val IDENTITY_STORE_SCHEMA_VERSION = 1L - } + const val IDENTITY_STORE_SCHEMA_VERSION = 1L override fun migrate(realm: DynamicRealm, oldVersion: Long, newVersion: Long) { Timber.v("Migrating Realm Identity from $oldVersion to $newVersion") diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/integrationmanager/IntegrationManager.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/integrationmanager/IntegrationManager.kt index f79f8084a8e501d0f4a8bb1908d481ef18a9896e..b654b8610def5f3cce043fd7f4002e32fc6d4f82 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/integrationmanager/IntegrationManager.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/integrationmanager/IntegrationManager.kt @@ -33,7 +33,7 @@ import org.matrix.android.sdk.internal.extensions.observeNotNull import org.matrix.android.sdk.api.session.SessionLifecycleObserver import org.matrix.android.sdk.internal.session.SessionScope import org.matrix.android.sdk.api.session.accountdata.UserAccountDataTypes -import org.matrix.android.sdk.api.session.accountdata.AccountDataEvent +import org.matrix.android.sdk.api.session.accountdata.UserAccountDataEvent import org.matrix.android.sdk.internal.session.user.accountdata.UserAccountDataDataSource import org.matrix.android.sdk.internal.session.user.accountdata.UpdateUserAccountDataTask import org.matrix.android.sdk.internal.session.widgets.helper.WidgetFactory @@ -43,8 +43,8 @@ import javax.inject.Inject /** * The integration manager allows to - * - Get the Integration Manager that a user has explicitly set for its account (via account data) - * - Get the recommended/preferred Integration Manager list as defined by the HomeServer (via wellknown) + * - Get the integration manager that a user has explicitly set for its account (via account data) + * - Get the recommended/preferred integration manager list as defined by the homeserver (via wellknown) * - Check if the user has disabled the integration manager feature * - Allow / Disallow Integration manager (propagated to other riot clients) * @@ -240,7 +240,7 @@ internal class IntegrationManager @Inject constructor(matrixConfiguration: Matri ) } - private fun AccountDataEvent.asIntegrationManagerWidgetContent(): WidgetContent? { + private fun UserAccountDataEvent.asIntegrationManagerWidgetContent(): WidgetContent? { return extractWidgetSequence(widgetFactory) .filter { WidgetType.IntegrationManager == it.type diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/notification/DefaultPushRuleService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/notification/DefaultPushRuleService.kt index 38f6b08b43b39d9eba976a7820f0faaeb0f6652a..4e8abcf7840c38f5e314ab7770ae0ce42f6f508b 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/notification/DefaultPushRuleService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/notification/DefaultPushRuleService.kt @@ -113,8 +113,8 @@ internal class DefaultPushRuleService @Inject constructor( addPushRuleTask.execute(AddPushRuleTask.Params(kind, pushRule)) } - override suspend fun updatePushRuleActions(kind: RuleKind, oldPushRule: PushRule, newPushRule: PushRule) { - updatePushRuleActionsTask.execute(UpdatePushRuleActionsTask.Params(kind, oldPushRule, newPushRule)) + override suspend fun updatePushRuleActions(kind: RuleKind, ruleId: String, enable: Boolean, actions: List<Action>?) { + updatePushRuleActionsTask.execute(UpdatePushRuleActionsTask.Params(kind, ruleId, enable, actions)) } override suspend fun removePushRule(kind: RuleKind, pushRule: PushRule) { diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/permalinks/ViaParameterFinder.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/permalinks/ViaParameterFinder.kt index 72fbfcced5ee6f6dbf608d623bebb8f238380a32..21f55bbc42535b6da82316844e01155d36512504 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/permalinks/ViaParameterFinder.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/permalinks/ViaParameterFinder.kt @@ -16,17 +16,25 @@ package org.matrix.android.sdk.internal.session.permalinks +import org.matrix.android.sdk.api.MatrixPatterns.getDomain +import org.matrix.android.sdk.api.query.QueryStringValue +import org.matrix.android.sdk.api.session.events.model.EventType +import org.matrix.android.sdk.api.session.events.model.toModel import org.matrix.android.sdk.api.session.room.members.roomMemberQueryParams import org.matrix.android.sdk.api.session.room.model.Membership +import org.matrix.android.sdk.api.session.room.model.PowerLevelsContent +import org.matrix.android.sdk.api.session.room.powerlevels.PowerLevelsHelper import org.matrix.android.sdk.internal.di.UserId import org.matrix.android.sdk.internal.session.room.RoomGetter +import org.matrix.android.sdk.internal.session.room.state.StateEventDataSource import java.net.URLEncoder import javax.inject.Inject import javax.inject.Provider internal class ViaParameterFinder @Inject constructor( @UserId private val userId: String, - private val roomGetterProvider: Provider<RoomGetter> + private val roomGetterProvider: Provider<RoomGetter>, + private val stateEventDataSource: StateEventDataSource ) { fun computeViaParams(roomId: String, max: Int): List<String> { @@ -47,9 +55,9 @@ internal class ViaParameterFinder @Inject constructor( } fun computeViaParams(userId: String, roomId: String, max: Int): List<String> { - val userHomeserver = userId.substringAfter(":") + val userHomeserver = userId.getDomain() return getUserIdsOfJoinedMembers(roomId) - .map { it.substringAfter(":") } + .map { it.getDomain() } .groupBy { it } .mapValues { it.value.size } .toMutableMap() @@ -69,4 +77,28 @@ internal class ViaParameterFinder @Inject constructor( .orEmpty() .toSet() } + + fun computeViaParamsForRestricted(roomId: String, max: Int): List<String> { + val userThatCanInvite = roomGetterProvider.get().getRoom(roomId) + ?.getRoomMembers(roomMemberQueryParams { memberships = listOf(Membership.JOIN) }) + ?.map { it.userId } + ?.filter { userCanInvite(userId, roomId) } + .orEmpty() + .toSet() + + return userThatCanInvite.map { it.getDomain() } + .groupBy { it } + .mapValues { it.value.size } + .toMutableMap() + .let { map -> map.keys.sortedByDescending { map[it] } } + .take(max) + } + + fun userCanInvite(userId: String, roomId: String): Boolean { + val powerLevelsHelper = stateEventDataSource.getStateEvent(roomId, EventType.STATE_ROOM_POWER_LEVELS, QueryStringValue.NoCondition) + ?.content?.toModel<PowerLevelsContent>() + ?.let { PowerLevelsHelper(it) } + + return powerLevelsHelper?.isUserAbleToInvite(userId) ?: false + } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/profile/ProfileAPI.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/profile/ProfileAPI.kt index 485a4973cab0f838e566ff57f3ee4092bd0ad769..4b56db9f1330cf999d5bcf6715c5bcbb1820d268 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/profile/ProfileAPI.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/profile/ProfileAPI.kt @@ -86,7 +86,7 @@ internal interface ProfileAPI { suspend fun addMsisdn(@Body body: AddMsisdnBody): AddMsisdnResponse /** - * Validate Msisdn code (same model than for Identity server API) + * Validate Msisdn code (same model than for identity server API) */ @POST suspend fun validateMsisdn(@Url url: String, diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/UpdatePushRuleActionsTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/UpdatePushRuleActionsTask.kt index 2a24aee8929111559584efbf6fda0e3d97deeca3..b8dbabd09ebcd6eb1cfa5511164499546b2f7906 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/UpdatePushRuleActionsTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/UpdatePushRuleActionsTask.kt @@ -15,8 +15,9 @@ */ package org.matrix.android.sdk.internal.session.pushers +import org.matrix.android.sdk.api.pushrules.Action import org.matrix.android.sdk.api.pushrules.RuleKind -import org.matrix.android.sdk.api.pushrules.rest.PushRule +import org.matrix.android.sdk.api.pushrules.toJson import org.matrix.android.sdk.internal.network.GlobalErrorReceiver import org.matrix.android.sdk.internal.network.executeRequest import org.matrix.android.sdk.internal.task.Task @@ -25,8 +26,9 @@ import javax.inject.Inject internal interface UpdatePushRuleActionsTask : Task<UpdatePushRuleActionsTask.Params, Unit> { data class Params( val kind: RuleKind, - val oldPushRule: PushRule, - val newPushRule: PushRule + val ruleId: String, + val enable: Boolean, + val actions: List<Action>? ) } @@ -36,20 +38,14 @@ internal class DefaultUpdatePushRuleActionsTask @Inject constructor( ) : UpdatePushRuleActionsTask { override suspend fun execute(params: UpdatePushRuleActionsTask.Params) { - if (params.oldPushRule.enabled != params.newPushRule.enabled) { - // First change enabled state executeRequest(globalErrorReceiver) { - pushRulesApi.updateEnableRuleStatus(params.kind.value, params.newPushRule.ruleId, params.newPushRule.enabled) + pushRulesApi.updateEnableRuleStatus(params.kind.value, params.ruleId, enable = params.enable) } - } - - if (params.newPushRule.enabled) { - // Also ensure the actions are up to date - val body = mapOf("actions" to params.newPushRule.actions) - - executeRequest(globalErrorReceiver) { - pushRulesApi.updateRuleActions(params.kind.value, params.newPushRule.ruleId, body) + if (params.actions != null) { + val body = mapOf("actions" to params.actions.toJson()) + executeRequest(globalErrorReceiver) { + pushRulesApi.updateRuleActions(params.kind.value, params.ruleId, body) + } } - } } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/DefaultRoom.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/DefaultRoom.kt index 5a2eef7e8a3dbe5fa23a93cfb9bea6a29026f95d..8afd690f642d82fcf62dcf175dfdd11f90b57d15 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/DefaultRoom.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/DefaultRoom.kt @@ -17,10 +17,10 @@ package org.matrix.android.sdk.internal.session.room import androidx.lifecycle.LiveData -import org.matrix.android.sdk.api.session.accountdata.AccountDataService import org.matrix.android.sdk.api.session.crypto.CryptoService import org.matrix.android.sdk.api.session.events.model.EventType import org.matrix.android.sdk.api.session.room.Room +import org.matrix.android.sdk.api.session.room.accountdata.RoomAccountDataService import org.matrix.android.sdk.api.session.room.alias.AliasService import org.matrix.android.sdk.api.session.room.call.RoomCallService import org.matrix.android.sdk.api.session.room.members.MembershipService @@ -37,12 +37,12 @@ import org.matrix.android.sdk.api.session.room.tags.TagsService import org.matrix.android.sdk.api.session.room.timeline.TimelineService import org.matrix.android.sdk.api.session.room.typing.TypingService import org.matrix.android.sdk.api.session.room.uploads.UploadsService +import org.matrix.android.sdk.api.session.room.version.RoomVersionService import org.matrix.android.sdk.api.session.search.SearchResult import org.matrix.android.sdk.api.session.space.Space import org.matrix.android.sdk.api.util.Optional import org.matrix.android.sdk.internal.crypto.MXCRYPTO_ALGORITHM_MEGOLM import org.matrix.android.sdk.internal.session.permalinks.ViaParameterFinder -import org.matrix.android.sdk.internal.session.room.accountdata.RoomAccountDataService import org.matrix.android.sdk.internal.session.room.state.SendStateTask import org.matrix.android.sdk.internal.session.room.summary.RoomSummaryDataSource import org.matrix.android.sdk.internal.session.search.SearchTask @@ -68,9 +68,11 @@ internal class DefaultRoom(override val roomId: String, private val roomMembersService: MembershipService, private val roomPushRuleService: RoomPushRuleService, private val roomAccountDataService: RoomAccountDataService, + private val roomVersionService: RoomVersionService, private val sendStateTask: SendStateTask, private val viaParameterFinder: ViaParameterFinder, - private val searchTask: SearchTask) : + private val searchTask: SearchTask +) : Room, TimelineService by timelineService, SendService by sendService, @@ -86,7 +88,8 @@ internal class DefaultRoom(override val roomId: String, RelationService by relationService, MembershipService by roomMembersService, RoomPushRuleService by roomPushRuleService, - AccountDataService by roomAccountDataService { + RoomAccountDataService by roomAccountDataService, + RoomVersionService by roomVersionService { override fun getRoomSummaryLive(): LiveData<Optional<RoomSummary>> { return roomSummaryDataSource.getRoomSummaryLive(roomId) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/DefaultRoomDirectoryService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/DefaultRoomDirectoryService.kt index 218d846afb5cf3e9452826f7512c02177093eec7..7330c91c202d0f088f87b7bbcaf0217e98369292 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/DefaultRoomDirectoryService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/DefaultRoomDirectoryService.kt @@ -16,10 +16,13 @@ package org.matrix.android.sdk.internal.session.room +import org.matrix.android.sdk.api.session.room.AliasAvailabilityResult import org.matrix.android.sdk.api.session.room.RoomDirectoryService +import org.matrix.android.sdk.api.session.room.alias.RoomAliasError import org.matrix.android.sdk.api.session.room.model.RoomDirectoryVisibility import org.matrix.android.sdk.api.session.room.model.roomdirectory.PublicRoomsParams import org.matrix.android.sdk.api.session.room.model.roomdirectory.PublicRoomsResponse +import org.matrix.android.sdk.internal.session.room.alias.RoomAliasAvailabilityChecker import org.matrix.android.sdk.internal.session.room.directory.GetPublicRoomTask import org.matrix.android.sdk.internal.session.room.directory.GetRoomDirectoryVisibilityTask import org.matrix.android.sdk.internal.session.room.directory.SetRoomDirectoryVisibilityTask @@ -28,7 +31,8 @@ import javax.inject.Inject internal class DefaultRoomDirectoryService @Inject constructor( private val getPublicRoomTask: GetPublicRoomTask, private val getRoomDirectoryVisibilityTask: GetRoomDirectoryVisibilityTask, - private val setRoomDirectoryVisibilityTask: SetRoomDirectoryVisibilityTask + private val setRoomDirectoryVisibilityTask: SetRoomDirectoryVisibilityTask, + private val roomAliasAvailabilityChecker: RoomAliasAvailabilityChecker ) : RoomDirectoryService { override suspend fun getPublicRooms(server: String?, @@ -43,4 +47,13 @@ internal class DefaultRoomDirectoryService @Inject constructor( override suspend fun setRoomDirectoryVisibility(roomId: String, roomDirectoryVisibility: RoomDirectoryVisibility) { setRoomDirectoryVisibilityTask.execute(SetRoomDirectoryVisibilityTask.Params(roomId, roomDirectoryVisibility)) } + + override suspend fun checkAliasAvailability(aliasLocalPart: String?): AliasAvailabilityResult { + return try { + roomAliasAvailabilityChecker.check(aliasLocalPart) + AliasAvailabilityResult.Available + } catch (failure: RoomAliasError) { + AliasAvailabilityResult.NotAvailable(failure) + } + } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/DefaultRoomService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/DefaultRoomService.kt index d9fe1288e29d0a538321ed390d9125773d3945e2..632ea4c45042ec2fa8e0b3c2c72c784ba8aa5772 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/DefaultRoomService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/DefaultRoomService.kt @@ -134,6 +134,10 @@ internal class DefaultRoomService @Inject constructor( deleteRoomAliasTask.execute(DeleteRoomAliasTask.Params(roomAlias)) } + override fun getChangeMemberships(roomIdOrAlias: String): ChangeMembershipState { + return roomChangeMembershipStateDataSource.getState(roomIdOrAlias) + } + override fun getChangeMembershipsLive(): LiveData<Map<String, ChangeMembershipState>> { return roomChangeMembershipStateDataSource.getLiveStates() } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/RoomAPI.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/RoomAPI.kt index 4f1260403938071011519252c458d94d53a2323e..18ece6062913c63e7edae097b36f1d55cd064539 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/RoomAPI.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/RoomAPI.kt @@ -369,4 +369,15 @@ internal interface RoomAPI { @Path("roomId") roomId: String, @Path("type") type: String, @Body content: JsonDict) + + /** + * Upgrades the given room to a particular room version. + * Errors: + * 400, The request was invalid. One way this can happen is if the room version requested is not supported by the homeserver + * (M_UNSUPPORTED_ROOM_VERSION) + * 403: The user is not permitted to upgrade the room.(M_FORBIDDEN) + */ + @POST(NetworkConstants.URI_API_PREFIX_PATH_R0 + "rooms/{roomId}/upgrade") + suspend fun upgradeRoom(@Path("roomId") roomId: String, + @Body body: RoomUpgradeBody): RoomUpgradeResponse } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/RoomFactory.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/RoomFactory.kt index 8efbf2360a27ad1bfed07660816ccffcb1d8c60a..d44eb32529b3954fb8d66938aaf15299550a30f2 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/RoomFactory.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/RoomFactory.kt @@ -19,8 +19,8 @@ package org.matrix.android.sdk.internal.session.room import org.matrix.android.sdk.api.session.crypto.CryptoService import org.matrix.android.sdk.api.session.room.Room import org.matrix.android.sdk.internal.session.SessionScope -import org.matrix.android.sdk.internal.session.room.accountdata.RoomAccountDataService import org.matrix.android.sdk.internal.session.permalinks.ViaParameterFinder +import org.matrix.android.sdk.internal.session.room.accountdata.DefaultRoomAccountDataService import org.matrix.android.sdk.internal.session.room.alias.DefaultAliasService import org.matrix.android.sdk.internal.session.room.call.DefaultRoomCallService import org.matrix.android.sdk.internal.session.room.draft.DefaultDraftService @@ -37,6 +37,7 @@ import org.matrix.android.sdk.internal.session.room.tags.DefaultTagsService import org.matrix.android.sdk.internal.session.room.timeline.DefaultTimelineService import org.matrix.android.sdk.internal.session.room.typing.DefaultTypingService import org.matrix.android.sdk.internal.session.room.uploads.DefaultUploadsService +import org.matrix.android.sdk.internal.session.room.version.DefaultRoomVersionService import org.matrix.android.sdk.internal.session.search.SearchTask import javax.inject.Inject @@ -61,7 +62,8 @@ internal class DefaultRoomFactory @Inject constructor(private val cryptoService: private val relationServiceFactory: DefaultRelationService.Factory, private val membershipServiceFactory: DefaultMembershipService.Factory, private val roomPushRuleServiceFactory: DefaultRoomPushRuleService.Factory, - private val roomAccountDataServiceFactory: RoomAccountDataService.Factory, + private val roomVersionServiceFactory: DefaultRoomVersionService.Factory, + private val roomAccountDataServiceFactory: DefaultRoomAccountDataService.Factory, private val sendStateTask: SendStateTask, private val viaParameterFinder: ViaParameterFinder, private val searchTask: SearchTask) : @@ -87,6 +89,7 @@ internal class DefaultRoomFactory @Inject constructor(private val cryptoService: roomMembersService = membershipServiceFactory.create(roomId), roomPushRuleService = roomPushRuleServiceFactory.create(roomId), roomAccountDataService = roomAccountDataServiceFactory.create(roomId), + roomVersionService = roomVersionServiceFactory.create(roomId), sendStateTask = sendStateTask, searchTask = searchTask, viaParameterFinder = viaParameterFinder diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/RoomModule.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/RoomModule.kt index d88c1950560d3928a682b70f8694d165fcd4eefc..c04c899e18f2c01cbb4be11d387ac4419c0e3fb1 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/RoomModule.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/RoomModule.kt @@ -92,6 +92,8 @@ import org.matrix.android.sdk.internal.session.room.typing.DefaultSendTypingTask import org.matrix.android.sdk.internal.session.room.typing.SendTypingTask import org.matrix.android.sdk.internal.session.room.uploads.DefaultGetUploadsTask import org.matrix.android.sdk.internal.session.room.uploads.GetUploadsTask +import org.matrix.android.sdk.internal.session.room.version.DefaultRoomVersionUpgradeTask +import org.matrix.android.sdk.internal.session.room.version.RoomVersionUpgradeTask import org.matrix.android.sdk.internal.session.space.DefaultSpaceService import retrofit2.Retrofit @@ -243,4 +245,7 @@ internal abstract class RoomModule { @Binds abstract fun bindGetEventTask(task: DefaultGetEventTask): GetEventTask + + @Binds + abstract fun bindRoomVersionUpgradeTask(task: DefaultRoomVersionUpgradeTask): RoomVersionUpgradeTask } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/RoomUpgradeBody.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/RoomUpgradeBody.kt new file mode 100644 index 0000000000000000000000000000000000000000..4629f6e409751a6108e93b172480eeb251d8d030 --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/RoomUpgradeBody.kt @@ -0,0 +1,26 @@ +/* + * Copyright 2021 The Matrix.org Foundation C.I.C. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.matrix.android.sdk.internal.session.room + +import com.squareup.moshi.Json +import com.squareup.moshi.JsonClass + +@JsonClass(generateAdapter = true) +internal data class RoomUpgradeBody( + @Json(name = "new_version") + val newVersion: String +) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/RoomUpgradeResponse.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/RoomUpgradeResponse.kt new file mode 100644 index 0000000000000000000000000000000000000000..1cca2c572bb794ea69fe45063ccc3d0365a49d3c --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/RoomUpgradeResponse.kt @@ -0,0 +1,26 @@ +/* + * Copyright 2021 The Matrix.org Foundation C.I.C. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.matrix.android.sdk.internal.session.room + +import com.squareup.moshi.Json +import com.squareup.moshi.JsonClass + +@JsonClass(generateAdapter = true) +internal data class RoomUpgradeResponse( + @Json(name = "replacement_room") + val replacementRoomId: String +) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/accountdata/RoomAccountDataService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/accountdata/DefaultRoomAccountDataService.kt similarity index 72% rename from matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/accountdata/RoomAccountDataService.kt rename to matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/accountdata/DefaultRoomAccountDataService.kt index 9e9e9dc322a4534c6d2b651f1f36a6f303a84a54..caeeb3bf5375aeea24618a76028edbe7a61a1d23 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/accountdata/RoomAccountDataService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/accountdata/DefaultRoomAccountDataService.kt @@ -20,34 +20,34 @@ import androidx.lifecycle.LiveData import dagger.assisted.Assisted import dagger.assisted.AssistedFactory import dagger.assisted.AssistedInject -import org.matrix.android.sdk.api.session.accountdata.AccountDataEvent -import org.matrix.android.sdk.api.session.accountdata.AccountDataService import org.matrix.android.sdk.api.session.events.model.Content +import org.matrix.android.sdk.api.session.room.accountdata.RoomAccountDataEvent +import org.matrix.android.sdk.api.session.room.accountdata.RoomAccountDataService import org.matrix.android.sdk.api.util.Optional -internal class RoomAccountDataService @AssistedInject constructor(@Assisted private val roomId: String, - private val dataSource: RoomAccountDataDataSource, - private val updateRoomAccountDataTask: UpdateRoomAccountDataTask -) : AccountDataService { +internal class DefaultRoomAccountDataService @AssistedInject constructor(@Assisted private val roomId: String, + private val dataSource: RoomAccountDataDataSource, + private val updateRoomAccountDataTask: UpdateRoomAccountDataTask +) : RoomAccountDataService { @AssistedFactory interface Factory { - fun create(roomId: String): RoomAccountDataService + fun create(roomId: String): DefaultRoomAccountDataService } - override fun getAccountDataEvent(type: String): AccountDataEvent? { + override fun getAccountDataEvent(type: String): RoomAccountDataEvent? { return dataSource.getAccountDataEvent(roomId, type) } - override fun getLiveAccountDataEvent(type: String): LiveData<Optional<AccountDataEvent>> { + override fun getLiveAccountDataEvent(type: String): LiveData<Optional<RoomAccountDataEvent>> { return dataSource.getLiveAccountDataEvent(roomId, type) } - override fun getAccountDataEvents(types: Set<String>): List<AccountDataEvent> { + override fun getAccountDataEvents(types: Set<String>): List<RoomAccountDataEvent> { return dataSource.getAccountDataEvents(roomId, types) } - override fun getLiveAccountDataEvents(types: Set<String>): LiveData<List<AccountDataEvent>> { + override fun getLiveAccountDataEvents(types: Set<String>): LiveData<List<RoomAccountDataEvent>> { return dataSource.getLiveAccountDataEvents(roomId, types) } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/accountdata/RoomAccountDataDataSource.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/accountdata/RoomAccountDataDataSource.kt index 0bcf9d7f386088e3c0624cfc3d712e4b7cbdc3a0..0e4493846c17072ce5b90def8405c81148364df5 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/accountdata/RoomAccountDataDataSource.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/accountdata/RoomAccountDataDataSource.kt @@ -20,14 +20,16 @@ import androidx.lifecycle.LiveData import androidx.lifecycle.MediatorLiveData import androidx.lifecycle.Transformations import com.zhuinden.monarchy.Monarchy -import org.matrix.android.sdk.api.session.accountdata.AccountDataEvent +import io.realm.Realm +import io.realm.RealmQuery +import org.matrix.android.sdk.api.session.room.accountdata.RoomAccountDataEvent import org.matrix.android.sdk.api.util.Optional import org.matrix.android.sdk.api.util.toOptional import org.matrix.android.sdk.internal.database.RealmSessionProvider import org.matrix.android.sdk.internal.database.mapper.AccountDataMapper import org.matrix.android.sdk.internal.database.model.RoomAccountDataEntityFields import org.matrix.android.sdk.internal.database.model.RoomEntity -import org.matrix.android.sdk.internal.database.query.where +import org.matrix.android.sdk.internal.database.model.RoomEntityFields import org.matrix.android.sdk.internal.di.SessionDatabase import javax.inject.Inject @@ -35,43 +37,62 @@ internal class RoomAccountDataDataSource @Inject constructor(@SessionDatabase pr private val realmSessionProvider: RealmSessionProvider, private val accountDataMapper: AccountDataMapper) { - fun getAccountDataEvent(roomId: String, type: String): AccountDataEvent? { + fun getAccountDataEvent(roomId: String, type: String): RoomAccountDataEvent? { return getAccountDataEvents(roomId, setOf(type)).firstOrNull() } - fun getLiveAccountDataEvent(roomId: String, type: String): LiveData<Optional<AccountDataEvent>> { + fun getLiveAccountDataEvent(roomId: String, type: String): LiveData<Optional<RoomAccountDataEvent>> { return Transformations.map(getLiveAccountDataEvents(roomId, setOf(type))) { it.firstOrNull()?.toOptional() } } - fun getAccountDataEvents(roomId: String, types: Set<String>): List<AccountDataEvent> { + /** + * @param roomId the roomId to search for account data event. If null will check in every room. + * @param types the types to filter. If empty will return all account data event in given room (or every room if roomId is null) + * + */ + fun getAccountDataEvents(roomId: String?, types: Set<String>): List<RoomAccountDataEvent> { return realmSessionProvider.withRealm { realm -> - val roomEntity = RoomEntity.where(realm, roomId).findFirst() ?: return@withRealm emptyList() + val roomEntity = buildRoomQuery(realm, roomId, types).findFirst() ?: return@withRealm emptyList() roomEntity.accountDataEvents(types) } } - fun getLiveAccountDataEvents(roomId: String, types: Set<String>): LiveData<List<AccountDataEvent>> { - val liveRoomEntity = monarchy.findAllManagedWithChanges { RoomEntity.where(it, roomId) } - val resultLiveData = MediatorLiveData<List<AccountDataEvent>>() - resultLiveData.addSource(liveRoomEntity) { - val roomEntity = it.realmResults.firstOrNull() - if (roomEntity == null) { - resultLiveData.postValue(emptyList()) - } else { - val mappedResult = roomEntity.accountDataEvents(types) - resultLiveData.postValue(mappedResult) - } + /** + * @param roomId the roomId to search for account data event. If null will check in every room. + * @param types the types to filter. If empty will return all account data event in the given room (or every room if roomId is null). + * + */ + fun getLiveAccountDataEvents(roomId: String?, types: Set<String>): LiveData<List<RoomAccountDataEvent>> { + val liveRoomEntity = monarchy.findAllManagedWithChanges { + buildRoomQuery(it, roomId, types) + } + val resultLiveData = MediatorLiveData<List<RoomAccountDataEvent>>() + resultLiveData.addSource(liveRoomEntity) { changeSet -> + val mappedResult = changeSet.realmResults.flatMap { it.accountDataEvents(types) } + resultLiveData.postValue(mappedResult) } return resultLiveData } - private fun RoomEntity.accountDataEvents(types: Set<String>): List<AccountDataEvent> { + private fun buildRoomQuery(realm: Realm, roomId: String?, types: Set<String>): RealmQuery<RoomEntity> { + val query = realm.where(RoomEntity::class.java) + if (roomId != null) { + query.equalTo(RoomEntityFields.ROOM_ID, roomId) + } + query.isNotEmpty(RoomEntityFields.ACCOUNT_DATA.`$`) + if (types.isNotEmpty()) { + query.`in`(RoomEntityFields.ACCOUNT_DATA.TYPE, types.toTypedArray()) + } + return query + } + + private fun RoomEntity.accountDataEvents(types: Set<String>): List<RoomAccountDataEvent> { val query = accountData.where() if (types.isNotEmpty()) { query.`in`(RoomAccountDataEntityFields.TYPE, types.toTypedArray()) } - return query.findAll().map { accountDataMapper.map(it) } + return query.findAll().map { accountDataMapper.map(roomId, it) } } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/alias/RoomAliasAvailabilityChecker.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/alias/RoomAliasAvailabilityChecker.kt index b39cbaa5825c69ecfda9656115e8827542626fa8..7c137a810275a1146ecb6ea5fa6e6a3531ed1713 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/alias/RoomAliasAvailabilityChecker.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/alias/RoomAliasAvailabilityChecker.kt @@ -16,6 +16,7 @@ package org.matrix.android.sdk.internal.session.room.alias +import org.matrix.android.sdk.api.MatrixPatterns.getDomain import org.matrix.android.sdk.api.failure.Failure import org.matrix.android.sdk.api.session.room.alias.RoomAliasError import org.matrix.android.sdk.internal.di.UserId @@ -51,19 +52,19 @@ internal class RoomAliasAvailabilityChecker @Inject constructor( } catch (throwable: Throwable) { if (throwable is Failure.ServerError && throwable.httpCode == 404) { // This is a 404, so the alias is available: nominal case - null + return } else { // Other error, propagate it throw throwable } } - ?.let { + .let { // Alias already exists: error case throw RoomAliasError.AliasNotAvailable } } companion object { - internal fun String.toFullLocalAlias(userId: String) = "#" + this + ":" + userId.substringAfter(":") + internal fun String.toFullLocalAlias(userId: String) = "#" + this + ":" + userId.getDomain() } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/create/CreateRoomBodyBuilder.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/create/CreateRoomBodyBuilder.kt index 018b8653886ac861ffc7ca791cdda340af72af20..9bb3899f2fdc966b183379f1cc7c3798df9ce9cb 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/create/CreateRoomBodyBuilder.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/create/CreateRoomBodyBuilder.kt @@ -17,16 +17,10 @@ package org.matrix.android.sdk.internal.session.room.create import org.matrix.android.sdk.api.extensions.tryOrNull -import org.matrix.android.sdk.api.session.crypto.crosssigning.CrossSigningService import org.matrix.android.sdk.api.session.events.model.Event import org.matrix.android.sdk.api.session.events.model.EventType -import org.matrix.android.sdk.api.session.events.model.toContent import org.matrix.android.sdk.api.session.identity.IdentityServiceError import org.matrix.android.sdk.api.session.identity.toMedium -import org.matrix.android.sdk.api.session.room.model.GuestAccess -import org.matrix.android.sdk.api.session.room.model.RoomHistoryVisibility -import org.matrix.android.sdk.api.session.room.model.RoomJoinRules -import org.matrix.android.sdk.api.session.room.model.RoomJoinRulesContent import org.matrix.android.sdk.api.session.room.model.create.CreateRoomParams import org.matrix.android.sdk.api.util.MimeTypes import org.matrix.android.sdk.internal.crypto.DeviceListManager @@ -45,7 +39,6 @@ import javax.inject.Inject internal class CreateRoomBodyBuilder @Inject constructor( private val ensureIdentityTokenTask: EnsureIdentityTokenTask, - private val crossSigningService: CrossSigningService, private val deviceListManager: DeviceListManager, private val identityStore: IdentityStore, private val fileUploader: FileUploader, @@ -59,7 +52,7 @@ internal class CreateRoomBodyBuilder @Inject constructor( val invite3pids = params.invite3pids .takeIf { it.isNotEmpty() } ?.let { invites -> - // This can throw Exception if Identity server is not configured + // This can throw an exception if identity server is not configured ensureIdentityTokenTask.execute(Unit) val identityServerUrlWithoutProtocol = identityStore.getIdentityServerUrlWithoutProtocol() @@ -76,18 +69,18 @@ internal class CreateRoomBodyBuilder @Inject constructor( } } - if (params.joinRuleRestricted != null) { - params.roomVersion = "org.matrix.msc3083" - params.historyVisibility = params.historyVisibility ?: RoomHistoryVisibility.SHARED - params.guestAccess = params.guestAccess ?: GuestAccess.Forbidden - } - val initialStates = listOfNotNull( - buildEncryptionWithAlgorithmEvent(params), - buildHistoryVisibilityEvent(params), - buildAvatarEvent(params), - buildGuestAccess(params), - buildJoinRulesRestricted(params) - ) + params.featurePreset?.updateRoomParams(params) + + val initialStates = ( + listOfNotNull( + buildEncryptionWithAlgorithmEvent(params), + buildHistoryVisibilityEvent(params), + buildAvatarEvent(params), + buildGuestAccess(params) + ) + + params.featurePreset?.setupInitialStates().orEmpty() + + buildCustomInitialStates(params) + ) .takeIf { it.isNotEmpty() } return CreateRoomBody( @@ -95,7 +88,7 @@ internal class CreateRoomBodyBuilder @Inject constructor( roomAliasName = params.roomAliasName, name = params.name, topic = params.topic, - invitedUserIds = params.invitedUserIds.filter { it != userId }, + invitedUserIds = params.invitedUserIds.filter { it != userId }.takeIf { it.isNotEmpty() }, invite3pids = invite3pids, creationContent = params.creationContent.takeIf { it.isNotEmpty() }, initialStates = initialStates, @@ -103,10 +96,19 @@ internal class CreateRoomBodyBuilder @Inject constructor( isDirect = params.isDirect, powerLevelContentOverride = params.powerLevelContentOverride, roomVersion = params.roomVersion - ) } + private fun buildCustomInitialStates(params: CreateRoomParams): List<Event> { + return params.initialStates.map { + Event( + type = it.type, + stateKey = it.stateKey, + content = it.content + ) + } + } + private suspend fun buildAvatarEvent(params: CreateRoomParams): Event? { return params.avatarUri?.let { avatarUri -> // First upload the image, ignoring any error @@ -148,20 +150,6 @@ internal class CreateRoomBodyBuilder @Inject constructor( } } - private fun buildJoinRulesRestricted(params: CreateRoomParams): Event? { - return params.joinRuleRestricted - ?.let { allowList -> - Event( - type = EventType.STATE_ROOM_JOIN_RULES, - stateKey = "", - content = RoomJoinRulesContent( - _joinRules = RoomJoinRules.RESTRICTED.value, - allowList = allowList - ).toContent() - ) - } - } - /** * Add the crypto algorithm to the room creation parameters. */ diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/membership/RoomChangeMembershipStateDataSource.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/membership/RoomChangeMembershipStateDataSource.kt index b9c547d4fb8c6b2f911174084eac88bf1b84bcd2..35d8cb08af62e62a75b796ce158b251c033c373e 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/membership/RoomChangeMembershipStateDataSource.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/membership/RoomChangeMembershipStateDataSource.kt @@ -21,6 +21,7 @@ import androidx.lifecycle.MutableLiveData import org.matrix.android.sdk.api.session.room.members.ChangeMembershipState import org.matrix.android.sdk.api.session.room.model.Membership import org.matrix.android.sdk.internal.session.SessionScope +import java.util.concurrent.ConcurrentHashMap import javax.inject.Inject /** @@ -30,7 +31,7 @@ import javax.inject.Inject internal class RoomChangeMembershipStateDataSource @Inject constructor() { private val mutableLiveStates = MutableLiveData<Map<String, ChangeMembershipState>>(emptyMap()) - private val states = HashMap<String, ChangeMembershipState>() + private val states = ConcurrentHashMap<String, ChangeMembershipState>() /** * This will update local states to be synced with the server. diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/membership/joining/JoinRoomTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/membership/joining/JoinRoomTask.kt index 33776e4f6ece17d41a1e1910167425093ff6d291..562b25683b1e47f780eb07c48f92514e5d8190fc 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/membership/joining/JoinRoomTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/membership/joining/JoinRoomTask.kt @@ -54,6 +54,10 @@ internal class DefaultJoinRoomTask @Inject constructor( ) : JoinRoomTask { override suspend fun execute(params: JoinRoomTask.Params) { + val currentState = roomChangeMembershipStateDataSource.getState(params.roomIdOrAlias) + if (currentState.isInProgress() || currentState == ChangeMembershipState.Joined) { + return + } roomChangeMembershipStateDataSource.updateState(params.roomIdOrAlias, ChangeMembershipState.Joining) val joinRoomResponse = try { executeRequest(globalErrorReceiver) { diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/DefaultSendService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/DefaultSendService.kt index 449189e6b56cb0ef723e5f8bf8e67511b24c4f86..a64b9039474a9f4c54dc113a55dd86de2735d257 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/DefaultSendService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/DefaultSendService.kt @@ -142,7 +142,7 @@ internal class DefaultSendService @AssistedInject constructor( // The image has not yet been sent val attachmentData = ContentAttachmentData( size = messageContent.info!!.size, - mimeType = messageContent.info.mimeType!!, + mimeType = messageContent.mimeType, width = messageContent.info.width.toLong(), height = messageContent.info.height.toLong(), name = messageContent.body, @@ -169,7 +169,7 @@ internal class DefaultSendService @AssistedInject constructor( is MessageFileContent -> { val attachmentData = ContentAttachmentData( size = messageContent.info!!.size, - mimeType = messageContent.info.mimeType!!, + mimeType = messageContent.mimeType, name = messageContent.getFileName(), queryUri = Uri.parse(messageContent.url), type = ContentAttachmentData.Type.FILE @@ -181,10 +181,11 @@ internal class DefaultSendService @AssistedInject constructor( val attachmentData = ContentAttachmentData( size = messageContent.audioInfo?.size ?: 0, duration = messageContent.audioInfo?.duration?.toLong() ?: 0L, - mimeType = messageContent.audioInfo?.mimeType, + mimeType = messageContent.mimeType, name = messageContent.body, queryUri = Uri.parse(messageContent.url), - type = ContentAttachmentData.Type.AUDIO + type = ContentAttachmentData.Type.AUDIO, + waveform = messageContent.audioWaveformInfo?.waveform ) localEchoRepository.updateSendState(localEcho.eventId, roomId, SendState.UNSENT) internalSendMedia(listOf(localEcho.root), attachmentData, true) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/LocalEchoEventFactory.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/LocalEchoEventFactory.kt index f505b13b337ee1325f4e3eacd544d4e6ccf87292..c610326a9409625c91b0a7e965891f797462f0e2 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/LocalEchoEventFactory.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/LocalEchoEventFactory.kt @@ -29,6 +29,7 @@ import org.matrix.android.sdk.api.session.events.model.RelationType import org.matrix.android.sdk.api.session.events.model.UnsignedData import org.matrix.android.sdk.api.session.events.model.toContent import org.matrix.android.sdk.api.session.room.model.message.AudioInfo +import org.matrix.android.sdk.api.session.room.model.message.AudioWaveformInfo import org.matrix.android.sdk.api.session.room.model.message.FileInfo import org.matrix.android.sdk.api.session.room.model.message.ImageInfo import org.matrix.android.sdk.api.session.room.model.message.MessageAudioContent @@ -74,6 +75,7 @@ internal class LocalEchoEventFactory @Inject constructor( private val markdownParser: MarkdownParser, private val textPillsUtils: TextPillsUtils, private val thumbnailExtractor: ThumbnailExtractor, + private val waveformSanitizer: WaveFormSanitizer, private val localEchoRepository: LocalEchoRepository, private val permalinkFactory: PermalinkFactory ) { @@ -289,14 +291,21 @@ internal class LocalEchoEventFactory @Inject constructor( } private fun createAudioEvent(roomId: String, attachment: ContentAttachmentData): Event { + val isVoiceMessage = attachment.waveform != null val content = MessageAudioContent( msgType = MessageType.MSGTYPE_AUDIO, body = attachment.name ?: "audio", audioInfo = AudioInfo( + duration = attachment.duration?.toInt(), mimeType = attachment.getSafeMimeType()?.takeIf { it.isNotBlank() }, size = attachment.size ), - url = attachment.queryUri.toString() + url = attachment.queryUri.toString(), + audioWaveformInfo = if (!isVoiceMessage) null else AudioWaveformInfo( + duration = attachment.duration?.toInt(), + waveform = waveformSanitizer.sanitize(attachment.waveform) + ), + voiceMessageIndicator = if (!isVoiceMessage) null else emptyMap() ) return createMessageEvent(roomId, content) } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/WaveFormSanitizer.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/WaveFormSanitizer.kt new file mode 100644 index 0000000000000000000000000000000000000000..78a03f3775c6e7aa4f3ed5134ea26cef199b9986 --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/WaveFormSanitizer.kt @@ -0,0 +1,86 @@ +/* + * Copyright (c) 2021 The Matrix.org Foundation C.I.C. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.matrix.android.sdk.internal.session.room.send + +import timber.log.Timber +import javax.inject.Inject +import kotlin.math.abs +import kotlin.math.ceil + +internal class WaveFormSanitizer @Inject constructor() { + private companion object { + const val MIN_NUMBER_OF_VALUES = 30 + const val MAX_NUMBER_OF_VALUES = 120 + + const val MAX_VALUE = 1024 + } + + /** + * The array should have no less than 30 elements and no more than 120. + * List of integers between zero and 1024, inclusive. + */ + fun sanitize(waveForm: List<Int>?): List<Int>? { + if (waveForm.isNullOrEmpty()) { + return null + } + + // Limit the number of items + val sizeInRangeList = mutableListOf<Int>() + when { + waveForm.size < MIN_NUMBER_OF_VALUES -> { + // Repeat the same value to have at least 30 items + val repeatTimes = ceil(MIN_NUMBER_OF_VALUES / waveForm.size.toDouble()).toInt() + waveForm.map { value -> + repeat(repeatTimes) { + sizeInRangeList.add(value) + } + } + } + waveForm.size > MAX_NUMBER_OF_VALUES -> { + val keepOneOf = ceil(waveForm.size.toDouble() / MAX_NUMBER_OF_VALUES).toInt() + waveForm.mapIndexed { idx, value -> + if (idx % keepOneOf == 0) { + sizeInRangeList.add(value) + } + } + } + else -> { + sizeInRangeList.addAll(waveForm) + } + } + + // OK, ensure all items are positive + val positiveList = sizeInRangeList.map { + abs(it) + } + + // Ensure max is not above MAX_VALUE + val max = positiveList.maxOrNull() ?: MAX_VALUE + + val finalList = if (max > MAX_VALUE) { + // Reduce the values + positiveList.map { + it * MAX_VALUE / max + } + } else { + positiveList + } + + Timber.d("Sanitize from ${waveForm.size} items to ${finalList.size} items. Max value was $max") + return finalList + } +} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/queue/EventSenderProcessorCoroutine.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/queue/EventSenderProcessorCoroutine.kt index 80bfd02b0eef8bcec3f29cde2d5d2f2b4451c673..3be01762e77cc57e65df3c986d11b83286ba33f8 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/queue/EventSenderProcessorCoroutine.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/queue/EventSenderProcessorCoroutine.kt @@ -46,7 +46,7 @@ private const val MAX_RETRY_COUNT = 3 /** * This class is responsible for sending events in order in each room. It uses the QueuedTask.queueIdentifier to execute tasks sequentially. - * Each send is retried 3 times, if there is no network (e.g if cannot ping home server) it will wait and + * Each send is retried 3 times, if there is no network (e.g if cannot ping homeserver) it will wait and * periodically test reachability before resume (does not count as a retry) * * If the app is killed before all event were sent, on next wakeup the scheduled events will be re posted diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/queue/EventSenderProcessorThread.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/queue/EventSenderProcessorThread.kt index 9db7cc90394fbb7378231dc963314e58d7ddac30..f32890f3fb42cd912e68f83b7a91aa7536e2345b 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/queue/EventSenderProcessorThread.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/queue/EventSenderProcessorThread.kt @@ -42,7 +42,7 @@ import kotlin.concurrent.schedule /** * A simple ever running thread unique for that session responsible of sending events in order. - * Each send is retried 3 times, if there is no network (e.g if cannot ping home server) it will wait and + * Each send is retried 3 times, if there is no network (e.g if cannot ping homeserver) it will wait and * periodically test reachability before resume (does not count as a retry) * * If the app is killed before all event were sent, on next wakeup the scheduled events will be re posted diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/queue/HomeServerAvailabilityChecker.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/queue/HomeServerAvailabilityChecker.kt index 2d53699917b03c819fd0ac04882a61edf7fea8ca..1d7ce587f9938e088942ab3e31f4b3619d2104f5 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/queue/HomeServerAvailabilityChecker.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/queue/HomeServerAvailabilityChecker.kt @@ -26,8 +26,8 @@ import java.net.Socket internal class HomeServerAvailabilityChecker(val sessionParams: SessionParams) { fun check(): Boolean { - val host = sessionParams.homeServerConnectionConfig.homeServerUri.host ?: return false - val port = sessionParams.homeServerConnectionConfig.homeServerUri.port.takeIf { it != -1 } ?: 80 + val host = sessionParams.homeServerConnectionConfig.homeServerUriBase.host ?: return false + val port = sessionParams.homeServerConnectionConfig.homeServerUriBase.port.takeIf { it != -1 } ?: 80 val timeout = 30_000 try { Socket().use { socket -> diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/state/DefaultStateService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/state/DefaultStateService.kt index ff2afb5d61aff480092658ea0c8d9900e0de1f8b..7eed22f65fcb29af921bc454c79c55527135025b 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/state/DefaultStateService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/state/DefaultStateService.kt @@ -19,8 +19,8 @@ package org.matrix.android.sdk.internal.session.room.state import android.net.Uri import androidx.lifecycle.LiveData import dagger.assisted.Assisted -import dagger.assisted.AssistedInject import dagger.assisted.AssistedFactory +import dagger.assisted.AssistedInject import org.matrix.android.sdk.api.query.QueryStringValue import org.matrix.android.sdk.api.session.events.model.Event import org.matrix.android.sdk.api.session.events.model.EventType @@ -29,17 +29,20 @@ import org.matrix.android.sdk.api.session.room.model.GuestAccess import org.matrix.android.sdk.api.session.room.model.RoomCanonicalAliasContent import org.matrix.android.sdk.api.session.room.model.RoomHistoryVisibility import org.matrix.android.sdk.api.session.room.model.RoomJoinRules +import org.matrix.android.sdk.api.session.room.model.RoomJoinRulesAllowEntry +import org.matrix.android.sdk.api.session.room.model.RoomJoinRulesContent import org.matrix.android.sdk.api.session.room.state.StateService import org.matrix.android.sdk.api.util.JsonDict import org.matrix.android.sdk.api.util.MimeTypes import org.matrix.android.sdk.api.util.Optional import org.matrix.android.sdk.internal.session.content.FileUploader -import java.lang.UnsupportedOperationException +import org.matrix.android.sdk.internal.session.permalinks.ViaParameterFinder internal class DefaultStateService @AssistedInject constructor(@Assisted private val roomId: String, private val stateEventDataSource: StateEventDataSource, private val sendStateTask: SendStateTask, - private val fileUploader: FileUploader + private val fileUploader: FileUploader, + private val viaParameterFinder: ViaParameterFinder ) : StateService { @AssistedFactory @@ -126,12 +129,19 @@ internal class DefaultStateService @AssistedInject constructor(@Assisted private ) } - override suspend fun updateJoinRule(joinRules: RoomJoinRules?, guestAccess: GuestAccess?) { + override suspend fun updateJoinRule(joinRules: RoomJoinRules?, guestAccess: GuestAccess?, allowList: List<RoomJoinRulesAllowEntry>?) { if (joinRules != null) { - if (joinRules == RoomJoinRules.RESTRICTED) throw UnsupportedOperationException("No yet supported") + val body = if (joinRules == RoomJoinRules.RESTRICTED) { + RoomJoinRulesContent( + _joinRules = RoomJoinRules.RESTRICTED.value, + allowList = allowList + ).toContent() + } else { + mapOf("join_rule" to joinRules) + } sendStateEvent( eventType = EventType.STATE_ROOM_JOIN_RULES, - body = mapOf("join_rule" to joinRules), + body = body, stateKey = null ) } @@ -160,4 +170,20 @@ internal class DefaultStateService @AssistedInject constructor(@Assisted private stateKey = null ) } + + override suspend fun setJoinRulePublic() { + updateJoinRule(RoomJoinRules.PUBLIC, null) + } + + override suspend fun setJoinRuleInviteOnly() { + updateJoinRule(RoomJoinRules.INVITE, null) + } + + override suspend fun setJoinRuleRestricted(allowList: List<String>) { + // we need to compute correct via parameters and check if PL are correct + val allowEntries = allowList.map { spaceId -> + RoomJoinRulesAllowEntry(spaceId, viaParameterFinder.computeViaParamsForRestricted(spaceId, 3)) + } + updateJoinRule(RoomJoinRules.RESTRICTED, null, allowEntries) + } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/summary/RoomSummaryDataSource.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/summary/RoomSummaryDataSource.kt index bff1af60ca9af4c1e8f3df7f489592ba76c4c908..0b8c6df8066c6274acaf8c257619a030b1f45ed3 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/summary/RoomSummaryDataSource.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/summary/RoomSummaryDataSource.kt @@ -247,10 +247,10 @@ internal class RoomSummaryDataSource @Inject constructor(@SessionDatabase privat queryParams.roomCategoryFilter?.let { when (it) { - RoomCategoryFilter.ONLY_DM -> query.equalTo(RoomSummaryEntityFields.IS_DIRECT, true) - RoomCategoryFilter.ONLY_ROOMS -> query.equalTo(RoomSummaryEntityFields.IS_DIRECT, false) + RoomCategoryFilter.ONLY_DM -> query.equalTo(RoomSummaryEntityFields.IS_DIRECT, true) + RoomCategoryFilter.ONLY_ROOMS -> query.equalTo(RoomSummaryEntityFields.IS_DIRECT, false) RoomCategoryFilter.ONLY_WITH_NOTIFICATIONS -> query.greaterThan(RoomSummaryEntityFields.NOTIFICATION_COUNT, 0) - RoomCategoryFilter.ALL -> { + RoomCategoryFilter.ALL -> { // nop } } @@ -274,15 +274,15 @@ internal class RoomSummaryDataSource @Inject constructor(@SessionDatabase privat query.equalTo(RoomSummaryEntityFields.ROOM_TYPE, it) } when (queryParams.roomCategoryFilter) { - RoomCategoryFilter.ONLY_DM -> query.equalTo(RoomSummaryEntityFields.IS_DIRECT, true) - RoomCategoryFilter.ONLY_ROOMS -> query.equalTo(RoomSummaryEntityFields.IS_DIRECT, false) + RoomCategoryFilter.ONLY_DM -> query.equalTo(RoomSummaryEntityFields.IS_DIRECT, true) + RoomCategoryFilter.ONLY_ROOMS -> query.equalTo(RoomSummaryEntityFields.IS_DIRECT, false) RoomCategoryFilter.ONLY_WITH_NOTIFICATIONS -> query.greaterThan(RoomSummaryEntityFields.NOTIFICATION_COUNT, 0) - RoomCategoryFilter.ALL -> Unit // nop + RoomCategoryFilter.ALL -> Unit // nop } // Timber.w("VAL: activeSpaceId : ${queryParams.activeSpaceId}") when (queryParams.activeSpaceFilter) { - is ActiveSpaceFilter.ActiveSpace -> { + is ActiveSpaceFilter.ActiveSpace -> { // It's annoying but for now realm java does not support querying in primitive list :/ // https://github.com/realm/realm-java/issues/5361 if (queryParams.activeSpaceFilter.currentSpaceId == null) { @@ -300,8 +300,8 @@ internal class RoomSummaryDataSource @Inject constructor(@SessionDatabase privat } } - if (queryParams.activeGroupId != null) { - query.contains(RoomSummaryEntityFields.GROUP_IDS, queryParams.activeGroupId!!) + queryParams.activeGroupId?.let { activeGroupId -> + query.contains(RoomSummaryEntityFields.GROUP_IDS, activeGroupId) } return query } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/summary/RoomSummaryUpdater.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/summary/RoomSummaryUpdater.kt index 7cbcfee713565ecdd840f3c08d26201ef896ce4b..842c9d3abadfbf41d7f92a3e10c87633ccd8c98c 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/summary/RoomSummaryUpdater.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/summary/RoomSummaryUpdater.kt @@ -199,7 +199,6 @@ internal class RoomSummaryUpdater @Inject constructor( measureTimeMillis { val lookupMap = realm.where(RoomSummaryEntity::class.java) .process(RoomSummaryEntityFields.MEMBERSHIP_STR, Membership.activeMemberships()) - .equalTo(RoomSummaryEntityFields.IS_DIRECT, false) // we order by roomID to be consistent when breaking parent/child cycles .sort(RoomSummaryEntityFields.ROOM_ID) .findAll().map { diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/DefaultTimeline.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/DefaultTimeline.kt index e230599f8f6d1d013cfd4d03ae6d720be6bcff18..8cc5d943b7e291ba0c4afdfd11c096e88874276c 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/DefaultTimeline.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/DefaultTimeline.kt @@ -24,7 +24,6 @@ import io.realm.RealmQuery import io.realm.RealmResults import io.realm.Sort import org.matrix.android.sdk.api.MatrixCallback -import org.matrix.android.sdk.api.NoOpMatrixCallback import org.matrix.android.sdk.api.extensions.orFalse import org.matrix.android.sdk.api.extensions.tryOrNull import org.matrix.android.sdk.api.session.events.model.EventType @@ -168,9 +167,7 @@ internal class DefaultTimeline( timelineEvents.addChangeListener(eventsChangeListener) handleInitialLoad() loadRoomMembersTask - .configureWith(LoadRoomMembersTask.Params(roomId)) { - this.callback = NoOpMatrixCallback() - } + .configureWith(LoadRoomMembersTask.Params(roomId)) .executeBy(taskExecutor) // Ensure ReadReceipt from init sync are loaded diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/typing/DefaultTypingService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/typing/DefaultTypingService.kt index 39b7967bc1de69314497a86d90e8d0cbeaa03e55..b76829e8937eda5fce2a22b5a446bab224855cbd 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/typing/DefaultTypingService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/typing/DefaultTypingService.kt @@ -18,13 +18,13 @@ package org.matrix.android.sdk.internal.session.room.typing import android.os.SystemClock import dagger.assisted.Assisted -import dagger.assisted.AssistedInject import dagger.assisted.AssistedFactory -import org.matrix.android.sdk.api.MatrixCallback +import dagger.assisted.AssistedInject +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Job +import kotlinx.coroutines.delay +import kotlinx.coroutines.launch import org.matrix.android.sdk.api.session.room.typing.TypingService -import org.matrix.android.sdk.api.util.Cancelable -import org.matrix.android.sdk.internal.task.TaskExecutor -import org.matrix.android.sdk.internal.task.configureWith import timber.log.Timber /** @@ -35,7 +35,6 @@ import timber.log.Timber */ internal class DefaultTypingService @AssistedInject constructor( @Assisted private val roomId: String, - private val taskExecutor: TaskExecutor, private val sendTypingTask: SendTypingTask ) : TypingService { @@ -44,8 +43,8 @@ internal class DefaultTypingService @AssistedInject constructor( fun create(roomId: String): DefaultTypingService } - private var currentTask: Cancelable? = null - private var currentAutoStopTask: Cancelable? = null + private val coroutineScope = CoroutineScope(Job()) + private var currentTask: Job? = null // What the homeserver knows private var userIsTyping = false @@ -53,26 +52,24 @@ internal class DefaultTypingService @AssistedInject constructor( // Last time the user is typing event has been sent private var lastRequestTimestamp: Long = 0 + /** + * Notify to the server that the user is typing and schedule the auto typing off + */ override fun userIsTyping() { - scheduleAutoStop() - val now = SystemClock.elapsedRealtime() - - if (userIsTyping && now < lastRequestTimestamp + MIN_DELAY_BETWEEN_TWO_USER_IS_TYPING_REQUESTS_MILLIS) { - Timber.d("Typing: Skip start request") - return - } - - Timber.d("Typing: Send start request") - userIsTyping = true - lastRequestTimestamp = now - currentTask?.cancel() - - val params = SendTypingTask.Params(roomId, true) - currentTask = sendTypingTask - .configureWith(params) - .executeBy(taskExecutor) + currentTask = coroutineScope.launch { + if (userIsTyping && now < lastRequestTimestamp + MIN_DELAY_BETWEEN_TWO_USER_IS_TYPING_REQUESTS_MILLIS) { + Timber.d("Typing: Skip start request") + } else { + Timber.d("Typing: Send start request") + lastRequestTimestamp = now + sendRequest(true) + } + delay(MIN_DELAY_TO_SEND_STOP_TYPING_REQUEST_WHEN_NO_USER_ACTIVITY_MILLIS) + Timber.d("Typing: auto stop") + sendRequest(false) + } } override fun userStopsTyping() { @@ -82,35 +79,22 @@ internal class DefaultTypingService @AssistedInject constructor( } Timber.d("Typing: Send stop request") - userIsTyping = false lastRequestTimestamp = 0 - currentAutoStopTask?.cancel() currentTask?.cancel() - - val params = SendTypingTask.Params(roomId, false) - currentTask = sendTypingTask - .configureWith(params) - .executeBy(taskExecutor) + currentTask = coroutineScope.launch { + sendRequest(false) + } } - private fun scheduleAutoStop() { - Timber.d("Typing: Schedule auto stop") - currentAutoStopTask?.cancel() - - val params = SendTypingTask.Params( - roomId, - false, - delay = MIN_DELAY_TO_SEND_STOP_TYPING_REQUEST_WHEN_NO_USER_ACTIVITY_MILLIS) - currentAutoStopTask = sendTypingTask - .configureWith(params) { - callback = object : MatrixCallback<Unit> { - override fun onSuccess(data: Unit) { - userIsTyping = false - } - } - } - .executeBy(taskExecutor) + private suspend fun sendRequest(isTyping: Boolean) { + try { + sendTypingTask.execute(SendTypingTask.Params(roomId, isTyping)) + userIsTyping = isTyping + } catch (failure: Throwable) { + // Ignore network error, etc... + Timber.w(failure, "Unable to send typing request") + } } companion object { diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/typing/SendTypingTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/typing/SendTypingTask.kt index 0b0df743113285d432cc586afbef88cb5237e0a6..0bdceb9adebddc70ed3612b5ff125227ba9a66f0 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/typing/SendTypingTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/typing/SendTypingTask.kt @@ -17,11 +17,10 @@ package org.matrix.android.sdk.internal.session.room.typing import org.matrix.android.sdk.internal.di.UserId +import org.matrix.android.sdk.internal.network.GlobalErrorReceiver import org.matrix.android.sdk.internal.network.executeRequest import org.matrix.android.sdk.internal.session.room.RoomAPI import org.matrix.android.sdk.internal.task.Task -import kotlinx.coroutines.delay -import org.matrix.android.sdk.internal.network.GlobalErrorReceiver import javax.inject.Inject internal interface SendTypingTask : Task<SendTypingTask.Params, Unit> { @@ -29,9 +28,7 @@ internal interface SendTypingTask : Task<SendTypingTask.Params, Unit> { data class Params( val roomId: String, val isTyping: Boolean, - val typingTimeoutMillis: Int? = 30_000, - // Optional delay before sending the request to the homeserver - val delay: Long? = null + val typingTimeoutMillis: Int? = 30_000 ) } @@ -42,8 +39,6 @@ internal class DefaultSendTypingTask @Inject constructor( ) : SendTypingTask { override suspend fun execute(params: SendTypingTask.Params) { - delay(params.delay ?: -1) - executeRequest(globalErrorReceiver) { roomAPI.sendTypingState( params.roomId, diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/version/DefaultRoomVersionService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/version/DefaultRoomVersionService.kt new file mode 100644 index 0000000000000000000000000000000000000000..dc12c3209b2811398a41c35ec2009ac0074b5f08 --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/version/DefaultRoomVersionService.kt @@ -0,0 +1,84 @@ +/* + * Copyright 2021 The Matrix.org Foundation C.I.C. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.matrix.android.sdk.internal.session.room.version + +import dagger.assisted.Assisted +import dagger.assisted.AssistedFactory +import dagger.assisted.AssistedInject +import org.matrix.android.sdk.api.query.QueryStringValue +import org.matrix.android.sdk.api.session.events.model.EventType +import org.matrix.android.sdk.api.session.events.model.toModel +import org.matrix.android.sdk.api.session.homeserver.RoomVersionStatus +import org.matrix.android.sdk.api.session.room.model.PowerLevelsContent +import org.matrix.android.sdk.api.session.room.model.create.RoomCreateContent +import org.matrix.android.sdk.api.session.room.powerlevels.PowerLevelsHelper +import org.matrix.android.sdk.api.session.room.version.RoomVersionService +import org.matrix.android.sdk.internal.session.homeserver.HomeServerCapabilitiesDataSource +import org.matrix.android.sdk.internal.session.room.state.StateEventDataSource + +internal class DefaultRoomVersionService @AssistedInject constructor( + @Assisted private val roomId: String, + private val homeServerCapabilitiesDataSource: HomeServerCapabilitiesDataSource, + private val stateEventDataSource: StateEventDataSource, + private val roomVersionUpgradeTask: RoomVersionUpgradeTask +) : RoomVersionService { + + @AssistedFactory + interface Factory { + fun create(roomId: String): DefaultRoomVersionService + } + + override fun getRoomVersion(): String { + return stateEventDataSource.getStateEvent(roomId, EventType.STATE_ROOM_CREATE, QueryStringValue.IsEmpty) + ?.content + ?.toModel<RoomCreateContent>() + ?.roomVersion + // as per spec -> Defaults to "1" if the key does not exist. + ?: DEFAULT_ROOM_VERSION + } + + override suspend fun upgradeToVersion(version: String): String { + return roomVersionUpgradeTask.execute( + RoomVersionUpgradeTask.Params( + roomId = roomId, + newVersion = version + ) + ) + } + + override fun getRecommendedVersion(): String { + return homeServerCapabilitiesDataSource.getHomeServerCapabilities()?.roomVersions?.defaultRoomVersion ?: DEFAULT_ROOM_VERSION + } + + override fun isUsingUnstableRoomVersion(): Boolean { + val versionCaps = homeServerCapabilitiesDataSource.getHomeServerCapabilities()?.roomVersions + val currentVersion = getRoomVersion() + return versionCaps?.supportedVersion?.firstOrNull { it.version == currentVersion }?.status == RoomVersionStatus.UNSTABLE + } + + override fun userMayUpgradeRoom(userId: String): Boolean { + val powerLevelsHelper = stateEventDataSource.getStateEvent(roomId, EventType.STATE_ROOM_POWER_LEVELS, QueryStringValue.NoCondition) + ?.content?.toModel<PowerLevelsContent>() + ?.let { PowerLevelsHelper(it) } + + return powerLevelsHelper?.isUserAllowedToSend(userId, true, EventType.STATE_ROOM_TOMBSTONE) ?: false + } + + companion object { + const val DEFAULT_ROOM_VERSION = "1" + } +} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/version/RoomVersionUpgradeTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/version/RoomVersionUpgradeTask.kt new file mode 100644 index 0000000000000000000000000000000000000000..457bb3e948bbd96d3ec28dd184fbcbab5a753772 --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/version/RoomVersionUpgradeTask.kt @@ -0,0 +1,66 @@ +/* + * Copyright 2021 The Matrix.org Foundation C.I.C. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.matrix.android.sdk.internal.session.room.version + +import io.realm.RealmConfiguration +import org.matrix.android.sdk.api.extensions.tryOrNull +import org.matrix.android.sdk.api.session.room.model.Membership +import org.matrix.android.sdk.internal.database.awaitNotEmptyResult +import org.matrix.android.sdk.internal.database.model.RoomSummaryEntity +import org.matrix.android.sdk.internal.database.model.RoomSummaryEntityFields +import org.matrix.android.sdk.internal.di.SessionDatabase +import org.matrix.android.sdk.internal.network.GlobalErrorReceiver +import org.matrix.android.sdk.internal.network.executeRequest +import org.matrix.android.sdk.internal.session.room.RoomAPI +import org.matrix.android.sdk.internal.session.room.RoomUpgradeBody +import org.matrix.android.sdk.internal.task.Task +import java.util.concurrent.TimeUnit +import javax.inject.Inject + +internal interface RoomVersionUpgradeTask : Task<RoomVersionUpgradeTask.Params, String> { + data class Params( + val roomId: String, + val newVersion: String + ) +} + +internal class DefaultRoomVersionUpgradeTask @Inject constructor( + private val roomAPI: RoomAPI, + private val globalErrorReceiver: GlobalErrorReceiver, + @SessionDatabase + private val realmConfiguration: RealmConfiguration +) : RoomVersionUpgradeTask { + + override suspend fun execute(params: RoomVersionUpgradeTask.Params): String { + val replacementRoomId = executeRequest(globalErrorReceiver) { + roomAPI.upgradeRoom( + roomId = params.roomId, + body = RoomUpgradeBody(params.newVersion) + ) + }.replacementRoomId + + // Wait for room to come back from the sync (but it can maybe be in the DB if the sync response is received before) + tryOrNull { + awaitNotEmptyResult(realmConfiguration, TimeUnit.MINUTES.toMillis(1L)) { realm -> + realm.where(RoomSummaryEntity::class.java) + .equalTo(RoomSummaryEntityFields.ROOM_ID, replacementRoomId) + .equalTo(RoomSummaryEntityFields.MEMBERSHIP_STR, Membership.JOIN.name) + } + } + return replacementRoomId + } +} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/space/DefaultSpace.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/space/DefaultSpace.kt index 70c52bf4aec8169596a706a6929af844e774b65c..233eef45f8d1f074e43fa2e0872f42ccf2d7d288 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/space/DefaultSpace.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/space/DefaultSpace.kt @@ -86,6 +86,12 @@ internal class DefaultSpace( ) } + override fun getChildInfo(roomId: String): SpaceChildContent? { + return room.getStateEvents(setOf(EventType.STATE_SPACE_CHILD), QueryStringValue.Equals(roomId)) + .firstOrNull() + ?.content.toModel<SpaceChildContent>() + } + override suspend fun setChildrenOrder(roomId: String, order: String?) { val existing = room.getStateEvents(setOf(EventType.STATE_SPACE_CHILD), QueryStringValue.Equals(roomId)) .firstOrNull() diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/space/DefaultSpaceService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/space/DefaultSpaceService.kt index 9c6153b34969ce07357c80c73173c462b6ecc326..0c5c0416f968aecb8f911a5a45bf9a6d8f613f9f 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/space/DefaultSpaceService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/space/DefaultSpaceService.kt @@ -66,12 +66,13 @@ internal class DefaultSpaceService @Inject constructor( return createRoomTask.executeRetry(params, 3) } - override suspend fun createSpace(name: String, topic: String?, avatarUri: Uri?, isPublic: Boolean): String { + override suspend fun createSpace(name: String, topic: String?, avatarUri: Uri?, isPublic: Boolean, roomAliasLocalPart: String?): String { return createSpace(CreateSpaceParams().apply { this.name = name this.topic = topic this.avatarUri = avatarUri if (isPublic) { + this.roomAliasName = roomAliasLocalPart this.powerLevelContentOverride = (powerLevelContentOverride ?: PowerLevelsContent()).copy( invite = 0 ) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/SyncTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/SyncTask.kt index 83a2ffc4466cd7a90e4ef890413562f8de4c122c..c80fbe60c1b7e52d3f856a27645bde819021e262 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/SyncTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/SyncTask.kt @@ -94,7 +94,7 @@ internal class DefaultSyncTask @Inject constructor( userStore.createOrUpdate(userId) initialSyncProgressService.startRoot(InitSyncStep.ImportingAccount, 100) } - // Maybe refresh the home server capabilities data we know + // Maybe refresh the homeserver capabilities data we know getHomeServerCapabilitiesTask.execute(GetHomeServerCapabilitiesTask.Params(forceRefresh = false)) val readTimeOut = (params.timeout + TIMEOUT_MARGIN).coerceAtLeast(TimeOutInterceptor.DEFAULT_LONG_TIMEOUT) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/UserAccountDataSyncHandler.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/UserAccountDataSyncHandler.kt index 3aebd90ed26d045c6a6b1d006959629d4c8766be..b8d987d5009471f7a08b1ce7553dccdc01ea976a 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/UserAccountDataSyncHandler.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/UserAccountDataSyncHandler.kt @@ -23,7 +23,7 @@ import io.realm.kotlin.where import org.matrix.android.sdk.api.pushrules.RuleScope import org.matrix.android.sdk.api.pushrules.RuleSetKey import org.matrix.android.sdk.api.pushrules.rest.GetPushRulesResponse -import org.matrix.android.sdk.api.session.accountdata.AccountDataEvent +import org.matrix.android.sdk.api.session.accountdata.UserAccountDataEvent import org.matrix.android.sdk.api.session.accountdata.UserAccountDataTypes import org.matrix.android.sdk.api.session.events.model.Content import org.matrix.android.sdk.api.session.events.model.toModel @@ -113,7 +113,7 @@ internal class UserAccountDataSyncHandler @Inject constructor( } } - private fun handlePushRules(realm: Realm, event: AccountDataEvent) { + private fun handlePushRules(realm: Realm, event: UserAccountDataEvent) { val pushRules = event.content.toModel<GetPushRulesResponse>() ?: return realm.where(PushRulesEntity::class.java) .findAll() @@ -155,7 +155,7 @@ internal class UserAccountDataSyncHandler @Inject constructor( realm.insertOrUpdate(underrides) } - private fun handleDirectChatRooms(realm: Realm, event: AccountDataEvent) { + private fun handleDirectChatRooms(realm: Realm, event: UserAccountDataEvent) { val content = event.content.toModel<DirectMessagesContent>() ?: return content.forEach { (userId, roomIds) -> roomIds.forEach { roomId -> @@ -181,7 +181,7 @@ internal class UserAccountDataSyncHandler @Inject constructor( } } - private fun handleIgnoredUsers(realm: Realm, event: AccountDataEvent) { + private fun handleIgnoredUsers(realm: Realm, event: UserAccountDataEvent) { val userIds = event.content.toModel<IgnoredUsersContent>()?.ignoredUsers?.keys ?: return realm.where(IgnoredUserEntity::class.java) .findAll() @@ -191,7 +191,7 @@ internal class UserAccountDataSyncHandler @Inject constructor( // TODO If not initial sync, we should execute a init sync } - private fun handleBreadcrumbs(realm: Realm, event: AccountDataEvent) { + private fun handleBreadcrumbs(realm: Realm, event: UserAccountDataEvent) { val recentRoomIds = event.content.toModel<BreadcrumbsContent>()?.recentRoomIds ?: return val entity = BreadcrumbsEntity.getOrCreate(realm) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/model/accountdata/UserAccountDataSync.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/model/accountdata/UserAccountDataSync.kt index ddb71cd19f4344733f23b1a53ef800d9034fdb25..05b50ab2c5ee00be2f069b7c008721ea67c7d4d2 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/model/accountdata/UserAccountDataSync.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/model/accountdata/UserAccountDataSync.kt @@ -18,9 +18,9 @@ package org.matrix.android.sdk.internal.session.sync.model.accountdata import com.squareup.moshi.Json import com.squareup.moshi.JsonClass -import org.matrix.android.sdk.api.session.accountdata.AccountDataEvent +import org.matrix.android.sdk.api.session.accountdata.UserAccountDataEvent @JsonClass(generateAdapter = true) internal data class UserAccountDataSync( - @Json(name = "events") val list: List<AccountDataEvent> = emptyList() + @Json(name = "events") val list: List<UserAccountDataEvent> = emptyList() ) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/parsing/RoomSyncAccountDataHandler.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/parsing/RoomSyncAccountDataHandler.kt index c8aab586a01048cfb19a5507552bd1f7ff2b5293..8bf9ad5b908f2af0a78d1bb7f23df514a4467a52 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/parsing/RoomSyncAccountDataHandler.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/parsing/RoomSyncAccountDataHandler.kt @@ -56,7 +56,6 @@ internal class RoomSyncAccountDataHandler @Inject constructor(private val roomTa private fun handleGeneric(roomEntity: RoomEntity, content: JsonDict?, eventType: String) { val existing = roomEntity.accountData.where().equalTo(RoomAccountDataEntityFields.TYPE, eventType).findFirst() if (existing != null) { - // Update current value existing.contentStr = ContentMapper.map(content) } else { val roomAccountData = RoomAccountDataEntity( diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/user/accountdata/UserAccountDataService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/user/accountdata/DefaultSessionAccountDataService.kt similarity index 55% rename from matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/user/accountdata/UserAccountDataService.kt rename to matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/user/accountdata/DefaultSessionAccountDataService.kt index b15d1d0f8bbe8f2cd737b36c57ecea11e616e134..ff1750ce8e4ffa57f8ab39c1d175421b53b10c95 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/user/accountdata/UserAccountDataService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/user/accountdata/DefaultSessionAccountDataService.kt @@ -18,42 +18,53 @@ package org.matrix.android.sdk.internal.session.user.accountdata import androidx.lifecycle.LiveData import com.zhuinden.monarchy.Monarchy -import org.matrix.android.sdk.api.session.accountdata.AccountDataService +import org.matrix.android.sdk.api.session.accountdata.SessionAccountDataService import org.matrix.android.sdk.api.session.events.model.Content import org.matrix.android.sdk.api.util.Optional import org.matrix.android.sdk.internal.di.SessionDatabase import org.matrix.android.sdk.internal.session.sync.UserAccountDataSyncHandler -import org.matrix.android.sdk.api.session.accountdata.AccountDataEvent +import org.matrix.android.sdk.api.session.accountdata.UserAccountDataEvent +import org.matrix.android.sdk.api.session.room.accountdata.RoomAccountDataEvent +import org.matrix.android.sdk.internal.session.room.accountdata.RoomAccountDataDataSource import org.matrix.android.sdk.internal.task.TaskExecutor import org.matrix.android.sdk.internal.task.configureWith import org.matrix.android.sdk.internal.util.awaitCallback import javax.inject.Inject -internal class UserAccountDataService @Inject constructor( +internal class DefaultSessionAccountDataService @Inject constructor( @SessionDatabase private val monarchy: Monarchy, private val updateUserAccountDataTask: UpdateUserAccountDataTask, private val userAccountDataSyncHandler: UserAccountDataSyncHandler, - private val accountDataDataSource: UserAccountDataDataSource, + private val userAccountDataDataSource: UserAccountDataDataSource, + private val roomAccountDataDataSource: RoomAccountDataDataSource, private val taskExecutor: TaskExecutor -) : AccountDataService { +) : SessionAccountDataService { - override fun getAccountDataEvent(type: String): AccountDataEvent? { - return accountDataDataSource.getAccountDataEvent(type) + override fun getUserAccountDataEvent(type: String): UserAccountDataEvent? { + return userAccountDataDataSource.getAccountDataEvent(type) } - override fun getLiveAccountDataEvent(type: String): LiveData<Optional<AccountDataEvent>> { - return accountDataDataSource.getLiveAccountDataEvent(type) + override fun getLiveUserAccountDataEvent(type: String): LiveData<Optional<UserAccountDataEvent>> { + return userAccountDataDataSource.getLiveAccountDataEvent(type) } - override fun getAccountDataEvents(types: Set<String>): List<AccountDataEvent> { - return accountDataDataSource.getAccountDataEvents(types) + override fun getUserAccountDataEvents(types: Set<String>): List<UserAccountDataEvent> { + return userAccountDataDataSource.getAccountDataEvents(types) } - override fun getLiveAccountDataEvents(types: Set<String>): LiveData<List<AccountDataEvent>> { - return accountDataDataSource.getLiveAccountDataEvents(types) + override fun getLiveUserAccountDataEvents(types: Set<String>): LiveData<List<UserAccountDataEvent>> { + return userAccountDataDataSource.getLiveAccountDataEvents(types) } - override suspend fun updateAccountData(type: String, content: Content) { + override fun getRoomAccountDataEvents(types: Set<String>): List<RoomAccountDataEvent> { + return roomAccountDataDataSource.getAccountDataEvents(null, types) + } + + override fun getLiveRoomAccountDataEvents(types: Set<String>): LiveData<List<RoomAccountDataEvent>> { + return roomAccountDataDataSource.getLiveAccountDataEvents(null, types) + } + + override suspend fun updateUserAccountData(type: String, content: Content) { val params = UpdateUserAccountDataTask.AnyParams(type = type, any = content) awaitCallback<Unit> { callback -> updateUserAccountDataTask.configureWith(params) { diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/user/accountdata/DirectChatsHelper.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/user/accountdata/DirectChatsHelper.kt index e297f59b969a3622d47da201382df6b7f6a2bb14..a9e5089774076564400a016d61c1672fbb927933 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/user/accountdata/DirectChatsHelper.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/user/accountdata/DirectChatsHelper.kt @@ -31,9 +31,11 @@ internal class DirectChatsHelper @Inject constructor(@SessionDatabase */ fun getLocalUserAccount(filterRoomId: String? = null): MutableMap<String, MutableList<String>> { return Realm.getInstance(realmConfiguration).use { realm -> + // Makes sure we have the latest realm updates, this is important as we sent this information to the server. + realm.refresh() RoomSummaryEntity.getDirectRooms(realm) .asSequence() - .filter { it.roomId != filterRoomId && it.directUserId != null } + .filter { it.roomId != filterRoomId && it.directUserId != null && it.membership.isActive() } .groupByTo(mutableMapOf(), { it.directUserId!! }, { it.roomId }) } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/user/accountdata/UserAccountDataDataSource.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/user/accountdata/UserAccountDataDataSource.kt index f64b1bdd2ecc7db38fb446b96104f52a52b6f978..b36bdc80f889be918a36a413d87123a252428886 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/user/accountdata/UserAccountDataDataSource.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/user/accountdata/UserAccountDataDataSource.kt @@ -21,7 +21,7 @@ import androidx.lifecycle.Transformations import com.zhuinden.monarchy.Monarchy import io.realm.Realm import io.realm.RealmQuery -import org.matrix.android.sdk.api.session.accountdata.AccountDataEvent +import org.matrix.android.sdk.api.session.accountdata.UserAccountDataEvent import org.matrix.android.sdk.api.util.Optional import org.matrix.android.sdk.api.util.toOptional import org.matrix.android.sdk.internal.database.RealmSessionProvider @@ -35,23 +35,23 @@ internal class UserAccountDataDataSource @Inject constructor(@SessionDatabase pr private val realmSessionProvider: RealmSessionProvider, private val accountDataMapper: AccountDataMapper) { - fun getAccountDataEvent(type: String): AccountDataEvent? { + fun getAccountDataEvent(type: String): UserAccountDataEvent? { return getAccountDataEvents(setOf(type)).firstOrNull() } - fun getLiveAccountDataEvent(type: String): LiveData<Optional<AccountDataEvent>> { + fun getLiveAccountDataEvent(type: String): LiveData<Optional<UserAccountDataEvent>> { return Transformations.map(getLiveAccountDataEvents(setOf(type))) { it.firstOrNull()?.toOptional() } } - fun getAccountDataEvents(types: Set<String>): List<AccountDataEvent> { + fun getAccountDataEvents(types: Set<String>): List<UserAccountDataEvent> { return realmSessionProvider.withRealm { accountDataEventsQuery(it, types).findAll().map(accountDataMapper::map) } } - fun getLiveAccountDataEvents(types: Set<String>): LiveData<List<AccountDataEvent>> { + fun getLiveAccountDataEvents(types: Set<String>): LiveData<List<UserAccountDataEvent>> { return monarchy.findAllMappedWithChanges( { accountDataEventsQuery(it, types) }, accountDataMapper::map diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/widgets/WidgetManager.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/widgets/WidgetManager.kt index ca1a129da79e262486fe5926df4bb9c847fb0f0f..e0f43a11c570bceb4b94c61a004291d3048c0f83 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/widgets/WidgetManager.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/widgets/WidgetManager.kt @@ -23,7 +23,7 @@ import androidx.lifecycle.LiveData import androidx.lifecycle.Transformations import org.matrix.android.sdk.api.query.QueryStringValue import org.matrix.android.sdk.api.session.Session -import org.matrix.android.sdk.api.session.accountdata.AccountDataEvent +import org.matrix.android.sdk.api.session.accountdata.UserAccountDataEvent import org.matrix.android.sdk.api.session.accountdata.UserAccountDataTypes import org.matrix.android.sdk.api.session.events.model.Content import org.matrix.android.sdk.api.session.events.model.Event @@ -47,7 +47,7 @@ import javax.inject.Inject @SessionScope internal class WidgetManager @Inject constructor(private val integrationManager: IntegrationManager, - private val accountDataDataSource: UserAccountDataDataSource, + private val userAccountDataDataSource: UserAccountDataDataSource, private val stateEventDataSource: StateEventDataSource, private val createWidgetTask: CreateWidgetTask, private val widgetFactory: WidgetFactory, @@ -136,7 +136,7 @@ internal class WidgetManager @Inject constructor(private val integrationManager: widgetTypes: Set<String>? = null, excludedTypes: Set<String>? = null ): LiveData<List<Widget>> { - val widgetsAccountData = accountDataDataSource.getLiveAccountDataEvent(UserAccountDataTypes.TYPE_WIDGETS) + val widgetsAccountData = userAccountDataDataSource.getLiveAccountDataEvent(UserAccountDataTypes.TYPE_WIDGETS) return Transformations.map(widgetsAccountData) { it.getOrNull()?.mapToWidgets(widgetTypes, excludedTypes).orEmpty() } @@ -146,12 +146,12 @@ internal class WidgetManager @Inject constructor(private val integrationManager: widgetTypes: Set<String>? = null, excludedTypes: Set<String>? = null ): List<Widget> { - val widgetsAccountData = accountDataDataSource.getAccountDataEvent(UserAccountDataTypes.TYPE_WIDGETS) ?: return emptyList() + val widgetsAccountData = userAccountDataDataSource.getAccountDataEvent(UserAccountDataTypes.TYPE_WIDGETS) ?: return emptyList() return widgetsAccountData.mapToWidgets(widgetTypes, excludedTypes) } - private fun AccountDataEvent.mapToWidgets(widgetTypes: Set<String>? = null, - excludedTypes: Set<String>? = null): List<Widget> { + private fun UserAccountDataEvent.mapToWidgets(widgetTypes: Set<String>? = null, + excludedTypes: Set<String>? = null): List<Widget> { return extractWidgetSequence(widgetFactory) .filter { val widgetType = it.widgetContent.type ?: return@filter false diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/widgets/helper/UserAccountWidgets.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/widgets/helper/UserAccountWidgets.kt index 5aa32d5a31751f5f928e6be47cab0d1da907340d..6f423b38a09ed42f3f4ed74f69761bf6ad869d29 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/widgets/helper/UserAccountWidgets.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/widgets/helper/UserAccountWidgets.kt @@ -19,10 +19,10 @@ package org.matrix.android.sdk.internal.session.widgets.helper import org.matrix.android.sdk.api.session.events.model.Event import org.matrix.android.sdk.api.session.events.model.toModel import org.matrix.android.sdk.api.util.JsonDict -import org.matrix.android.sdk.api.session.accountdata.AccountDataEvent +import org.matrix.android.sdk.api.session.accountdata.UserAccountDataEvent import org.matrix.android.sdk.api.session.widgets.model.Widget -internal fun AccountDataEvent.extractWidgetSequence(widgetFactory: WidgetFactory): Sequence<Widget> { +internal fun UserAccountDataEvent.extractWidgetSequence(widgetFactory: WidgetFactory): Sequence<Widget> { return content.asSequence() .mapNotNull { @Suppress("UNCHECKED_CAST") diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/util/file/AtomicFileCreator.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/util/file/AtomicFileCreator.kt new file mode 100644 index 0000000000000000000000000000000000000000..ca10c0ed0f85cecb5b098e3f80d758bfeca0a9e9 --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/util/file/AtomicFileCreator.kt @@ -0,0 +1,42 @@ +/* + * Copyright (c) 2021 The Matrix.org Foundation C.I.C. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.matrix.android.sdk.internal.util.file + +import timber.log.Timber +import java.io.File + +internal class AtomicFileCreator(private val file: File) { + val partFile = File(file.parentFile, "${file.name}.part") + + init { + if (file.exists()) { + Timber.w("## AtomicFileCreator: target file ${file.path} exists, it should not happen.") + } + if (partFile.exists()) { + Timber.d("## AtomicFileCreator: discard aborted part file ${partFile.path}") + // No need to delete the file, we will overwrite it + } + } + + fun cancel() { + partFile.delete() + } + + fun commit() { + partFile.renameTo(file) + } +} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/wellknown/GetWellknownTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/wellknown/GetWellknownTask.kt index 7a9beac8c08e1d8db524a90cc0308ee6054dc967..f11e87e1e72bdf551191bc8d7ae3fdb456e91077 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/wellknown/GetWellknownTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/wellknown/GetWellknownTask.kt @@ -18,7 +18,6 @@ package org.matrix.android.sdk.internal.wellknown import android.util.MalformedJsonException import dagger.Lazy -import org.matrix.android.sdk.api.MatrixPatterns import org.matrix.android.sdk.api.auth.data.HomeServerConnectionConfig import org.matrix.android.sdk.api.auth.data.WellKnown import org.matrix.android.sdk.api.auth.wellknown.WellknownResult @@ -39,7 +38,11 @@ import javax.net.ssl.HttpsURLConnection internal interface GetWellknownTask : Task<GetWellknownTask.Params, WellknownResult> { data class Params( - val matrixId: String, + /** + * domain, for instance "matrix.org" + * the URL will be https://{domain}/.well-known/matrix/client + */ + val domain: String, val homeServerConnectionConfig: HomeServerConnectionConfig? ) } @@ -54,14 +57,8 @@ internal class DefaultGetWellknownTask @Inject constructor( ) : GetWellknownTask { override suspend fun execute(params: GetWellknownTask.Params): WellknownResult { - if (!MatrixPatterns.isUserId(params.matrixId)) { - return WellknownResult.InvalidMatrixId - } - - val homeServerDomain = params.matrixId.substringAfter(":") - val client = buildClient(params.homeServerConnectionConfig) - return findClientConfig(homeServerDomain, client) + return findClientConfig(params.domain, client) } private fun buildClient(homeServerConnectionConfig: HomeServerConnectionConfig?): OkHttpClient { @@ -133,7 +130,7 @@ internal class DefaultGetWellknownTask @Inject constructor( } /** - * Return true if home server is valid, and (if applicable) if identity server is pingable + * Return true if homeserver is valid, and (if applicable) if identity server is pingable */ private suspend fun validateHomeServer(homeServerBaseUrl: String, wellKnown: WellKnown, client: OkHttpClient): WellknownResult { val capabilitiesAPI = retrofitFactory.create(client, homeServerBaseUrl) @@ -189,7 +186,7 @@ internal class DefaultGetWellknownTask @Inject constructor( } /** - * Try to get an identity server URL from a home server URL, using a .wellknown request + * Try to get an identity server URL from a homeserver URL, using a .wellknown request */ /* fun getIdentityServer(homeServerUrl: String, callback: ApiCallback<String?>) { diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/worker/WorkerParamsFactory.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/worker/WorkerParamsFactory.kt index 7aed74d2ca64dd898f6402bee40aba097196126e..7a47270efd3460489f78add0608b2b932680335e 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/worker/WorkerParamsFactory.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/worker/WorkerParamsFactory.kt @@ -46,7 +46,7 @@ internal object WorkerParamsFactory { inline fun <reified T> fromData(data: Data) = fromData(T::class.java, data) - fun <T> fromData(clazz: Class<T>, data: Data): T? = tryOrNull("Unable to parse work parameters") { + fun <T> fromData(clazz: Class<T>, data: Data): T? = tryOrNull<T?>("Unable to parse work parameters") { val json = data.getString(KEY) return if (json == null) { null diff --git a/matrix-sdk-android/src/main/java/org/matrix/androidsdk/crypto/data/MXDeviceInfo.java b/matrix-sdk-android/src/main/java/org/matrix/androidsdk/crypto/data/MXDeviceInfo.java index 393c633c6aaee6e0e63109dfa989c8412345b649..1014ceda0e56a5988cc4abaa84cb24d0c9588683 100755 --- a/matrix-sdk-android/src/main/java/org/matrix/androidsdk/crypto/data/MXDeviceInfo.java +++ b/matrix-sdk-android/src/main/java/org/matrix/androidsdk/crypto/data/MXDeviceInfo.java @@ -66,7 +66,7 @@ public class MXDeviceInfo implements Serializable { public Map<String, Map<String, String>> signatures; /* - * Additional data from the home server. + * Additional data from the homeserver. */ public Map<String, Object> unsigned; @@ -81,4 +81,4 @@ public class MXDeviceInfo implements Serializable { public MXDeviceInfo() { mVerified = DEVICE_VERIFICATION_UNKNOWN; } -} \ No newline at end of file +} diff --git a/matrix-sdk-android/src/main/res/values-fr/strings_sas.xml b/matrix-sdk-android/src/main/res/values-fr/strings_sas.xml index af9d797542edaf6ad53ee88a923c036f7bb1a35b..e7d6ef8058f188a4c33a7f609e5be3c1d25f321c 100644 --- a/matrix-sdk-android/src/main/res/values-fr/strings_sas.xml +++ b/matrix-sdk-android/src/main/res/values-fr/strings_sas.xml @@ -33,11 +33,11 @@ <string name="verification_emoji_heart">CÅ“ur</string> <string name="verification_emoji_smiley">Sourire</string> <string name="verification_emoji_robot">Robot</string> - <string name="verification_emoji_hat">Châpeau</string> + <string name="verification_emoji_hat">Chapeau</string> <string name="verification_emoji_glasses">Lunettes</string> <string name="verification_emoji_spanner">Clé à molette</string> <string name="verification_emoji_santa">Père Noël</string> - <string name="verification_emoji_thumbs_up">Pouce en l\'air</string> + <string name="verification_emoji_thumbs_up">Pouce en l’air</string> <string name="verification_emoji_umbrella">Parapluie</string> <string name="verification_emoji_hourglass">Sablier</string> <string name="verification_emoji_clock">Réveil</string> diff --git a/matrix-sdk-android/src/main/res/values-hu/strings_sas.xml b/matrix-sdk-android/src/main/res/values-hu/strings_sas.xml new file mode 100644 index 0000000000000000000000000000000000000000..5b5e0e0205f1b943ae3860fb5c8a70025a38a09a --- /dev/null +++ b/matrix-sdk-android/src/main/res/values-hu/strings_sas.xml @@ -0,0 +1,68 @@ +<?xml version="1.0" encoding="utf-8"?> +<resources> + <!-- Generated file, do not edit --> + <string name="verification_emoji_dog">Kutya</string> + <string name="verification_emoji_cat">Macska</string> + <string name="verification_emoji_lion">Oroszlán</string> + <string name="verification_emoji_horse">Ló</string> + <string name="verification_emoji_unicorn">Egyszarvú</string> + <string name="verification_emoji_pig">Malac</string> + <string name="verification_emoji_elephant">Elefánt</string> + <string name="verification_emoji_rabbit">Nyúl</string> + <string name="verification_emoji_panda">Panda</string> + <string name="verification_emoji_rooster">Kakas</string> + <string name="verification_emoji_penguin">Pingvin</string> + <string name="verification_emoji_turtle">TeknÅ‘s</string> + <string name="verification_emoji_fish">Hal</string> + <string name="verification_emoji_octopus">Polip</string> + <string name="verification_emoji_butterfly">Pillangó</string> + <string name="verification_emoji_flower">Virág</string> + <string name="verification_emoji_tree">Fa</string> + <string name="verification_emoji_cactus">Kaktusz</string> + <string name="verification_emoji_mushroom">Gomba</string> + <string name="verification_emoji_globe">Földgömb</string> + <string name="verification_emoji_moon">Hold</string> + <string name="verification_emoji_cloud">FelhÅ‘</string> + <string name="verification_emoji_fire">Tűz</string> + <string name="verification_emoji_banana">Banán</string> + <string name="verification_emoji_apple">Alma</string> + <string name="verification_emoji_strawberry">Eper</string> + <string name="verification_emoji_corn">Kukorica</string> + <string name="verification_emoji_pizza">Pizza</string> + <string name="verification_emoji_cake">Süti</string> + <string name="verification_emoji_heart">SzÃv</string> + <string name="verification_emoji_smiley">Mosoly</string> + <string name="verification_emoji_robot">Robot</string> + <string name="verification_emoji_hat">Kalap</string> + <string name="verification_emoji_glasses">Szemüveg</string> + <string name="verification_emoji_spanner">Csavarkulcs</string> + <string name="verification_emoji_santa">Télapó</string> + <string name="verification_emoji_thumbs_up">Hüvelykujj fel</string> + <string name="verification_emoji_umbrella">EsernyÅ‘</string> + <string name="verification_emoji_hourglass">Homokóra</string> + <string name="verification_emoji_clock">Óra</string> + <string name="verification_emoji_gift">Ajándék</string> + <string name="verification_emoji_light_bulb">ÉgÅ‘</string> + <string name="verification_emoji_book">Könyv</string> + <string name="verification_emoji_pencil">Ceruza</string> + <string name="verification_emoji_paperclip">Gémkapocs</string> + <string name="verification_emoji_scissors">Olló</string> + <string name="verification_emoji_lock">Lakat</string> + <string name="verification_emoji_key">Kulcs</string> + <string name="verification_emoji_hammer">Kalapács</string> + <string name="verification_emoji_telephone">Telefon</string> + <string name="verification_emoji_flag">Zászló</string> + <string name="verification_emoji_train">Vonat</string> + <string name="verification_emoji_bicycle">Kerékpár</string> + <string name="verification_emoji_aeroplane">RepülÅ‘</string> + <string name="verification_emoji_rocket">Rakáta</string> + <string name="verification_emoji_trophy">Trófea</string> + <string name="verification_emoji_ball">Labda</string> + <string name="verification_emoji_guitar">Gitár</string> + <string name="verification_emoji_trumpet">Trombita</string> + <string name="verification_emoji_bell">Harang</string> + <string name="verification_emoji_anchor">Horgony</string> + <string name="verification_emoji_headphones">Fejhallgató</string> + <string name="verification_emoji_folder">Mappa</string> + <string name="verification_emoji_pin">Rajszeg</string> +</resources> diff --git a/matrix-sdk-android/src/main/res/values-tzm/strings_sas.xml b/matrix-sdk-android/src/main/res/values-tzm/strings_sas.xml new file mode 100644 index 0000000000000000000000000000000000000000..bebf64e5abdb3b59d8b65875ff95328a31fbe56c --- /dev/null +++ b/matrix-sdk-android/src/main/res/values-tzm/strings_sas.xml @@ -0,0 +1,30 @@ +<?xml version="1.0" encoding="utf-8"?> +<resources> + <!-- Generated file, do not edit --> + <string name="verification_emoji_dog">Aydi</string> + <string name="verification_emoji_cat">Amuc</string> + <string name="verification_emoji_lion">Izem</string> + <string name="verification_emoji_horse">Ayyis</string> + <string name="verification_emoji_pig">Ilef</string> + <string name="verification_emoji_elephant">Ilu</string> + <string name="verification_emoji_rabbit">Agnin</string> + <string name="verification_emoji_rooster">Ayaẓiá¸</string> + <string name="verification_emoji_turtle">Ifker</string> + <string name="verification_emoji_fish">Aselm</string> + <string name="verification_emoji_tree">Aseklu</string> + <string name="verification_emoji_mushroom">Agursel</string> + <string name="verification_emoji_moon">Ayyur</string> + <string name="verification_emoji_fire">Timessi</string> + <string name="verification_emoji_banana">Tabanant</string> + <string name="verification_emoji_apple">Tadeffuyt</string> + <string name="verification_emoji_heart">Ul</string> + <string name="verification_emoji_robot">Aá¹›ubu</string> + <string name="verification_emoji_hat">Taraza</string> + <string name="verification_emoji_book">Adlis</string> + <string name="verification_emoji_key">Tasarut</string> + <string name="verification_emoji_telephone">Atilifun</string> + <string name="verification_emoji_flag">Acenyal</string> + <string name="verification_emoji_ball">Tcama</string> + <string name="verification_emoji_guitar">Agiá¹aá¹›</string> + <string name="verification_emoji_folder">Asdaw</string> +</resources> diff --git a/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/session/room/send/WaveFormSanitizerTest.kt b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/session/room/send/WaveFormSanitizerTest.kt new file mode 100644 index 0000000000000000000000000000000000000000..23c8aeb76bbc212909d6aacd2be5ef1592449561 --- /dev/null +++ b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/session/room/send/WaveFormSanitizerTest.kt @@ -0,0 +1,123 @@ +/* + * Copyright (c) 2021 The Matrix.org Foundation C.I.C. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.matrix.android.sdk.internal.session.room.send + +import org.amshove.kluent.shouldBe +import org.amshove.kluent.shouldBeInRange +import org.junit.Test + +class WaveFormSanitizerTest { + + private val waveFormSanitizer = WaveFormSanitizer() + + @Test + fun sanitizeNull() { + waveFormSanitizer.sanitize(null) shouldBe null + } + + @Test + fun sanitizeEmpty() { + waveFormSanitizer.sanitize(emptyList()) shouldBe null + } + + @Test + fun sanitizeSingleton() { + val result = waveFormSanitizer.sanitize(listOf(1))!! + result.size shouldBe 30 + checkResult(result) + } + + @Test + fun sanitize29() { + val list = generateSequence { 1 }.take(29).toList() + val result = waveFormSanitizer.sanitize(list)!! + checkResult(result) + } + + @Test + fun sanitize30() { + val list = generateSequence { 1 }.take(30).toList() + val result = waveFormSanitizer.sanitize(list)!! + result.size shouldBe 30 + checkResult(result) + } + + @Test + fun sanitize31() { + val list = generateSequence { 1 }.take(31).toList() + val result = waveFormSanitizer.sanitize(list)!! + checkResult(result) + } + + @Test + fun sanitize119() { + val list = generateSequence { 1 }.take(119).toList() + val result = waveFormSanitizer.sanitize(list)!! + checkResult(result) + } + + @Test + fun sanitize120() { + val list = generateSequence { 1 }.take(120).toList() + val result = waveFormSanitizer.sanitize(list)!! + result.size shouldBe 120 + checkResult(result) + } + + @Test + fun sanitize121() { + val list = generateSequence { 1 }.take(121).toList() + val result = waveFormSanitizer.sanitize(list)!! + checkResult(result) + } + + @Test + fun sanitize1024() { + val list = generateSequence { 1 }.take(1024).toList() + val result = waveFormSanitizer.sanitize(list)!! + checkResult(result) + } + + @Test + fun sanitizeNegative() { + val list = generateSequence { -1 }.take(30).toList() + val result = waveFormSanitizer.sanitize(list)!! + checkResult(result) + } + + @Test + fun sanitizeMaxValue() { + val list = generateSequence { 1025 }.take(30).toList() + val result = waveFormSanitizer.sanitize(list)!! + checkResult(result) + } + + @Test + fun sanitizeNegativeMaxValue() { + val list = generateSequence { -1025 }.take(30).toList() + val result = waveFormSanitizer.sanitize(list)!! + checkResult(result) + } + + private fun checkResult(result: List<Int>) { + result.forEach { + it shouldBeInRange 0..1024 + } + + result.size shouldBeInRange 30..120 + } +} diff --git a/tools/import_from_element.sh b/tools/import_from_element.sh index 331df43c2d9c244266a5532f99b0478a0032d065..8d64b6db6b3ff6ae5d68d5b2c2b3c57793ec2128 100755 --- a/tools/import_from_element.sh +++ b/tools/import_from_element.sh @@ -42,7 +42,7 @@ git add -A read -p "Press enter to build the library (update the version first?)" # Build the library -./gradlew clean assembleRelease +./gradlew clean assembleRelease --stacktrace # Success