diff --git a/CHANGES.md b/CHANGES.md index 4f580c4e5d7b379dc86ab738907aad1b89b6b8a4..cc0f3ac3a89e948abb9ae6abafc9e47aaacd69c3 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,5 +1,22 @@ Please also refer to the Changelog of Element Android: https://github.com/vector-im/element-android/blob/main/CHANGES.md +Changes in Matrix-SDK 1.3.18 (2022-02-04) +=================================================== + +**Warning**: This release may trigger an initial sync. + +Imported from Element 1.3.18. (https://github.com/vector-im/element-android/releases/tag/v1.3.18) + +Bugfixes 🛠+---------- + - Avoid deleting root event of CurrentState on gappy sync. In order to restore lost Events an initial sync may be triggered. ([#5137](https://github.com/vector-im/element-android/issues/5137)) + +SDK API changes âš ï¸ +------------------ + - `StateService.sendStateEvent()` now takes a non-nullable String for the parameter `stateKey`. If null was used, just now use an empty string. ([#4895](https://github.com/vector-im/element-android/issues/4895)) + - 429 are not automatically retried anymore in case of too long retry delay ([#4995](https://github.com/vector-im/element-android/issues/4995)) + + Changes in Matrix-SDK 1.3.14 (2022-01-12) =================================================== diff --git a/dependencies.gradle b/dependencies.gradle index 6cb5fac64c284f53b129fe7f499d354975ca0076..77d072e7c7f9c0a8dffb42f7d35c267fd8b4680e 100644 --- a/dependencies.gradle +++ b/dependencies.gradle @@ -29,6 +29,7 @@ def vanniktechEmoji = "0.8.0" def mockk = "1.12.1" def espresso = "3.4.0" def androidxTest = "1.4.0" +def androidxOrchestrator = "1.4.1" ext.libs = [ @@ -41,7 +42,6 @@ ext.libs = [ jetbrains : [ 'coroutinesCore' : "org.jetbrains.kotlinx:kotlinx-coroutines-core:$kotlinCoroutines", 'coroutinesAndroid' : "org.jetbrains.kotlinx:kotlinx-coroutines-android:$kotlinCoroutines", - 'coroutinesRx2' : "org.jetbrains.kotlinx:kotlinx-coroutines-rx2:$kotlinCoroutines", 'coroutinesTest' : "org.jetbrains.kotlinx:kotlinx-coroutines-test:$kotlinCoroutines" ], androidx : [ @@ -63,7 +63,7 @@ ext.libs = [ 'pagingRuntimeKtx' : "androidx.paging:paging-runtime-ktx:2.1.2", 'coreTesting' : "androidx.arch.core:core-testing:2.1.0", 'testCore' : "androidx.test:core:$androidxTest", - 'orchestrator' : "androidx.test:orchestrator:$androidxTest", + 'orchestrator' : "androidx.test:orchestrator:$androidxOrchestrator", 'testRunner' : "androidx.test:runner:$androidxTest", 'testRules' : "androidx.test:rules:$androidxTest", 'espressoCore' : "androidx.test.espresso:espresso-core:$espresso", @@ -71,6 +71,7 @@ ext.libs = [ 'espressoIntents' : "androidx.test.espresso:espresso-intents:$espresso" ], google : [ + // TODO There is 1.6.0? 'material' : "com.google.android.material:material:1.4.0" ], dagger : [ @@ -86,8 +87,7 @@ ext.libs = [ 'retrofitMoshi' : "com.squareup.retrofit2:converter-moshi:$retrofit" ], rx : [ - 'rxKotlin' : "io.reactivex.rxjava2:rxkotlin:2.4.0", - 'rxAndroid' : "io.reactivex.rxjava2:rxandroid:2.1.1" + 'rxKotlin' : "io.reactivex.rxjava2:rxkotlin:2.4.0" ], arrow : [ 'core' : "io.arrow-kt:arrow-core:$arrow", diff --git a/dependencies_groups.gradle b/dependencies_groups.gradle index 3853919bcb14b06683f990ff2801f3f88c1846bb..7de8100469ba5238322de827938b302b31932eab 100644 --- a/dependencies_groups.gradle +++ b/dependencies_groups.gradle @@ -4,7 +4,6 @@ ext.groups = [ ], group: [ 'com.github.Armen101', - 'com.github.BillCarsonFr', 'com.github.chrisbanes', 'com.github.hyuwah', 'com.github.jetradarmobile', @@ -84,6 +83,7 @@ ext.groups = [ 'com.jakewharton.android.repackaged', 'com.jakewharton.timber', 'com.linkedin.dexmaker', + 'com.mapbox.mapboxsdk', 'com.nulab-inc', 'com.otaliastudios.opengl', 'com.parse.bolts', @@ -154,11 +154,13 @@ ext.groups = [ 'org.jetbrains.intellij.deps', 'org.jetbrains.kotlin', 'org.jetbrains.kotlinx', + 'org.json', 'org.jsoup', 'org.junit', 'org.junit.jupiter', 'org.junit.platform', 'org.jvnet.staxex', + 'org.maplibre.gl', 'org.matrix.android', 'org.mockito', 'org.mongodb', diff --git a/gradle.properties b/gradle.properties index 48f69eb43f4ce7d3e9dbeb91aab36161c9d56800..cb10f71f955feb13c9f6f98c460ee4bf2cb59860 100644 --- a/gradle.properties +++ b/gradle.properties @@ -26,7 +26,7 @@ vector.httpLogLevel=NONE # Ref: https://github.com/vanniktech/gradle-maven-publish-plugin GROUP=org.matrix.android POM_ARTIFACT_ID=matrix-android-sdk2 -VERSION_NAME=1.3.14 +VERSION_NAME=1.3.18 POM_PACKAGING=aar diff --git a/matrix-sdk-android/build.gradle b/matrix-sdk-android/build.gradle index 7f6d3a91b1b1fc33924f892b7dbde34d717b0fa5..60a1a49249634aa62ceb2a9a461c80a4753a83e0 100644 --- a/matrix-sdk-android/build.gradle +++ b/matrix-sdk-android/build.gradle @@ -48,7 +48,7 @@ android { testOptions { // Comment to run on Android 12 - execution 'ANDROIDX_TEST_ORCHESTRATOR' +// execution 'ANDROIDX_TEST_ORCHESTRATOR' } buildTypes { @@ -67,6 +67,7 @@ android { adbOptions { installOptions "-g" +// timeOutInMs 350 * 1000 } compileOptions { @@ -118,6 +119,11 @@ dependencies { implementation libs.squareup.retrofit implementation libs.squareup.retrofitMoshi + // When version of okhttp is updated (current is 4.9.3), consider removing the workaround + // to force usage of Protocol.HTTP_1_1. Check the status of: + // - https://github.com/square/okhttp/issues/3278 + // - https://github.com/square/okhttp/issues/4455 + // - https://github.com/square/okhttp/issues/3146 implementation(platform("com.squareup.okhttp3:okhttp-bom:4.9.3")) implementation 'com.squareup.okhttp3:okhttp' implementation 'com.squareup.okhttp3:logging-interceptor' @@ -161,7 +167,7 @@ dependencies { implementation libs.apache.commonsImaging // Phone number https://github.com/google/libphonenumber - implementation 'com.googlecode.libphonenumber:libphonenumber:8.12.40' + implementation 'com.googlecode.libphonenumber:libphonenumber:8.12.42' testImplementation libs.tests.junit testImplementation 'org.robolectric:robolectric:4.7.3' diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/account/AccountCreationTest.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/account/AccountCreationTest.kt index e0451bea386ce580572c1c1adb54e8c760ef4f6a..486bc0276950b0a50195818ac3e20984cf00effc 100644 --- a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/account/AccountCreationTest.kt +++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/account/AccountCreationTest.kt @@ -16,7 +16,9 @@ package org.matrix.android.sdk.account +import androidx.test.filters.LargeTest import org.junit.FixMethodOrder +import org.junit.Ignore import org.junit.Test import org.junit.runner.RunWith import org.junit.runners.JUnit4 @@ -29,6 +31,7 @@ import org.matrix.android.sdk.common.TestConstants @RunWith(JUnit4::class) @FixMethodOrder(MethodSorters.JVM) +@LargeTest class AccountCreationTest : InstrumentedTest { private val commonTestHelper = CommonTestHelper(context()) @@ -42,6 +45,7 @@ class AccountCreationTest : InstrumentedTest { } @Test + @Ignore("This test will be ignored until it is fixed") fun createAccountAndLoginAgainTest() { val session = commonTestHelper.createAccount(TestConstants.USER_ALICE, SessionTestParams(withInitialSync = true)) diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/account/ChangePasswordTest.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/account/ChangePasswordTest.kt index d32bcb3fe5b370f2cf4c8d4ae40377bcb5d17566..933074cdce2dc14f6c7d22b85ceaa2645db70d95 100644 --- a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/account/ChangePasswordTest.kt +++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/account/ChangePasswordTest.kt @@ -18,6 +18,7 @@ package org.matrix.android.sdk.account import org.amshove.kluent.shouldBeTrue import org.junit.FixMethodOrder +import org.junit.Ignore import org.junit.Test import org.junit.runner.RunWith import org.junit.runners.JUnit4 @@ -30,6 +31,7 @@ import org.matrix.android.sdk.common.TestConstants @RunWith(JUnit4::class) @FixMethodOrder(MethodSorters.JVM) +@Ignore("This test will be ignored until it is fixed") class ChangePasswordTest : InstrumentedTest { private val commonTestHelper = CommonTestHelper(context()) diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/common/RetryTestRule.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/common/RetryTestRule.kt new file mode 100644 index 0000000000000000000000000000000000000000..b16ab98e6c9fc69d9af8c91be067e196a7cca7a5 --- /dev/null +++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/common/RetryTestRule.kt @@ -0,0 +1,52 @@ +/* + * Copyright 2022 The Matrix.org Foundation C.I.C. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.matrix.android.sdk.common + +import org.junit.rules.TestRule +import org.junit.runner.Description +import org.junit.runners.model.Statement + +/** + * Retry test rule used to retry test that failed. + * Retry failed test 3 times + */ +class RetryTestRule(val retryCount: Int = 3) : TestRule { + + override fun apply(base: Statement, description: Description): Statement { + return statement(base) + } + + private fun statement(base: Statement): Statement { + return object : Statement() { + @Throws(Throwable::class) + override fun evaluate() { + var caughtThrowable: Throwable? = null + + // implement retry logic here + for (i in 0 until retryCount) { + try { + base.evaluate() + return + } catch (t: Throwable) { + caughtThrowable = t + } + } + throw caughtThrowable!! + } + } + } +} diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/common/TestConstants.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/common/TestConstants.kt index 8eb7e251e248ea36022e127a8ed10feb1832b244..5c9b79361e891d545fbaaea1774a68f8c6f75145 100644 --- a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/common/TestConstants.kt +++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/common/TestConstants.kt @@ -22,8 +22,8 @@ object TestConstants { const val TESTS_HOME_SERVER_URL = "http://10.0.2.2:8080" - // Time out to use when waiting for server response. 20s - private const val AWAIT_TIME_OUT_MILLIS = 20_000 + // Time out to use when waiting for server response. + private const val AWAIT_TIME_OUT_MILLIS = 30_000 // Time out to use when waiting for server response, when the debugger is connected. 10 minutes private const val AWAIT_TIME_OUT_WITH_DEBUGGER_MILLIS = 10 * 60_000 diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/PreShareKeysTest.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/PreShareKeysTest.kt index d0f63227f59c424b2720e30181bd0b4d0db71aef..c95cc6b4ca352fb0accd97297448151b49d20d7f 100644 --- a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/PreShareKeysTest.kt +++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/PreShareKeysTest.kt @@ -21,6 +21,7 @@ import androidx.test.ext.junit.runners.AndroidJUnit4 import org.junit.Assert.assertEquals import org.junit.Assert.assertNotNull import org.junit.FixMethodOrder +import org.junit.Ignore import org.junit.Test import org.junit.runner.RunWith import org.junit.runners.MethodSorters @@ -40,6 +41,7 @@ class PreShareKeysTest : InstrumentedTest { private val cryptoTestHelper = CryptoTestHelper(testHelper) @Test + @Ignore("This test will be ignored until it is fixed") fun ensure_outbound_session_happy_path() { val testData = cryptoTestHelper.doE2ETestWithAliceAndBobInARoom(true) val e2eRoomID = testData.roomId @@ -97,7 +99,6 @@ class PreShareKeysTest : InstrumentedTest { } } - testHelper.signOutAndClose(aliceSession) - testHelper.signOutAndClose(bobSession) + testData.cleanUp(testHelper) } } diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/UnwedgingTest.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/UnwedgingTest.kt index 458eae6ab237eeb0d443cceb024eee14c12f7629..0a8ce6768078f5d9160ec060853821990abbacf3 100644 --- a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/UnwedgingTest.kt +++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/UnwedgingTest.kt @@ -21,6 +21,7 @@ import org.amshove.kluent.shouldBe import org.junit.Assert import org.junit.Before import org.junit.FixMethodOrder +import org.junit.Ignore import org.junit.Test import org.junit.runner.RunWith import org.junit.runners.MethodSorters @@ -84,6 +85,7 @@ class UnwedgingTest : InstrumentedTest { * -> This is automatically fixed after SDKs restarted the olm session */ @Test + @Ignore("This test will be ignored until it is fixed") fun testUnwedging() { val cryptoTestData = cryptoTestHelper.doE2ETestWithAliceAndBobInARoom() diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/crosssigning/XSigningTest.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/crosssigning/XSigningTest.kt index d9cc7a8ac0f0dd788b35cc8941aef72df018de9e..a6e8f94c910e30eb1c5137dac9c5b554e08937e2 100644 --- a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/crosssigning/XSigningTest.kt +++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/crosssigning/XSigningTest.kt @@ -17,6 +17,7 @@ package org.matrix.android.sdk.internal.crypto.crosssigning import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.LargeTest import org.junit.Assert.assertEquals import org.junit.Assert.assertFalse import org.junit.Assert.assertNotNull @@ -24,6 +25,7 @@ import org.junit.Assert.assertNull import org.junit.Assert.assertTrue import org.junit.Assert.fail import org.junit.FixMethodOrder +import org.junit.Ignore import org.junit.Test import org.junit.runner.RunWith import org.junit.runners.MethodSorters @@ -43,6 +45,7 @@ import kotlin.coroutines.resume @RunWith(AndroidJUnit4::class) @FixMethodOrder(MethodSorters.NAME_ASCENDING) +@LargeTest class XSigningTest : InstrumentedTest { private val testHelper = CommonTestHelper(context()) @@ -124,11 +127,11 @@ class XSigningTest : InstrumentedTest { assertFalse("Bob keys from alice pov should not be trusted", bobKeysFromAlicePOV.isTrusted()) - testHelper.signOutAndClose(aliceSession) - testHelper.signOutAndClose(bobSession) + cryptoTestData.cleanUp(testHelper) } @Test + @Ignore("This test will be ignored until it is fixed") fun test_CrossSigningTestAliceTrustBobNewDevice() { val cryptoTestData = cryptoTestHelper.doE2ETestWithAliceAndBobInARoom() diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/encryption/EncryptionTest.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/encryption/EncryptionTest.kt index 189fc405eb8ddc4a581dc3e52563442919ffbed7..060201d62452ad858d8a8c6bdd8340445462f47c 100644 --- a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/encryption/EncryptionTest.kt +++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/encryption/EncryptionTest.kt @@ -62,7 +62,7 @@ class EncryptionTest : InstrumentedTest { // Send an encryption Event as a State Event room.sendStateEvent( eventType = EventType.STATE_ROOM_ENCRYPTION, - stateKey = null, + stateKey = "", body = EncryptionEventContent(algorithm = MXCRYPTO_ALGORITHM_MEGOLM).toContent() ) } diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/gossiping/KeyShareTests.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/gossiping/KeyShareTests.kt index 975d4816286fc615f0294833b5e4e0b29731dfa8..e0605db0b830024dd6eb982043b013b56ea5c523 100644 --- a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/gossiping/KeyShareTests.kt +++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/gossiping/KeyShareTests.kt @@ -18,12 +18,14 @@ package org.matrix.android.sdk.internal.crypto.gossiping import android.util.Log import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.LargeTest import junit.framework.TestCase.assertEquals import junit.framework.TestCase.assertNotNull import junit.framework.TestCase.assertTrue import junit.framework.TestCase.fail import org.junit.Assert import org.junit.FixMethodOrder +import org.junit.Ignore import org.junit.Test import org.junit.runner.RunWith import org.junit.runners.MethodSorters @@ -59,11 +61,13 @@ import kotlin.coroutines.resume @RunWith(AndroidJUnit4::class) @FixMethodOrder(MethodSorters.JVM) +@LargeTest class KeyShareTests : InstrumentedTest { private val commonTestHelper = CommonTestHelper(context()) @Test + @Ignore("This test will be ignored until it is fixed") fun test_DoNotSelfShareIfNotTrusted() { val aliceSession = commonTestHelper.createAccount(TestConstants.USER_ALICE, SessionTestParams(true)) @@ -195,6 +199,7 @@ class KeyShareTests : InstrumentedTest { } @Test + @Ignore("This test will be ignored until it is fixed") fun test_ShareSSSSSecret() { val aliceSession1 = commonTestHelper.createAccount(TestConstants.USER_ALICE, SessionTestParams(true)) @@ -307,6 +312,7 @@ class KeyShareTests : InstrumentedTest { } @Test + @Ignore("This test will be ignored until it is fixed") fun test_ImproperKeyShareBug() { val aliceSession = commonTestHelper.createAccount(TestConstants.USER_ALICE, SessionTestParams(true)) diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/gossiping/WithHeldTests.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/gossiping/WithHeldTests.kt index c835c2d40b083e5c8c5bdd6a0a8c0e0926430e93..586d96b0074087e7898c57d1be9bcbd037595072 100644 --- a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/gossiping/WithHeldTests.kt +++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/gossiping/WithHeldTests.kt @@ -18,8 +18,10 @@ package org.matrix.android.sdk.internal.crypto.gossiping import android.util.Log import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.LargeTest import org.junit.Assert import org.junit.FixMethodOrder +import org.junit.Ignore import org.junit.Test import org.junit.runner.RunWith import org.junit.runners.MethodSorters @@ -39,12 +41,14 @@ import org.matrix.android.sdk.internal.crypto.model.event.WithHeldCode @RunWith(AndroidJUnit4::class) @FixMethodOrder(MethodSorters.JVM) +@LargeTest class WithHeldTests : InstrumentedTest { private val testHelper = CommonTestHelper(context()) private val cryptoTestHelper = CryptoTestHelper(testHelper) @Test + @Ignore("This test will be ignored until it is fixed") fun test_WithHeldUnverifiedReason() { // ============================= // ARRANGE @@ -129,6 +133,7 @@ class WithHeldTests : InstrumentedTest { } @Test + @Ignore("This test will be ignored until it is fixed") fun test_WithHeldNoOlm() { val testData = cryptoTestHelper.doE2ETestWithAliceAndBobInARoom() val aliceSession = testData.firstSession @@ -199,6 +204,7 @@ class WithHeldTests : InstrumentedTest { } @Test + @Ignore("This test will be ignored until it is fixed") fun test_WithHeldKeyRequest() { val testData = cryptoTestHelper.doE2ETestWithAliceAndBobInARoom() val aliceSession = testData.firstSession 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 2a07b74115dd979bb41a712c93fde374994f682e..4c9456621947a30cc94bfc7567de6030bf95538e 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 @@ -17,12 +17,14 @@ package org.matrix.android.sdk.internal.crypto.keysbackup import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.LargeTest import org.junit.Assert.assertEquals import org.junit.Assert.assertFalse import org.junit.Assert.assertNotNull import org.junit.Assert.assertNull import org.junit.Assert.assertTrue import org.junit.FixMethodOrder +import org.junit.Ignore import org.junit.Test import org.junit.runner.RunWith import org.junit.runners.MethodSorters @@ -47,6 +49,7 @@ import java.util.concurrent.CountDownLatch @RunWith(AndroidJUnit4::class) @FixMethodOrder(MethodSorters.JVM) +@LargeTest class KeysBackupTest : InstrumentedTest { private val testHelper = CommonTestHelper(context()) @@ -59,6 +62,7 @@ class KeysBackupTest : InstrumentedTest { * - Reset keys backup markers */ @Test + @Ignore("This test will be ignored until it is fixed") fun roomKeysTest_testBackupStore_ok() { val cryptoTestData = cryptoTestHelper.doE2ETestWithAliceAndBobInARoomWithEncryptedMessages() @@ -157,6 +161,7 @@ class KeysBackupTest : InstrumentedTest { * - Check the backup completes */ @Test + @Ignore("This test will be ignored until it is fixed") fun backupAfterCreateKeysBackupVersionTest() { val cryptoTestData = cryptoTestHelper.doE2ETestWithAliceAndBobInARoomWithEncryptedMessages() @@ -197,6 +202,7 @@ class KeysBackupTest : InstrumentedTest { * Check that backupAllGroupSessions() returns valid data */ @Test + @Ignore("This test will be ignored until it is fixed") fun backupAllGroupSessionsTest() { val cryptoTestData = cryptoTestHelper.doE2ETestWithAliceAndBobInARoomWithEncryptedMessages() @@ -241,6 +247,7 @@ class KeysBackupTest : InstrumentedTest { * - Compare the decrypted megolm key with the original one */ @Test + @Ignore("This test will be ignored until it is fixed") fun testEncryptAndDecryptKeysBackupData() { val cryptoTestData = cryptoTestHelper.doE2ETestWithAliceAndBobInARoomWithEncryptedMessages() @@ -282,6 +289,7 @@ class KeysBackupTest : InstrumentedTest { * - Restore must be successful */ @Test + @Ignore("This test will be ignored until it is fixed") fun restoreKeysBackupTest() { val testData = keysBackupTestHelper.createKeysBackupScenarioWithPassword(null) @@ -365,6 +373,7 @@ class KeysBackupTest : InstrumentedTest { * - It must be trusted and must have with 2 signatures now */ @Test + @Ignore("This test will be ignored until it is fixed") fun trustKeyBackupVersionTest() { // - Do an e2e backup to the homeserver with a recovery key // - And log Alice on a new device @@ -424,6 +433,7 @@ class KeysBackupTest : InstrumentedTest { * - It must be trusted and must have with 2 signatures now */ @Test + @Ignore("This test will be ignored until it is fixed") fun trustKeyBackupVersionWithRecoveryKeyTest() { // - Do an e2e backup to the homeserver with a recovery key // - And log Alice on a new device @@ -481,6 +491,7 @@ class KeysBackupTest : InstrumentedTest { * - The backup must still be untrusted and disabled */ @Test + @Ignore("This test will be ignored until it is fixed") fun trustKeyBackupVersionWithWrongRecoveryKeyTest() { // - Do an e2e backup to the homeserver with a recovery key // - And log Alice on a new device @@ -522,6 +533,7 @@ class KeysBackupTest : InstrumentedTest { * - It must be trusted and must have with 2 signatures now */ @Test + @Ignore("This test will be ignored until it is fixed") fun trustKeyBackupVersionWithPasswordTest() { val password = "Password" @@ -581,6 +593,7 @@ class KeysBackupTest : InstrumentedTest { * - The backup must still be untrusted and disabled */ @Test + @Ignore("This test will be ignored until it is fixed") fun trustKeyBackupVersionWithWrongPasswordTest() { val password = "Password" val badPassword = "Bad Password" @@ -621,6 +634,7 @@ class KeysBackupTest : InstrumentedTest { * - It must fail */ @Test + @Ignore("This test will be ignored until it is fixed") fun restoreKeysBackupWithAWrongRecoveryKeyTest() { val testData = keysBackupTestHelper.createKeysBackupScenarioWithPassword(null) @@ -654,6 +668,7 @@ class KeysBackupTest : InstrumentedTest { * - Restore must be successful */ @Test + @Ignore("This test will be ignored until it is fixed") fun testBackupWithPassword() { val password = "password" @@ -709,6 +724,7 @@ class KeysBackupTest : InstrumentedTest { * - It must fail */ @Test + @Ignore("This test will be ignored until it is fixed") fun restoreKeysBackupWithAWrongPasswordTest() { val password = "password" val wrongPassword = "passw0rd" @@ -745,6 +761,7 @@ class KeysBackupTest : InstrumentedTest { * - Restore must be successful */ @Test + @Ignore("This test will be ignored until it is fixed") fun testUseRecoveryKeyToRestoreAPasswordBasedKeysBackup() { val password = "password" @@ -773,6 +790,7 @@ class KeysBackupTest : InstrumentedTest { * - It must fail */ @Test + @Ignore("This test will be ignored until it is fixed") fun testUsePasswordToRestoreARecoveryKeyBasedKeysBackup() { val testData = keysBackupTestHelper.createKeysBackupScenarioWithPassword(null) @@ -804,6 +822,7 @@ class KeysBackupTest : InstrumentedTest { * - Check the returned KeysVersionResult is trusted */ @Test + @Ignore("This test will be ignored until it is fixed") fun testIsKeysBackupTrusted() { // - Create a backup version val cryptoTestData = cryptoTestHelper.doE2ETestWithAliceAndBobInARoomWithEncryptedMessages() @@ -847,6 +866,7 @@ class KeysBackupTest : InstrumentedTest { * -> The new alice session must back up to the same version */ @Test + @Ignore("This test will be ignored until it is fixed") fun testCheckAndStartKeysBackupWhenRestartingAMatrixSession() { // - Create a backup version val cryptoTestData = cryptoTestHelper.doE2ETestWithAliceAndBobInARoomWithEncryptedMessages() @@ -978,6 +998,7 @@ class KeysBackupTest : InstrumentedTest { * -> It must success */ @Test + @Ignore("This test will be ignored until it is fixed") fun testBackupAfterVerifyingADevice() { // - Create a backup version val cryptoTestData = cryptoTestHelper.doE2ETestWithAliceAndBobInARoomWithEncryptedMessages() 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 43f8dc07627d65c854826eba951e3e81dab857d3..67f17727b128e02bb8d67e3558de9899d711ddce 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 @@ -22,6 +22,7 @@ import org.junit.Assert.assertEquals import org.junit.Assert.assertNotNull import org.junit.Assert.assertNull import org.junit.FixMethodOrder +import org.junit.Ignore import org.junit.Test import org.junit.runner.RunWith import org.junit.runners.MethodSorters @@ -47,8 +48,6 @@ import org.matrix.android.sdk.internal.crypto.secrets.DefaultSharedSecretStorage @FixMethodOrder(MethodSorters.JVM) class QuadSTests : InstrumentedTest { - private val testHelper = CommonTestHelper(context()) - private val emptyKeySigner = object : KeySigner { override fun sign(canonicalJson: String): Map<String, Map<String, String>>? { return null @@ -57,6 +56,8 @@ class QuadSTests : InstrumentedTest { @Test fun test_Generate4SKey() { + val testHelper = CommonTestHelper(context()) + val aliceSession = testHelper.createAccount(TestConstants.USER_ALICE, SessionTestParams(true)) val quadS = aliceSession.sharedSecretStorageService @@ -108,6 +109,8 @@ class QuadSTests : InstrumentedTest { @Test fun test_StoreSecret() { + val testHelper = CommonTestHelper(context()) + val aliceSession = testHelper.createAccount(TestConstants.USER_ALICE, SessionTestParams(true)) val keyId = "My.Key" val info = generatedSecret(aliceSession, keyId, true) @@ -151,6 +154,8 @@ class QuadSTests : InstrumentedTest { @Test fun test_SetDefaultLocalEcho() { + val testHelper = CommonTestHelper(context()) + val aliceSession = testHelper.createAccount(TestConstants.USER_ALICE, SessionTestParams(true)) val quadS = aliceSession.sharedSecretStorageService @@ -171,6 +176,8 @@ class QuadSTests : InstrumentedTest { @Test fun test_StoreSecretWithMultipleKey() { + val testHelper = CommonTestHelper(context()) + val aliceSession = testHelper.createAccount(TestConstants.USER_ALICE, SessionTestParams(true)) val keyId1 = "Key.1" val key1Info = generatedSecret(aliceSession, keyId1, true) @@ -217,7 +224,10 @@ class QuadSTests : InstrumentedTest { } @Test + @Ignore("Test is working locally, not in GitHub actions") fun test_GetSecretWithBadPassphrase() { + val testHelper = CommonTestHelper(context()) + val aliceSession = testHelper.createAccount(TestConstants.USER_ALICE, SessionTestParams(true)) val keyId1 = "Key.1" val passphrase = "The good pass phrase" @@ -264,6 +274,8 @@ class QuadSTests : InstrumentedTest { } private fun assertAccountData(session: Session, type: String): UserAccountDataEvent { + val testHelper = CommonTestHelper(context()) + var accountData: UserAccountDataEvent? = null testHelper.waitWithLatch { val liveAccountData = session.accountDataService().getLiveUserAccountDataEvent(type) @@ -281,6 +293,7 @@ class QuadSTests : InstrumentedTest { private fun generatedSecret(session: Session, keyId: String, asDefault: Boolean = true): SsssKeyCreationInfo { val quadS = session.sharedSecretStorageService + val testHelper = CommonTestHelper(context()) val creationInfo = testHelper.runBlockingTest { quadS.generateKey(keyId, null, keyId, emptyKeySigner) @@ -300,6 +313,7 @@ class QuadSTests : InstrumentedTest { private fun generatedSecretFromPassphrase(session: Session, passphrase: String, keyId: String, asDefault: Boolean = true): SsssKeyCreationInfo { val quadS = session.sharedSecretStorageService + val testHelper = CommonTestHelper(context()) val creationInfo = testHelper.runBlockingTest { quadS.generateKeyWithPassphrase( diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/verification/SASTest.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/verification/SASTest.kt index c914da6f71fdc9ee40038971266bc948baf49c87..8cd725504d33bfb0e0bdf621654c3440edc9e544 100644 --- a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/verification/SASTest.kt +++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/verification/SASTest.kt @@ -25,6 +25,7 @@ import org.junit.Assert.assertNull import org.junit.Assert.assertTrue import org.junit.Assert.fail import org.junit.FixMethodOrder +import org.junit.Ignore import org.junit.Test import org.junit.runner.RunWith import org.junit.runners.MethodSorters @@ -53,11 +54,11 @@ import java.util.concurrent.CountDownLatch @RunWith(AndroidJUnit4::class) @FixMethodOrder(MethodSorters.NAME_ASCENDING) class SASTest : InstrumentedTest { - private val testHelper = CommonTestHelper(context()) - private val cryptoTestHelper = CryptoTestHelper(testHelper) @Test fun test_aliceStartThenAliceCancel() { + val testHelper = CommonTestHelper(context()) + val cryptoTestHelper = CryptoTestHelper(testHelper) val cryptoTestData = cryptoTestHelper.doE2ETestWithAliceAndBobInARoom() val aliceSession = cryptoTestData.firstSession @@ -137,7 +138,10 @@ class SASTest : InstrumentedTest { } @Test + @Ignore("This test will be ignored until it is fixed") fun test_key_agreement_protocols_must_include_curve25519() { + val testHelper = CommonTestHelper(context()) + val cryptoTestHelper = CryptoTestHelper(testHelper) fail("Not passing for the moment") val cryptoTestData = cryptoTestHelper.doE2ETestWithAliceAndBobInARoom() @@ -194,7 +198,10 @@ class SASTest : InstrumentedTest { } @Test + @Ignore("This test will be ignored until it is fixed") fun test_key_agreement_macs_Must_include_hmac_sha256() { + val testHelper = CommonTestHelper(context()) + val cryptoTestHelper = CryptoTestHelper(testHelper) fail("Not passing for the moment") val cryptoTestData = cryptoTestHelper.doE2ETestWithAliceAndBobInARoom() @@ -232,7 +239,10 @@ class SASTest : InstrumentedTest { } @Test + @Ignore("This test will be ignored until it is fixed") fun test_key_agreement_short_code_include_decimal() { + val testHelper = CommonTestHelper(context()) + val cryptoTestHelper = CryptoTestHelper(testHelper) fail("Not passing for the moment") val cryptoTestData = cryptoTestHelper.doE2ETestWithAliceAndBobInARoom() @@ -303,6 +313,8 @@ class SASTest : InstrumentedTest { // If a device has two verifications in progress with the same device, then it should cancel both verifications. @Test fun test_aliceStartTwoRequests() { + val testHelper = CommonTestHelper(context()) + val cryptoTestHelper = CryptoTestHelper(testHelper) val cryptoTestData = cryptoTestHelper.doE2ETestWithAliceAndBobInARoom() val aliceSession = cryptoTestData.firstSession @@ -342,7 +354,10 @@ class SASTest : InstrumentedTest { * Test that when alice starts a 'correct' request, bob agrees. */ @Test + @Ignore("This test will be ignored until it is fixed") fun test_aliceAndBobAgreement() { + val testHelper = CommonTestHelper(context()) + val cryptoTestHelper = CryptoTestHelper(testHelper) val cryptoTestData = cryptoTestHelper.doE2ETestWithAliceAndBobInARoom() val aliceSession = cryptoTestData.firstSession @@ -402,6 +417,8 @@ class SASTest : InstrumentedTest { @Test fun test_aliceAndBobSASCode() { + val testHelper = CommonTestHelper(context()) + val cryptoTestHelper = CryptoTestHelper(testHelper) val cryptoTestData = cryptoTestHelper.doE2ETestWithAliceAndBobInARoom() val aliceSession = cryptoTestData.firstSession @@ -458,6 +475,8 @@ class SASTest : InstrumentedTest { @Test fun test_happyPath() { + val testHelper = CommonTestHelper(context()) + val cryptoTestHelper = CryptoTestHelper(testHelper) val cryptoTestData = cryptoTestHelper.doE2ETestWithAliceAndBobInARoom() val aliceSession = cryptoTestData.firstSession @@ -527,9 +546,6 @@ class SASTest : InstrumentedTest { val bobDeviceInfoFromAlicePOV: CryptoDeviceInfo? = aliceSession.cryptoService().getDeviceInfo(bobUserId, bobDeviceId) val aliceDeviceInfoFromBobPOV: CryptoDeviceInfo? = bobSession.cryptoService().getDeviceInfo(aliceSession.myUserId, aliceSession.cryptoService().getMyDevice().deviceId) - // latch wait a bit again - Thread.sleep(1000) - assertTrue("alice device should be verified from bob point of view", aliceDeviceInfoFromBobPOV!!.isVerified) assertTrue("bob device should be verified from alice point of view", bobDeviceInfoFromAlicePOV!!.isVerified) cryptoTestData.cleanUp(testHelper) @@ -537,6 +553,8 @@ class SASTest : InstrumentedTest { @Test fun test_ConcurrentStart() { + val testHelper = CommonTestHelper(context()) + val cryptoTestHelper = CryptoTestHelper(testHelper) val cryptoTestData = cryptoTestHelper.doE2ETestWithAliceAndBobInARoom() val aliceSession = cryptoTestData.firstSession diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/verification/qrcode/VerificationTest.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/verification/qrcode/VerificationTest.kt index 36306aa383f59dfb96b1f5d28b2b7204e8def41a..35c5a4dab9cf72b0b99f6c3a88978f11b8bfc1f5 100644 --- a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/verification/qrcode/VerificationTest.kt +++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/verification/qrcode/VerificationTest.kt @@ -40,8 +40,6 @@ import kotlin.coroutines.resume @RunWith(AndroidJUnit4::class) @FixMethodOrder(MethodSorters.JVM) class VerificationTest : InstrumentedTest { - private val testHelper = CommonTestHelper(context()) - private val cryptoTestHelper = CryptoTestHelper(testHelper) data class ExpectedResult( val sasIsSupported: Boolean = false, @@ -155,6 +153,8 @@ class VerificationTest : InstrumentedTest { bobSupportedMethods: List<VerificationMethod>, expectedResultForAlice: ExpectedResult, expectedResultForBob: ExpectedResult) { + val testHelper = CommonTestHelper(context()) + val cryptoTestHelper = CryptoTestHelper(testHelper) val cryptoTestData = cryptoTestHelper.doE2ETestWithAliceAndBobInARoom() val aliceSession = cryptoTestData.firstSession diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/session/room/send/MarkdownParserTest.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/session/room/send/MarkdownParserTest.kt index 8625e97902ce7ac734f5f928ce35d1dd9d3ccc47..9856ee777068bc07c84dda507f299eef25f13845 100644 --- a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/session/room/send/MarkdownParserTest.kt +++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/session/room/send/MarkdownParserTest.kt @@ -21,6 +21,7 @@ import org.commonmark.parser.Parser import org.commonmark.renderer.html.HtmlRenderer import org.junit.Assert.assertEquals import org.junit.FixMethodOrder +import org.junit.Ignore import org.junit.Test import org.junit.runner.RunWith import org.junit.runners.MethodSorters @@ -132,6 +133,7 @@ class MarkdownParserTest : InstrumentedTest { * Note: the test is not passing, it does not work on Element Web neither */ @Test + @Ignore("This test will be ignored until it is fixed") fun parseStrike_not_passing() { testType( name = "strike", @@ -141,6 +143,7 @@ class MarkdownParserTest : InstrumentedTest { } @Test + @Ignore("This test will be ignored until it is fixed") fun parseStrikeNewLines() { testTypeNewLines( name = "strike", @@ -160,6 +163,7 @@ class MarkdownParserTest : InstrumentedTest { // TODO. Improve testTypeNewLines function to cover <pre><code class="language-code">test</code></pre> @Test + @Ignore("This test will be ignored until it is fixed") fun parseCodeNewLines_not_passing() { testTypeNewLines( name = "code", @@ -179,6 +183,7 @@ class MarkdownParserTest : InstrumentedTest { } @Test + @Ignore("This test will be ignored until it is fixed") fun parseCode2NewLines_not_passing() { testTypeNewLines( name = "code", @@ -197,6 +202,7 @@ class MarkdownParserTest : InstrumentedTest { } @Test + @Ignore("This test will be ignored until it is fixed") fun parseCode3NewLines_not_passing() { testTypeNewLines( name = "code", @@ -233,6 +239,7 @@ class MarkdownParserTest : InstrumentedTest { } @Test + @Ignore("This test will be ignored until it is fixed") fun parseQuote_not_passing() { "> quoted\nline2".let { markdownParser.parse(it).expect(it, "<blockquote><p>quoted<br />line2</p></blockquote>") } } 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/ordering/SpaceOrderTest.kt similarity index 99% rename from matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/SpaceOrderTest.kt rename to matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/ordering/SpaceOrderTest.kt index 3270dfb7574246c85ecf1a7b6a5b64a171c6d86c..50f4692edffce32104c4fe1b8fe963f7b6d7a98d 100644 --- a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/SpaceOrderTest.kt +++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/ordering/SpaceOrderTest.kt @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.matrix.android.sdk +package org.matrix.android.sdk.ordering import org.amshove.kluent.internal.assertEquals import org.junit.Assert 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/ordering/StringOrderTest.kt similarity index 99% rename from matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/StringOrderTest.kt rename to matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/ordering/StringOrderTest.kt index a625362c0413d766bdc4f9567c5f755afc315378..728986441a0589e8f5dbd0af0f716b510bf79049 100644 --- a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/StringOrderTest.kt +++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/ordering/StringOrderTest.kt @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.matrix.android.sdk +package org.matrix.android.sdk.ordering import org.amshove.kluent.internal.assertEquals import org.junit.Assert diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/room/timeline/TimelineForwardPaginationTest.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/room/timeline/TimelineForwardPaginationTest.kt index 05a43de0ac6ac9dcb0a504d14cbd030e152d172c..ee44af58b305549f000b3cba5d6c7a96bb5a0c24 100644 --- a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/room/timeline/TimelineForwardPaginationTest.kt +++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/room/timeline/TimelineForwardPaginationTest.kt @@ -16,6 +16,7 @@ package org.matrix.android.sdk.session.room.timeline +import androidx.test.filters.LargeTest import kotlinx.coroutines.runBlocking import org.amshove.kluent.internal.assertEquals import org.amshove.kluent.shouldBeFalse @@ -40,16 +41,20 @@ import java.util.concurrent.CountDownLatch @RunWith(JUnit4::class) @FixMethodOrder(MethodSorters.JVM) +@LargeTest class TimelineForwardPaginationTest : InstrumentedTest { - private val commonTestHelper = CommonTestHelper(context()) - private val cryptoTestHelper = CryptoTestHelper(commonTestHelper) +// @Rule +// @JvmField +// val mRetryTestRule = RetryTestRule() /** * This test ensure that if we click to permalink, we will be able to go back to the live */ @Test fun forwardPaginationTest() { + val commonTestHelper = CommonTestHelper(context()) + val cryptoTestHelper = CryptoTestHelper(commonTestHelper) val numberOfMessagesToSend = 90 val cryptoTestData = cryptoTestHelper.doE2ETestWithAliceInARoom(false) diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/room/timeline/TimelinePreviousLastForwardTest.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/room/timeline/TimelinePreviousLastForwardTest.kt index c6fdec150d5ca7571836daee0e3479e350f18469..c6d40bcaa26d1effe5c3415b96aed9b6b1a31b4b 100644 --- a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/room/timeline/TimelinePreviousLastForwardTest.kt +++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/room/timeline/TimelinePreviousLastForwardTest.kt @@ -16,6 +16,7 @@ package org.matrix.android.sdk.session.room.timeline +import androidx.test.filters.LargeTest import org.amshove.kluent.shouldBeFalse import org.amshove.kluent.shouldBeTrue import org.junit.FixMethodOrder @@ -38,16 +39,17 @@ import java.util.concurrent.CountDownLatch @RunWith(JUnit4::class) @FixMethodOrder(MethodSorters.JVM) +@LargeTest class TimelinePreviousLastForwardTest : InstrumentedTest { - private val commonTestHelper = CommonTestHelper(context()) - private val cryptoTestHelper = CryptoTestHelper(commonTestHelper) - /** * This test ensure that if we have a chunk in the timeline which is due to a sync, and we click to permalink, we will be able to go back to the live */ + @Test fun previousLastForwardTest() { + val commonTestHelper = CommonTestHelper(context()) + val cryptoTestHelper = CryptoTestHelper(commonTestHelper) val cryptoTestData = cryptoTestHelper.doE2ETestWithAliceAndBobInARoom(false) val aliceSession = cryptoTestData.firstSession diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/room/timeline/TimelineSimpleBackPaginationTest.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/room/timeline/TimelineSimpleBackPaginationTest.kt index b75df9b5a211194ab8955c0b9e0e01acf80d49ac..53f76f1c46c2181b5eba816f62f10dd22b81c18b 100644 --- a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/room/timeline/TimelineSimpleBackPaginationTest.kt +++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/room/timeline/TimelineSimpleBackPaginationTest.kt @@ -16,6 +16,7 @@ package org.matrix.android.sdk.session.room.timeline +import androidx.test.filters.LargeTest import kotlinx.coroutines.runBlocking import org.amshove.kluent.internal.assertEquals import org.junit.FixMethodOrder @@ -36,13 +37,13 @@ import org.matrix.android.sdk.common.TestConstants @RunWith(JUnit4::class) @FixMethodOrder(MethodSorters.JVM) +@LargeTest class TimelineSimpleBackPaginationTest : InstrumentedTest { - private val commonTestHelper = CommonTestHelper(context()) - private val cryptoTestHelper = CryptoTestHelper(commonTestHelper) - @Test fun timeline_backPaginate_shouldReachEndOfTimeline() { + val commonTestHelper = CommonTestHelper(context()) + val cryptoTestHelper = CryptoTestHelper(commonTestHelper) val numberOfMessagesToSent = 200 val cryptoTestData = cryptoTestHelper.doE2ETestWithAliceAndBobInARoom(false) diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/room/timeline/TimelineWithManyMembersTest.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/room/timeline/TimelineWithManyMembersTest.kt index ace48cef77d365a4c03450e226caee540629984f..ce02b2b527e89898b926e54ec7b16b81c219849c 100644 --- a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/room/timeline/TimelineWithManyMembersTest.kt +++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/room/timeline/TimelineWithManyMembersTest.kt @@ -16,8 +16,10 @@ package org.matrix.android.sdk.session.room.timeline +import androidx.test.filters.LargeTest import org.junit.Assert.fail import org.junit.FixMethodOrder +import org.junit.Ignore import org.junit.Test import org.junit.runner.RunWith import org.junit.runners.JUnit4 @@ -31,8 +33,13 @@ import org.matrix.android.sdk.common.CommonTestHelper import org.matrix.android.sdk.common.CryptoTestHelper import java.util.concurrent.CountDownLatch +/** !! Not working with the new timeline + * Disabling it until the fix is made + */ @RunWith(JUnit4::class) @FixMethodOrder(MethodSorters.JVM) +@Ignore("This test will be ignored until it is fixed") +@LargeTest class TimelineWithManyMembersTest : InstrumentedTest { companion object { @@ -45,6 +52,7 @@ class TimelineWithManyMembersTest : InstrumentedTest { /** * Ensures when someone sends a message to a crowded room, everyone can decrypt the message. */ + @Test fun everyone_should_decrypt_message_in_a_crowded_room() { val cryptoTestData = cryptoTestHelper.doE2ETestWithManyMembers(NUMBER_OF_MEMBERS) diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/search/SearchMessagesTest.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/search/SearchMessagesTest.kt index 45e4b53c77f39c0e82d83cdc61224359e38b1c8d..fa07cf5a02c07587f1d70725b8034c6a97c7d410 100644 --- a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/search/SearchMessagesTest.kt +++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/search/SearchMessagesTest.kt @@ -37,9 +37,6 @@ class SearchMessagesTest : InstrumentedTest { private const val MESSAGE = "Lorem ipsum dolor sit amet" } - private val commonTestHelper = CommonTestHelper(context()) - private val cryptoTestHelper = CryptoTestHelper(commonTestHelper) - @Test fun sendTextMessageAndSearchPartOfItUsingSession() { doTest { cryptoTestData -> @@ -76,6 +73,8 @@ class SearchMessagesTest : InstrumentedTest { } private fun doTest(block: suspend (CryptoTestData) -> SearchResult) { + val commonTestHelper = CommonTestHelper(context()) + val cryptoTestHelper = CryptoTestHelper(commonTestHelper) val cryptoTestData = cryptoTestHelper.doE2ETestWithAliceInARoom(false) val aliceSession = cryptoTestData.firstSession val aliceRoomId = cryptoTestData.roomId diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/space/SpaceCreationTest.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/space/SpaceCreationTest.kt index d7be19295cb2e0ad4576adbee5a3c236cb8ccea6..3b0f7586ccfd571d97c2c0c8887495f44420480e 100644 --- a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/space/SpaceCreationTest.kt +++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/space/SpaceCreationTest.kt @@ -16,6 +16,7 @@ package org.matrix.android.sdk.session.space +import androidx.test.filters.LargeTest import kotlinx.coroutines.delay import kotlinx.coroutines.runBlocking import org.junit.Assert.assertEquals @@ -43,12 +44,12 @@ import org.matrix.android.sdk.common.SessionTestParams @RunWith(JUnit4::class) @FixMethodOrder(MethodSorters.JVM) +@LargeTest class SpaceCreationTest : InstrumentedTest { - private val commonTestHelper = CommonTestHelper(context()) - @Test fun createSimplePublicSpace() { + val commonTestHelper = CommonTestHelper(context()) val session = commonTestHelper.createAccount("Hubble", SessionTestParams(true)) val roomName = "My Space" val topic = "A public space for test" @@ -58,6 +59,7 @@ class SpaceCreationTest : InstrumentedTest { // wait a bit to let the summary update it self :/ it.countDown() } + Thread.sleep(4_000) val syncedSpace = session.spaceService().getSpace(spaceId) commonTestHelper.waitWithLatch { @@ -99,6 +101,8 @@ class SpaceCreationTest : InstrumentedTest { @Test fun testJoinSimplePublicSpace() { + val commonTestHelper = CommonTestHelper(context()) + val aliceSession = commonTestHelper.createAccount("alice", SessionTestParams(true)) val bobSession = commonTestHelper.createAccount("bob", SessionTestParams(true)) @@ -130,6 +134,7 @@ class SpaceCreationTest : InstrumentedTest { @Test fun testSimplePublicSpaceWithChildren() { + val commonTestHelper = CommonTestHelper(context()) val aliceSession = commonTestHelper.createAccount("alice", SessionTestParams(true)) val bobSession = commonTestHelper.createAccount("bob", SessionTestParams(true)) diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/space/SpaceHierarchyTest.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/space/SpaceHierarchyTest.kt index 1c38edbbd918df90398584d46af6549d9c2fe64b..5fbfaf99a0ed9f2f0f414ed8e3bbf87357fe7a1f 100644 --- a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/space/SpaceHierarchyTest.kt +++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/space/SpaceHierarchyTest.kt @@ -23,6 +23,7 @@ import org.junit.Assert.assertEquals import org.junit.Assert.assertNotNull import org.junit.Assert.assertTrue import org.junit.FixMethodOrder +import org.junit.Ignore import org.junit.Test import org.junit.runner.RunWith import org.junit.runners.JUnit4 @@ -50,10 +51,10 @@ import org.matrix.android.sdk.common.SessionTestParams @FixMethodOrder(MethodSorters.JVM) class SpaceHierarchyTest : InstrumentedTest { - private val commonTestHelper = CommonTestHelper(context()) - @Test fun createCanonicalChildRelation() { + val commonTestHelper = CommonTestHelper(context()) + val session = commonTestHelper.createAccount("John", SessionTestParams(true)) val spaceName = "My Space" val topic = "A public space for test" @@ -170,6 +171,7 @@ class SpaceHierarchyTest : InstrumentedTest { @Test fun testFilteringBySpace() { + val commonTestHelper = CommonTestHelper(context()) val session = commonTestHelper.createAccount("John", SessionTestParams(true)) val spaceAInfo = createPublicSpace(session, "SpaceA", listOf( @@ -236,7 +238,7 @@ class SpaceHierarchyTest : InstrumentedTest { it.countDown() } - Thread.sleep(2_000) + Thread.sleep(6_000) val orphansUpdate = session.getRoomSummaries(roomSummaryQueryParams { activeSpaceFilter = ActiveSpaceFilter.ActiveSpace(null) }) @@ -244,7 +246,9 @@ class SpaceHierarchyTest : InstrumentedTest { } @Test + @Ignore("This test will be ignored until it is fixed") fun testBreakCycle() { + val commonTestHelper = CommonTestHelper(context()) val session = commonTestHelper.createAccount("John", SessionTestParams(true)) val spaceAInfo = createPublicSpace(session, "SpaceA", listOf( @@ -273,8 +277,6 @@ class SpaceHierarchyTest : InstrumentedTest { it.countDown() } - Thread.sleep(1000) - // A -> C -> A val aChildren = session.getFlattenRoomSummaryChildrenOf(spaceAInfo.spaceId) @@ -288,6 +290,7 @@ class SpaceHierarchyTest : InstrumentedTest { @Test fun testLiveFlatChildren() { + val commonTestHelper = CommonTestHelper(context()) val session = commonTestHelper.createAccount("John", SessionTestParams(true)) val spaceAInfo = createPublicSpace(session, "SpaceA", listOf( @@ -374,6 +377,7 @@ class SpaceHierarchyTest : InstrumentedTest { childInfo: List<Triple<String, Boolean, Boolean?>> /** Name, auto-join, canonical*/ ): TestSpaceCreationResult { + val commonTestHelper = CommonTestHelper(context()) var spaceId = "" var roomIds: List<String> = emptyList() commonTestHelper.waitWithLatch { latch -> @@ -401,6 +405,7 @@ class SpaceHierarchyTest : InstrumentedTest { childInfo: List<Triple<String, Boolean, Boolean?>> /** Name, auto-join, canonical*/ ): TestSpaceCreationResult { + val commonTestHelper = CommonTestHelper(context()) var spaceId = "" var roomIds: List<String> = emptyList() commonTestHelper.waitWithLatch { latch -> @@ -435,6 +440,7 @@ class SpaceHierarchyTest : InstrumentedTest { @Test fun testRootSpaces() { + val commonTestHelper = CommonTestHelper(context()) val session = commonTestHelper.createAccount("John", SessionTestParams(true)) /* val spaceAInfo = */ createPublicSpace(session, "SpaceA", listOf( @@ -459,9 +465,10 @@ class SpaceHierarchyTest : InstrumentedTest { runBlocking { val spaceB = session.spaceService().getSpace(spaceBInfo.spaceId) spaceB!!.addChildren(spaceCInfo.spaceId, viaServers, null, true) + Thread.sleep(6_000) } - Thread.sleep(2000) +// Thread.sleep(4_000) // + A // a1, a2 // + B @@ -478,6 +485,7 @@ class SpaceHierarchyTest : InstrumentedTest { @Test fun testParentRelation() { + val commonTestHelper = CommonTestHelper(context()) val aliceSession = commonTestHelper.createAccount("Alice", SessionTestParams(true)) val bobSession = commonTestHelper.createAccount("Bib", SessionTestParams(true)) @@ -542,7 +550,7 @@ class SpaceHierarchyTest : InstrumentedTest { ?.setUserPowerLevel(aliceSession.myUserId, Role.Admin.value) ?.toContent() - room.sendStateEvent(EventType.STATE_ROOM_POWER_LEVELS, null, newPowerLevelsContent!!) + room.sendStateEvent(EventType.STATE_ROOM_POWER_LEVELS, stateKey = "", newPowerLevelsContent!!) it.countDown() } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/failure/Extensions.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/failure/Extensions.kt index 13a26c89c19e4fe4bbf129a1a3100d929f8aa78d..aabe6e0d0698c37280687c39034cd9f9f2dce055 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/failure/Extensions.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/failure/Extensions.kt @@ -32,13 +32,18 @@ fun Throwable.is401() = fun Throwable.isTokenError() = this is Failure.ServerError && (error.code == MatrixError.M_UNKNOWN_TOKEN || - error.code == MatrixError.M_MISSING_TOKEN || - error.code == MatrixError.ORG_MATRIX_EXPIRED_ACCOUNT) + error.code == MatrixError.M_MISSING_TOKEN || + error.code == MatrixError.ORG_MATRIX_EXPIRED_ACCOUNT) + +fun Throwable.isLimitExceededError() = + this is Failure.ServerError && + httpCode == 429 && + error.code == MatrixError.M_LIMIT_EXCEEDED fun Throwable.shouldBeRetried(): Boolean { return this is Failure.NetworkConnection || this is IOException || - (this is Failure.ServerError && error.code == MatrixError.M_LIMIT_EXCEEDED) + this.isLimitExceededError() } /** diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/pushrules/EventMatchCondition.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/pushrules/EventMatchCondition.kt index eec5b0a402c1a3320191aec7b578c701a8889dc4..65a13b4fec916b247206464408c2b169ce4cb990 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/pushrules/EventMatchCondition.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/pushrules/EventMatchCondition.kt @@ -56,7 +56,13 @@ class EventMatchCondition( if (wordsOnly) { value.caseInsensitiveFind(pattern) } else { - val modPattern = if (pattern.hasSpecialGlobChar()) pattern.simpleGlobToRegExp() else "*$pattern*".simpleGlobToRegExp() + val modPattern = if (pattern.hasSpecialGlobChar()) { + // Regex.containsMatchIn() is way faster without leading and trailing + // stars, that don't make any difference for the evaluation result + pattern.removePrefix("*").removeSuffix("*").simpleGlobToRegExp() + } else { + pattern.simpleGlobToRegExp() + } val regex = Regex(modPattern, RegexOption.DOT_MATCHES_ALL) regex.containsMatchIn(value) } 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 36ab00731424038291c34542e85cc98f520713d5..be924e206367edac1242842571ab0c8652a76661 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 @@ -54,6 +54,7 @@ import org.matrix.android.sdk.api.session.securestorage.SecureStorageService import org.matrix.android.sdk.api.session.securestorage.SharedSecretStorageService import org.matrix.android.sdk.api.session.signout.SignOutService import org.matrix.android.sdk.api.session.space.SpaceService +import org.matrix.android.sdk.api.session.statistics.StatisticsListener import org.matrix.android.sdk.api.session.sync.FilterService import org.matrix.android.sdk.api.session.sync.SyncState import org.matrix.android.sdk.api.session.sync.model.SyncResponse @@ -287,7 +288,7 @@ interface Session : /** * A global session listener to get notified for some events. */ - interface Listener : SessionLifecycleObserver { + interface Listener : StatisticsListener, SessionLifecycleObserver { /** * Called when the session received new invites to room so the client can react to it once. */ diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/members/MembershipService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/members/MembershipService.kt index 198d6677a0bb319e764fd48ccb953616ac1ce015..d5bc65c14294de2aea842af5c2161773fee92410 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/members/MembershipService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/members/MembershipService.kt @@ -75,9 +75,12 @@ interface MembershipService { suspend fun unban(userId: String, reason: String? = null) /** - * Kick a user from the room + * Remove a user from the room */ - suspend fun kick(userId: String, reason: String? = null) + suspend fun remove(userId: String, reason: String? = null) + + @Deprecated("Use remove instead", ReplaceWith("remove(userId, reason)")) + suspend fun kick(userId: String, reason: String? = null) = remove(userId, reason) /** * Join the room, or accept an invitation. diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/LocationAsset.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/LocationAsset.kt new file mode 100644 index 0000000000000000000000000000000000000000..e8b3cf2488860a0bc6019d80db8844f771069546 --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/LocationAsset.kt @@ -0,0 +1,25 @@ +/* + * 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 + +@JsonClass(generateAdapter = true) +data class LocationAsset( + @Json(name = "type") val type: LocationAssetType? = null +) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/LocationAssetType.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/LocationAssetType.kt new file mode 100644 index 0000000000000000000000000000000000000000..ef40e21c47f582889fd015801131a937be268e20 --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/LocationAssetType.kt @@ -0,0 +1,26 @@ +/* + * 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 + +@JsonClass(generateAdapter = false) +enum class LocationAssetType { + @Json(name = "m.self") + SELF +} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/LocationInfo.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/LocationInfo.kt index a76c3c5b64d4d48f2570747cb28372b8462fcd42..a1fd3bd2ecc5c3c2251568984db63ace128a96a1 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/LocationInfo.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/LocationInfo.kt @@ -18,29 +18,17 @@ package org.matrix.android.sdk.api.session.room.model.message import com.squareup.moshi.Json import com.squareup.moshi.JsonClass -import org.matrix.android.sdk.internal.crypto.model.rest.EncryptedFileInfo @JsonClass(generateAdapter = true) data class LocationInfo( /** - * The URL to the thumbnail of the file. Only present if the thumbnail is unencrypted. + * Required. RFC5870 formatted geo uri 'geo:latitude,longitude;uncertainty' like 'geo:40.05,29.24;30' representing this location. */ - @Json(name = "thumbnail_url") val thumbnailUrl: String? = null, + @Json(name = "uri") val geoUri: String? = null, /** - * Metadata about the image referred to in thumbnail_url. + * Required. A description of the location e.g. 'Big Ben, London, UK', or some kind + * of content description for accessibility e.g. 'location attachment'. */ - @Json(name = "thumbnail_info") val thumbnailInfo: ThumbnailInfo? = null, - - /** - * Information on the encrypted thumbnail file, as specified in End-to-end encryption. Only present if the thumbnail is encrypted. - */ - @Json(name = "thumbnail_file") val thumbnailFile: EncryptedFileInfo? = null + @Json(name = "description") val description: String? = null ) - -/** - * Get the url of the encrypted thumbnail or of the thumbnail - */ -fun LocationInfo.getThumbnailUrl(): String? { - return thumbnailFile?.url ?: thumbnailUrl -} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/MessageLocationContent.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/MessageLocationContent.kt index 6881c0992467b75ea428d48c71226e491ea1b8c9..c090487c58ffabd7a4c31e7dc01abe76ff0b99f4 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/MessageLocationContent.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/MessageLocationContent.kt @@ -26,7 +26,7 @@ data class MessageLocationContent( /** * Required. Must be 'm.location'. */ - @Json(name = MessageContent.MSG_TYPE_JSON_KEY) override val msgType: String, + @Json(name = MessageContent.MSG_TYPE_JSON_KEY) override val msgType: String = MessageType.MSGTYPE_LOCATION, /** * Required. A description of the location e.g. 'Big Ben, London, UK', or some kind @@ -35,15 +35,33 @@ data class MessageLocationContent( @Json(name = "body") override val body: String, /** - * Required. A geo URI representing this location. + * Required. RFC5870 formatted geo uri 'geo:latitude,longitude;uncertainty' like 'geo:40.05,29.24;30' representing this location. */ @Json(name = "geo_uri") val geoUri: String, /** - * + * See https://github.com/matrix-org/matrix-doc/blob/matthew/location/proposals/3488-location.md */ - @Json(name = "info") val locationInfo: LocationInfo? = null, + @Json(name = "org.matrix.msc3488.location") val locationInfo: LocationInfo? = null, @Json(name = "m.relates_to") override val relatesTo: RelationDefaultContent? = null, - @Json(name = "m.new_content") override val newContent: Content? = null -) : MessageContent + @Json(name = "m.new_content") override val newContent: Content? = null, + + /** + * m.asset defines a generic asset that can be used for location tracking but also in other places like + * inventories, geofencing, checkins/checkouts etc. + * It should contain a mandatory namespaced type key defining what particular asset is being referred to. + * For the purposes of user location tracking m.self should be used in order to avoid duplicating the mxid. + */ + @Json(name = "m.asset") val locationAsset: LocationAsset? = null, + + /** + * Exact time that the data in the event refers to (milliseconds since the UNIX epoch) + */ + @Json(name = "org.matrix.msc3488.ts") val ts: Long? = null, + + @Json(name = "org.matrix.msc1767.text") val text: String? = null +) : MessageContent { + + fun getBestGeoUri() = locationInfo?.geoUri ?: geoUri +} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/PollCreationInfo.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/PollCreationInfo.kt index e652514b92c7f7635611a41d00acf16796d66b2d..a82c01b15989bbd5425a3e89085ec18d108fd81a 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/PollCreationInfo.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/PollCreationInfo.kt @@ -22,7 +22,7 @@ import com.squareup.moshi.JsonClass @JsonClass(generateAdapter = true) data class PollCreationInfo( @Json(name = "question") val question: PollQuestion? = null, - @Json(name = "kind") val kind: String? = "org.matrix.msc3381.poll.disclosed", + @Json(name = "kind") val kind: PollType? = PollType.DISCLOSED, @Json(name = "max_selections") val maxSelections: Int = 1, @Json(name = "answers") val answers: List<PollAnswer>? = null ) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/PollType.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/PollType.kt new file mode 100644 index 0000000000000000000000000000000000000000..3a8066b9bc10caea1f2ff1893ee070eb360a149a --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/PollType.kt @@ -0,0 +1,35 @@ +/* + * Copyright 2022 The Matrix.org Foundation C.I.C. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.matrix.android.sdk.api.session.room.model.message + +import com.squareup.moshi.Json +import com.squareup.moshi.JsonClass + +@JsonClass(generateAdapter = false) +enum class PollType { + /** + * Voters should see results as soon as they have voted. + */ + @Json(name = "org.matrix.msc3381.poll.disclosed") + DISCLOSED, + + /** + * Results should be only revealed when the poll is ended. + */ + @Json(name = "org.matrix.msc3381.poll.undisclosed") + UNDISCLOSED +} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/relation/RelationService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/relation/RelationService.kt index 59d84ef40fd82c6f458baba55fe856904b502ac1..763d4bb89294eaff6683ad0376922cc05cae4fec 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/relation/RelationService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/relation/RelationService.kt @@ -18,6 +18,7 @@ package org.matrix.android.sdk.api.session.room.model.relation import androidx.lifecycle.LiveData import org.matrix.android.sdk.api.session.events.model.Event import org.matrix.android.sdk.api.session.room.model.EventAnnotationsSummary +import org.matrix.android.sdk.api.session.room.model.message.PollType import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent import org.matrix.android.sdk.api.util.Cancelable import org.matrix.android.sdk.api.util.Optional @@ -64,6 +65,18 @@ interface RelationService { fun undoReaction(targetEventId: String, reaction: String): Cancelable + /** + * Edit a poll. + * @param pollType indicates open or closed polls + * @param targetEvent The poll event to edit + * @param question The edited question + * @param options The edited options + */ + fun editPoll(targetEvent: TimelineEvent, + pollType: PollType, + question: String, + options: List<String>): Cancelable + /** * Edit a text message body. Limited to "m.text" contentType * @param targetEvent The event to edit diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/send/SendService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/send/SendService.kt index 606500c4e7204e8b7c69c4c9be1f715e31c5c738..20d00394df48ae29c03e2650c67630b146bea1a9 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/send/SendService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/send/SendService.kt @@ -20,6 +20,7 @@ import org.matrix.android.sdk.api.session.content.ContentAttachmentData import org.matrix.android.sdk.api.session.events.model.Content import org.matrix.android.sdk.api.session.events.model.Event import org.matrix.android.sdk.api.session.room.model.message.MessageType +import org.matrix.android.sdk.api.session.room.model.message.PollType import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent import org.matrix.android.sdk.api.util.Cancelable @@ -91,11 +92,12 @@ interface SendService { /** * Send a poll to the room. + * @param pollType indicates open or closed polls * @param question the question * @param options list of options * @return a [Cancelable] */ - fun sendPoll(question: String, options: List<String>): Cancelable + fun sendPoll(pollType: PollType, question: String, options: List<String>): Cancelable /** * Method to send a poll response. @@ -131,6 +133,14 @@ interface SendService { */ fun resendMediaMessage(localEcho: TimelineEvent): Cancelable + /** + * Send a location event to the room + * @param latitude required latitude of the location + * @param longitude required longitude of the location + * @param uncertainty Accuracy of the location in meters + */ + fun sendLocation(latitude: Double, longitude: Double, uncertainty: Double?): Cancelable + /** * Remove this failed message from the timeline * @param localEcho the unsent local echo 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 4d3f95233d05d54e8202c1a48084b9913ecb001b..e9b0e4f6760c156503d70e40014373ccb6c5c97a 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 @@ -68,8 +68,11 @@ interface StateService { /** * Send a state event to the room + * @param eventType The type of event to send. + * @param stateKey The state_key for the state to send. Can be an empty string. + * @param body The content object of the event; the fields in this object will vary depending on the type of event */ - suspend fun sendStateEvent(eventType: String, stateKey: String?, body: JsonDict) + suspend fun sendStateEvent(eventType: String, stateKey: String, body: JsonDict) /** * Get a state event of the room diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/timeline/TimelineEvent.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/timeline/TimelineEvent.kt index 45dc322420fffb5c5accb97fe6856058e0479d73..3f7d2d127888c7d00556636f0cdf553c1b30c9fc 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/timeline/TimelineEvent.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/timeline/TimelineEvent.kt @@ -133,7 +133,7 @@ fun TimelineEvent.getEditedEventId(): String? { fun TimelineEvent.getLastMessageContent(): MessageContent? { return when (root.getClearType()) { EventType.STICKER -> root.getClearContent().toModel<MessageStickerContent>() - EventType.POLL_START -> root.getClearContent().toModel<MessagePollContent>() + EventType.POLL_START -> (annotations?.editSummary?.latestContent ?: root.getClearContent()).toModel<MessagePollContent>() else -> (annotations?.editSummary?.latestContent ?: root.getClearContent()).toModel() } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/statistics/StatisticEvent.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/statistics/StatisticEvent.kt new file mode 100644 index 0000000000000000000000000000000000000000..946792d31ef8731417fa377959b751983f4f8273 --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/statistics/StatisticEvent.kt @@ -0,0 +1,37 @@ +/* + * Copyright (c) 2022 The Matrix.org Foundation C.I.C. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.matrix.android.sdk.api.session.statistics + +/** + * Statistic Events. You can subscribe to received such events using [Session.Listener] + */ +sealed interface StatisticEvent { + /** + * Initial sync request, response downloading, and treatment (parsing and storage) of response + */ + data class InitialSyncRequest(val requestDurationMs: Int, + val downloadDurationMs: Int, + val treatmentDurationMs: Int, + val nbOfJoinedRooms: Int) : StatisticEvent + + /** + * Incremental sync event + */ + data class SyncTreatment(val durationMs: Int, + val afterPause: Boolean, + val nbOfJoinedRooms: Int) : StatisticEvent +} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/statistics/StatisticsListener.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/statistics/StatisticsListener.kt new file mode 100644 index 0000000000000000000000000000000000000000..a2cb7910a7d0c09e8c6888e96284b8de20a44bf4 --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/statistics/StatisticsListener.kt @@ -0,0 +1,23 @@ +/* + * Copyright (c) 2022 The Matrix.org Foundation C.I.C. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.matrix.android.sdk.api.session.statistics + +import org.matrix.android.sdk.api.session.Session + +interface StatisticsListener { + fun onStatisticsEvent(session: Session, statisticEvent: StatisticEvent) = Unit +} 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 1f45ac2a7530300f03809f9fbab5e5f86811cfee..01576c3d611644b80db49b83aadfb9f0ace88a6e 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 @@ -56,7 +56,7 @@ internal class RealmSessionStoreMigration @Inject constructor( ) : RealmMigration { companion object { - const val SESSION_STORE_SCHEMA_VERSION = 21L + const val SESSION_STORE_SCHEMA_VERSION = 22L } /** @@ -90,6 +90,7 @@ internal class RealmSessionStoreMigration @Inject constructor( if (oldVersion <= 18) migrateTo19(realm) if (oldVersion <= 19) migrateTo20(realm) if (oldVersion <= 20) migrateTo21(realm) + if (oldVersion <= 21) migrateTo22(realm) } private fun migrateTo1(realm: DynamicRealm) { @@ -445,4 +446,20 @@ internal class RealmSessionStoreMigration @Inject constructor( } } } + + private fun migrateTo22(realm: DynamicRealm) { + Timber.d("Step 21 -> 22") + val listJoinedRoomIds = realm.where("RoomEntity") + .equalTo(RoomEntityFields.MEMBERSHIP_STR, Membership.JOIN.name).findAll() + .map { it.getString(RoomEntityFields.ROOM_ID) } + + val hasMissingStateEvent = realm.where("CurrentStateEventEntity") + .`in`(CurrentStateEventEntityFields.ROOM_ID, listJoinedRoomIds.toTypedArray()) + .isNull(CurrentStateEventEntityFields.ROOT.`$`).findFirst() != null + + if (hasMissingStateEvent) { + Timber.v("Has some missing state event, clear session cache") + realm.deleteAll() + } + } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/ChunkEntity.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/ChunkEntity.kt index ecb602019aba1e9956d989ed57a9da9f7aecbe4a..c45c27ed08a0fe2f361ae17d3ccf64b1d4384ac0 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/ChunkEntity.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/ChunkEntity.kt @@ -52,6 +52,9 @@ internal fun ChunkEntity.deleteOnCascade(deleteStateEvents: Boolean, canDeleteRo if (deleteStateEvents) { stateEvents.deleteAllFromRealm() } - timelineEvents.clearWith { it.deleteOnCascade(canDeleteRoot) } + timelineEvents.clearWith { + val deleteRoot = canDeleteRoot && (it.root?.stateKey == null || deleteStateEvents) + it.deleteOnCascade(deleteRoot) + } deleteFromRealm() } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/di/NetworkModule.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/di/NetworkModule.kt index ad34a4d8a6411082f1ba02607a5d3a7b0a12f95d..0cbbe1210d288918441532e24e69eef13a448090 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/di/NetworkModule.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/di/NetworkModule.kt @@ -22,6 +22,7 @@ import dagger.Module import dagger.Provides import okhttp3.ConnectionSpec import okhttp3.OkHttpClient +import okhttp3.Protocol import okhttp3.logging.HttpLoggingInterceptor import org.matrix.android.sdk.BuildConfig import org.matrix.android.sdk.api.MatrixConfiguration @@ -71,6 +72,8 @@ internal object NetworkModule { val spec = ConnectionSpec.Builder(matrixConfiguration.connectionSpec).build() return OkHttpClient.Builder() + // workaround for #4669 + .protocols(listOf(Protocol.HTTP_1_1)) .connectTimeout(30, TimeUnit.SECONDS) .readTimeout(60, TimeUnit.SECONDS) .writeTimeout(60, TimeUnit.SECONDS) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/network/Request.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/network/Request.kt index 927d9f7dd2c177b3608a97f787821651ca8d0d89..695e7525af09580b8db70c40d3552a38a55b0252 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/network/Request.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/network/Request.kt @@ -19,8 +19,9 @@ package org.matrix.android.sdk.internal.network import kotlinx.coroutines.CancellationException import kotlinx.coroutines.delay import org.matrix.android.sdk.api.failure.Failure -import org.matrix.android.sdk.api.failure.MatrixError +import org.matrix.android.sdk.api.failure.GlobalError import org.matrix.android.sdk.api.failure.getRetryDelay +import org.matrix.android.sdk.api.failure.isLimitExceededError import org.matrix.android.sdk.api.failure.shouldBeRetried import org.matrix.android.sdk.internal.network.ssl.CertUtil import retrofit2.HttpException @@ -33,7 +34,8 @@ import java.io.IOException * * @param globalErrorReceiver will be use to notify error such as invalid token error. See [GlobalError] * @param canRetry if set to true, the request will be executed again in case of error, after a delay - * @param maxDelayBeforeRetry the max delay to wait before a retry + * @param maxDelayBeforeRetry the max delay to wait before a retry. Note that in the case of a 429, if the provided delay exceeds this value, the error will + * be propagated as it does not make sense to retry it with a shorter delay. * @param maxRetriesCount the max number of retries * @param requestBlock a suspend lambda to perform the network request */ @@ -74,23 +76,26 @@ internal suspend inline fun <DATA> executeRequest(globalErrorReceiver: GlobalErr currentRetryCount++ - if (exception is Failure.ServerError && - exception.httpCode == 429 && - exception.error.code == MatrixError.M_LIMIT_EXCEEDED && - currentRetryCount < maxRetriesCount) { + if (exception.isLimitExceededError() && currentRetryCount < maxRetriesCount) { // 429, we can retry - delay(exception.getRetryDelay(1_000)) + val retryDelay = exception.getRetryDelay(1_000) + if (retryDelay <= maxDelayBeforeRetry) { + delay(retryDelay) + } else { + // delay is too high to be retried, propagate the exception + throw exception + } } else if (canRetry && currentRetryCount < maxRetriesCount && exception.shouldBeRetried()) { delay(currentDelay) currentDelay = currentDelay.times(2L).coerceAtMost(maxDelayBeforeRetry) // Try again (loop) } else { throw when (exception) { - is IOException -> Failure.NetworkConnection(exception) + is IOException -> Failure.NetworkConnection(exception) is Failure.ServerError, is Failure.OtherServerError, - is CancellationException -> exception - else -> Failure.Unknown(exception) + is CancellationException -> exception + else -> Failure.Unknown(exception) } } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/notification/ProcessEventForPushTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/notification/ProcessEventForPushTask.kt index da15e158e5a1dec144e271b38ee1ba04244cd999..8b05d2ea629adf4db7495235d77eb1546adc5713 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/notification/ProcessEventForPushTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/notification/ProcessEventForPushTask.kt @@ -74,6 +74,7 @@ internal class DefaultProcessEventForPushTask @Inject constructor( event to it } } + Timber.d("[PushRules] matched ${matchedEvents.size} out of ${allEvents.size}") val allRedactedEvents = params.syncResponse.join .asSequence() 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 1fe7503141de9d612020d4cf7c34a46efb0ecd38..1c3d1971c2cc076acfd13df16e3311e62e7be21c 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 @@ -130,7 +130,7 @@ internal class DefaultRoom(override val roomId: String, else -> { val params = SendStateTask.Params( roomId = roomId, - stateKey = null, + stateKey = "", eventType = EventType.STATE_ROOM_ENCRYPTION, body = mapOf( "algorithm" to algorithm diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/EventRelationsAggregationProcessor.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/EventRelationsAggregationProcessor.kt index 62b6d626f5e509058ab2d22f2220cb9f43b545b3..3cc08df0e8fd7f7c2ebcd589fc5da0d197620633 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/EventRelationsAggregationProcessor.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/EventRelationsAggregationProcessor.kt @@ -34,10 +34,14 @@ import org.matrix.android.sdk.api.session.room.model.VoteInfo import org.matrix.android.sdk.api.session.room.model.VoteSummary import org.matrix.android.sdk.api.session.room.model.message.MessageContent import org.matrix.android.sdk.api.session.room.model.message.MessageEndPollContent +import org.matrix.android.sdk.api.session.room.model.message.MessagePollContent import org.matrix.android.sdk.api.session.room.model.message.MessagePollResponseContent import org.matrix.android.sdk.api.session.room.model.message.MessageRelationContent import org.matrix.android.sdk.api.session.room.model.relation.ReactionContent import org.matrix.android.sdk.api.session.room.powerlevels.PowerLevelsHelper +import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent +import org.matrix.android.sdk.api.session.room.timeline.getLastMessageContent +import org.matrix.android.sdk.internal.SessionManager import org.matrix.android.sdk.internal.crypto.model.event.EncryptedEventContent import org.matrix.android.sdk.internal.crypto.verification.toState import org.matrix.android.sdk.internal.database.mapper.ContentMapper @@ -55,6 +59,7 @@ import org.matrix.android.sdk.internal.database.model.TimelineEventEntity import org.matrix.android.sdk.internal.database.query.create import org.matrix.android.sdk.internal.database.query.getOrCreate import org.matrix.android.sdk.internal.database.query.where +import org.matrix.android.sdk.internal.di.SessionId import org.matrix.android.sdk.internal.di.UserId import org.matrix.android.sdk.internal.session.EventInsertLiveProcessor import org.matrix.android.sdk.internal.session.room.state.StateEventDataSource @@ -63,7 +68,9 @@ import javax.inject.Inject internal class EventRelationsAggregationProcessor @Inject constructor( @UserId private val userId: String, - private val stateEventDataSource: StateEventDataSource + private val stateEventDataSource: StateEventDataSource, + @SessionId private val sessionId: String, + private val sessionManager: SessionManager ) : EventInsertLiveProcessor { private val allowedTypes = listOf( @@ -79,6 +86,7 @@ internal class EventRelationsAggregationProcessor @Inject constructor( // EventType.KEY_VERIFICATION_READY, EventType.KEY_VERIFICATION_KEY, EventType.ENCRYPTED, + EventType.POLL_START, EventType.POLL_RESPONSE, EventType.POLL_END ) @@ -208,6 +216,14 @@ internal class EventRelationsAggregationProcessor @Inject constructor( } } } + EventType.POLL_START -> { + val content: MessagePollContent? = event.content.toModel() + if (content?.relatesTo?.type == RelationType.REPLACE) { + Timber.v("###REPLACE in room $roomId for event ${event.eventId}") + // A replace! + handleReplace(realm, event, content, roomId, isLocalEcho) + } + } EventType.POLL_RESPONSE -> { event.content.toModel<MessagePollResponseContent>(catchError = true)?.let { handleResponse(realm, event, it, roomId, isLocalEcho) @@ -274,6 +290,20 @@ internal class EventRelationsAggregationProcessor @Inject constructor( Timber.v("###REPLACE ignoring event for summary, it's known $eventId") return } + + ContentMapper + .map(eventAnnotationsSummaryEntity.pollResponseSummary?.aggregatedContent) + ?.toModel<PollSummaryContent>() + ?.apply { + totalVotes = 0 + winnerVoteCount = 0 + votes = emptyList() + votesSummary = emptyMap() + } + ?.apply { + eventAnnotationsSummaryEntity.pollResponseSummary?.aggregatedContent = ContentMapper.map(toContent()) + } + val txId = event.unsignedData?.transactionId // is it a remote echo? if (!isLocalEcho && existingSummary.editions.any { it.eventId == txId }) { @@ -315,6 +345,8 @@ internal class EventRelationsAggregationProcessor @Inject constructor( val targetEventId = relatedEventId ?: content.relatesTo?.eventId ?: return val eventTimestamp = event.originServerTs ?: return + val targetPollContent = getPollContent(roomId, targetEventId) ?: return + // ok, this is a poll response var existing = EventAnnotationsSummaryEntity.where(realm, roomId, targetEventId).findFirst() if (existing == null) { @@ -355,6 +387,12 @@ internal class EventRelationsAggregationProcessor @Inject constructor( Timber.d("## POLL Ignoring malformed response no option eventId:$eventId content: ${event.content}") } + // Check if this option is in available options + if (!targetPollContent.pollCreationInfo?.answers?.map { it.id }?.contains(option).orFalse()) { + Timber.v("## POLL $targetEventId doesn't contain option $option") + return + } + val votes = sumModel.votes?.toMutableList() ?: ArrayList() val existingVoteIndex = votes.indexOfFirst { it.userId == senderId } if (existingVoteIndex != -1) { @@ -408,6 +446,17 @@ internal class EventRelationsAggregationProcessor @Inject constructor( isLocalEcho: Boolean) { val pollEventId = content.relatesTo?.eventId ?: return + val pollOwnerId = getPollEvent(roomId, pollEventId)?.root?.senderId + val isPollOwner = pollOwnerId == event.senderId + + val powerLevelsHelper = stateEventDataSource.getStateEvent(roomId, EventType.STATE_ROOM_POWER_LEVELS, QueryStringValue.NoCondition) + ?.content?.toModel<PowerLevelsContent>() + ?.let { PowerLevelsHelper(it) } + if (!isPollOwner && !powerLevelsHelper?.isUserAbleToRedact(event.senderId ?: "").orFalse()) { + Timber.v("## Received poll.end event $pollEventId but user ${event.senderId} doesn't have enough power level in room $roomId") + return + } + var existing = EventAnnotationsSummaryEntity.where(realm, roomId, pollEventId).findFirst() if (existing == null) { Timber.v("## POLL creating new relation summary for $pollEventId") @@ -425,14 +474,6 @@ internal class EventRelationsAggregationProcessor @Inject constructor( return } - val powerLevelsHelper = stateEventDataSource.getStateEvent(roomId, EventType.STATE_ROOM_POWER_LEVELS, QueryStringValue.NoCondition) - ?.content?.toModel<PowerLevelsContent>() - ?.let { PowerLevelsHelper(it) } - if (!powerLevelsHelper?.isUserAbleToRedact(event.senderId ?: "").orFalse()) { - Timber.v("## Received poll.end event $pollEventId but user ${event.senderId} doesn't have enough power level in room $roomId") - return - } - val txId = event.unsignedData?.transactionId // is it a remote echo? if (!isLocalEcho && existingPollSummary.sourceLocalEchoEvents.contains(txId)) { @@ -446,6 +487,21 @@ internal class EventRelationsAggregationProcessor @Inject constructor( existingPollSummary.closedTime = event.originServerTs } + private fun getPollEvent(roomId: String, eventId: String): TimelineEvent? { + val session = sessionManager.getSessionComponent(sessionId)?.session() + return session?.getRoom(roomId)?.getTimeLineEvent(eventId) ?: return null.also { + Timber.v("## POLL target poll event $eventId not found in room $roomId") + } + } + + private fun getPollContent(roomId: String, eventId: String): MessagePollContent? { + val pollEvent = getPollEvent(roomId, eventId) ?: return null + + return pollEvent.getLastMessageContent() as? MessagePollContent ?: return null.also { + Timber.v("## POLL target poll event $eventId content is malformed") + } + } + private fun handleInitialAggregatedRelations(realm: Realm, event: Event, roomId: String, diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/membership/DefaultMembershipService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/membership/DefaultMembershipService.kt index 6cf82dde44d8a20d1d973d1e59e870f5674e54d3..49b58aa7655fd7989b59942d6707dfe651f614f9 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/membership/DefaultMembershipService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/membership/DefaultMembershipService.kt @@ -125,7 +125,7 @@ internal class DefaultMembershipService @AssistedInject constructor( membershipAdminTask.execute(params) } - override suspend fun kick(userId: String, reason: String?) { + override suspend fun remove(userId: String, reason: String?) { val params = MembershipAdminTask.Params(MembershipAdminTask.Type.KICK, roomId, userId, reason) membershipAdminTask.execute(params) } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/relation/DefaultRelationService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/relation/DefaultRelationService.kt index 07927b1412ea95593536478a7a1db46971a431cb..cbcc108dddf3aa1905a21115a38a5b10d69ef7dc 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/relation/DefaultRelationService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/relation/DefaultRelationService.kt @@ -24,6 +24,7 @@ import dagger.assisted.AssistedInject import org.matrix.android.sdk.api.MatrixCallback import org.matrix.android.sdk.api.session.events.model.Event import org.matrix.android.sdk.api.session.room.model.EventAnnotationsSummary +import org.matrix.android.sdk.api.session.room.model.message.PollType import org.matrix.android.sdk.api.session.room.model.relation.RelationService import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent import org.matrix.android.sdk.api.util.Cancelable @@ -112,6 +113,13 @@ internal class DefaultRelationService @AssistedInject constructor( .executeBy(taskExecutor) } + override fun editPoll(targetEvent: TimelineEvent, + pollType: PollType, + question: String, + options: List<String>): Cancelable { + return eventEditor.editPoll(targetEvent, pollType, question, options) + } + override fun editTextMessage(targetEvent: TimelineEvent, msgType: String, newBodyText: CharSequence, diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/relation/EventEditor.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/relation/EventEditor.kt index a666d40fc319d96d1d3b0e851314358bf83c58fe..a40a8df4435dc72ba512e60924a80e0f246e4e31 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/relation/EventEditor.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/relation/EventEditor.kt @@ -18,6 +18,7 @@ package org.matrix.android.sdk.internal.session.room.relation import org.matrix.android.sdk.api.session.events.model.Event import org.matrix.android.sdk.api.session.room.model.message.MessageType +import org.matrix.android.sdk.api.session.room.model.message.PollType import org.matrix.android.sdk.api.session.room.send.SendState import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent import org.matrix.android.sdk.api.util.Cancelable @@ -46,13 +47,11 @@ internal class EventEditor @Inject constructor(private val eventSenderProcessor: val editedEvent = eventFactory.createTextEvent(roomId, msgType, newBodyText, newBodyAutoMarkdown).copy( eventId = targetEvent.eventId ) - updateFailedEchoWithEvent(roomId, targetEvent.eventId, editedEvent) - return eventSenderProcessor.postEvent(editedEvent, cryptoSessionInfoProvider.isRoomEncrypted(roomId)) + return sendFailedEvent(targetEvent, editedEvent) } else if (targetEvent.root.sendState.isSent()) { val event = eventFactory .createReplaceTextEvent(roomId, targetEvent.eventId, newBodyText, newBodyAutoMarkdown, msgType, compatibilityBodyText) - .also { localEchoRepository.createLocalEcho(it) } - return eventSenderProcessor.postEvent(event, cryptoSessionInfoProvider.isRoomEncrypted(roomId)) + return sendReplaceEvent(roomId, event) } else { // Should we throw? Timber.w("Can't edit a sending event") @@ -60,6 +59,37 @@ internal class EventEditor @Inject constructor(private val eventSenderProcessor: } } + fun editPoll(targetEvent: TimelineEvent, + pollType: PollType, + question: String, + options: List<String>): Cancelable { + val roomId = targetEvent.roomId + if (targetEvent.root.sendState.hasFailed()) { + val editedEvent = eventFactory.createPollEvent(roomId, pollType, question, options).copy( + eventId = targetEvent.eventId + ) + return sendFailedEvent(targetEvent, editedEvent) + } else if (targetEvent.root.sendState.isSent()) { + val event = eventFactory + .createPollReplaceEvent(roomId, pollType, targetEvent.eventId, question, options) + return sendReplaceEvent(roomId, event) + } else { + Timber.w("Can't edit a sending event") + return NoOpCancellable + } + } + + private fun sendFailedEvent(targetEvent: TimelineEvent, editedEvent: Event): Cancelable { + val roomId = targetEvent.roomId + updateFailedEchoWithEvent(roomId, targetEvent.eventId, editedEvent) + return eventSenderProcessor.postEvent(editedEvent, cryptoSessionInfoProvider.isRoomEncrypted(roomId)) + } + + private fun sendReplaceEvent(roomId: String, editedEvent: Event): Cancelable { + localEchoRepository.createLocalEcho(editedEvent) + return eventSenderProcessor.postEvent(editedEvent, cryptoSessionInfoProvider.isRoomEncrypted(roomId)) + } + fun editReply(replyToEdit: TimelineEvent, originalTimelineEvent: TimelineEvent, newBodyText: String, 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 fb2fb3950af0b6c410cfa4f52d1a4507737d1ca3..5662a72cb82e5336a11e2604f32cad19ccf29e11 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 @@ -37,6 +37,7 @@ import org.matrix.android.sdk.api.session.room.model.message.MessageFileContent import org.matrix.android.sdk.api.session.room.model.message.MessageImageContent import org.matrix.android.sdk.api.session.room.model.message.MessageVideoContent import org.matrix.android.sdk.api.session.room.model.message.MessageWithAttachmentContent +import org.matrix.android.sdk.api.session.room.model.message.PollType import org.matrix.android.sdk.api.session.room.model.message.getFileUrl import org.matrix.android.sdk.api.session.room.send.SendService import org.matrix.android.sdk.api.session.room.send.SendState @@ -103,8 +104,8 @@ internal class DefaultSendService @AssistedInject constructor( .let { sendEvent(it) } } - override fun sendPoll(question: String, options: List<String>): Cancelable { - return localEchoEventFactory.createPollEvent(roomId, question, options) + override fun sendPoll(pollType: PollType, question: String, options: List<String>): Cancelable { + return localEchoEventFactory.createPollEvent(roomId, pollType, question, options) .also { createLocalEcho(it) } .let { sendEvent(it) } } @@ -121,6 +122,12 @@ internal class DefaultSendService @AssistedInject constructor( .let { sendEvent(it) } } + override fun sendLocation(latitude: Double, longitude: Double, uncertainty: Double?): Cancelable { + return localEchoEventFactory.createLocationEvent(roomId, latitude, longitude, uncertainty) + .also { createLocalEcho(it) } + .let { sendEvent(it) } + } + override fun redactEvent(event: Event, reason: String?): Cancelable { // TODO manage media/attachements? val redactionEcho = localEchoEventFactory.createRedactEvent(roomId, event.eventId!!, reason) 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 c4caedc407769754c9e200f7c3a5d76aa9423d39..1e46602411cd68a8cd97c3bdaa294e1b7a8ecec3 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 @@ -32,6 +32,9 @@ 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.LocationAsset +import org.matrix.android.sdk.api.session.room.model.message.LocationAssetType +import org.matrix.android.sdk.api.session.room.model.message.LocationInfo import org.matrix.android.sdk.api.session.room.model.message.MessageAudioContent import org.matrix.android.sdk.api.session.room.model.message.MessageContent import org.matrix.android.sdk.api.session.room.model.message.MessageContentWithFormattedBody @@ -39,6 +42,7 @@ import org.matrix.android.sdk.api.session.room.model.message.MessageEndPollConte import org.matrix.android.sdk.api.session.room.model.message.MessageFileContent import org.matrix.android.sdk.api.session.room.model.message.MessageFormat import org.matrix.android.sdk.api.session.room.model.message.MessageImageContent +import org.matrix.android.sdk.api.session.room.model.message.MessageLocationContent import org.matrix.android.sdk.api.session.room.model.message.MessagePollContent import org.matrix.android.sdk.api.session.room.model.message.MessagePollResponseContent import org.matrix.android.sdk.api.session.room.model.message.MessageTextContent @@ -48,6 +52,7 @@ import org.matrix.android.sdk.api.session.room.model.message.PollAnswer import org.matrix.android.sdk.api.session.room.model.message.PollCreationInfo import org.matrix.android.sdk.api.session.room.model.message.PollQuestion import org.matrix.android.sdk.api.session.room.model.message.PollResponse +import org.matrix.android.sdk.api.session.room.model.message.PollType import org.matrix.android.sdk.api.session.room.model.message.ThumbnailInfo import org.matrix.android.sdk.api.session.room.model.message.VideoInfo import org.matrix.android.sdk.api.session.room.model.relation.ReactionContent @@ -61,6 +66,8 @@ import org.matrix.android.sdk.internal.di.UserId import org.matrix.android.sdk.internal.session.content.ThumbnailExtractor import org.matrix.android.sdk.internal.session.permalinks.PermalinkFactory import org.matrix.android.sdk.internal.session.room.send.pills.TextPillsUtils +import java.util.UUID +import java.util.concurrent.TimeUnit import javax.inject.Inject /** @@ -124,6 +131,45 @@ internal class LocalEchoEventFactory @Inject constructor( )) } + private fun createPollContent(question: String, + options: List<String>, + pollType: PollType): MessagePollContent { + return MessagePollContent( + pollCreationInfo = PollCreationInfo( + question = PollQuestion( + question = question + ), + kind = pollType, + answers = options.map { option -> + PollAnswer( + id = UUID.randomUUID().toString(), + answer = option + ) + } + ) + ) + } + + fun createPollReplaceEvent(roomId: String, + pollType: PollType, + targetEventId: String, + question: String, + options: List<String>): Event { + val newContent = MessagePollContent( + relatesTo = RelationDefaultContent(RelationType.REPLACE, targetEventId), + newContent = createPollContent(question, options, pollType).toContent() + ) + val localId = LocalEcho.createLocalEchoId() + return Event( + roomId = roomId, + originServerTs = dummyOriginServerTs(), + senderId = userId, + eventId = localId, + type = EventType.POLL_START, + content = newContent.toContent() + ) + } + fun createPollReplyEvent(roomId: String, pollEventId: String, answerId: String): Event { @@ -149,21 +195,10 @@ internal class LocalEchoEventFactory @Inject constructor( } fun createPollEvent(roomId: String, + pollType: PollType, question: String, options: List<String>): Event { - val content = MessagePollContent( - pollCreationInfo = PollCreationInfo( - question = PollQuestion( - question = question - ), - answers = options.mapIndexed { index, option -> - PollAnswer( - id = "$index-$option", - answer = option - ) - } - ) - ) + val content = createPollContent(question, options, pollType) val localId = LocalEcho.createLocalEchoId() return Event( roomId = roomId, @@ -194,6 +229,27 @@ internal class LocalEchoEventFactory @Inject constructor( unsignedData = UnsignedData(age = null, transactionId = localId)) } + fun createLocationEvent(roomId: String, + latitude: Double, + longitude: Double, + uncertainty: Double?): Event { + val geoUri = buildGeoUri(latitude, longitude, uncertainty) + val content = MessageLocationContent( + geoUri = geoUri, + body = geoUri, + locationInfo = LocationInfo( + geoUri = geoUri, + description = geoUri + ), + locationAsset = LocationAsset( + type = LocationAssetType.SELF + ), + ts = TimeUnit.MILLISECONDS.toSeconds(System.currentTimeMillis()), + text = geoUri + ) + return createMessageEvent(roomId, content) + } + fun createReplaceTextOfReply(roomId: String, eventReplaced: TimelineEvent, originalEvent: TimelineEvent, @@ -480,6 +536,23 @@ internal class LocalEchoEventFactory @Inject constructor( } } + /** + * Returns RFC5870 formatted geo uri 'geo:latitude,longitude;uncertainty' like 'geo:40.05,29.24;30' + * Uncertainty of the location is in meters and not required. + */ + private fun buildGeoUri(latitude: Double, longitude: Double, uncertainty: Double?): String { + return buildString { + append("geo:") + append(latitude) + append(",") + append(longitude) + uncertainty?.let { + append(";") + append(it) + } + } + } + /* * { "content": { 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 3be01762e77cc57e65df3c986d11b83286ba33f8..eb691616140e3e25a1e4f50cc4699abf20e4fd00 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 @@ -23,8 +23,8 @@ import kotlinx.coroutines.launch import kotlinx.coroutines.withContext import org.matrix.android.sdk.api.auth.data.SessionParams import org.matrix.android.sdk.api.failure.Failure -import org.matrix.android.sdk.api.failure.MatrixError import org.matrix.android.sdk.api.failure.getRetryDelay +import org.matrix.android.sdk.api.failure.isLimitExceededError import org.matrix.android.sdk.api.session.Session import org.matrix.android.sdk.api.session.crypto.CryptoService import org.matrix.android.sdk.api.session.events.model.Event @@ -145,17 +145,17 @@ internal class EventSenderProcessorCoroutine @Inject constructor( task.execute() } catch (exception: Throwable) { when { - exception is IOException || exception is Failure.NetworkConnection -> { + exception is IOException || exception is Failure.NetworkConnection -> { canReachServer.set(false) task.markAsFailedOrRetry(exception, 0) } - (exception is Failure.ServerError && exception.error.code == MatrixError.M_LIMIT_EXCEEDED) -> { + (exception.isLimitExceededError()) -> { task.markAsFailedOrRetry(exception, exception.getRetryDelay(3_000)) } - exception is CancellationException -> { + exception is CancellationException -> { Timber.v("## $task has been cancelled, try next task") } - else -> { + else -> { Timber.v("## un-retryable error for $task, try next task") // this task is in error, check next one? task.onTaskFailed() 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 f32890f3fb42cd912e68f83b7a91aa7536e2345b..1ee313919401fcd72917d9d14ba048ad9bd71132 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 @@ -23,7 +23,7 @@ import org.matrix.android.sdk.api.auth.data.SessionParams import org.matrix.android.sdk.api.auth.data.sessionId import org.matrix.android.sdk.api.extensions.tryOrNull import org.matrix.android.sdk.api.failure.Failure -import org.matrix.android.sdk.api.failure.MatrixError +import org.matrix.android.sdk.api.failure.isLimitExceededError import org.matrix.android.sdk.api.failure.isTokenError import org.matrix.android.sdk.api.session.Session import org.matrix.android.sdk.api.session.crypto.CryptoService @@ -171,7 +171,7 @@ internal class EventSenderProcessorThread @Inject constructor( break@retryLoop } catch (exception: Throwable) { when { - exception is IOException || exception is Failure.NetworkConnection -> { + exception is IOException || exception is Failure.NetworkConnection -> { canReachServer = false if (task.retryCount.getAndIncrement() >= 3) task.onTaskFailed() while (!canReachServer) { @@ -180,7 +180,7 @@ internal class EventSenderProcessorThread @Inject constructor( waitForNetwork() } } - (exception is Failure.ServerError && exception.error.code == MatrixError.M_LIMIT_EXCEEDED) -> { + (exception.isLimitExceededError()) -> { if (task.retryCount.getAndIncrement() >= 3) task.onTaskFailed() Timber.v("## SendThread retryLoop retryable error for $task reason: ${exception.localizedMessage}") // wait a bit @@ -188,17 +188,17 @@ internal class EventSenderProcessorThread @Inject constructor( sleep(3_000) continue@retryLoop } - exception.isTokenError() -> { + exception.isTokenError() -> { Timber.v("## SendThread retryLoop retryable TOKEN error, interrupt") // we can exit the loop task.onTaskFailed() throw InterruptedException() } - exception is CancellationException -> { + exception is CancellationException -> { Timber.v("## SendThread task has been cancelled") break@retryLoop } - else -> { + else -> { Timber.v("## SendThread retryLoop Un-Retryable error, try next task") // this task is in error, check next one? task.onTaskFailed() 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 4ec27976a27ee769a9aa4e9d6316d4539b15ea19..417417f439424341b29830d8d36995fdb61f9ff6 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 @@ -68,7 +68,7 @@ internal class DefaultStateService @AssistedInject constructor(@Assisted private override suspend fun sendStateEvent( eventType: String, - stateKey: String?, + stateKey: String, body: JsonDict ) { val params = SendStateTask.Params( @@ -92,7 +92,7 @@ internal class DefaultStateService @AssistedInject constructor(@Assisted private sendStateEvent( eventType = EventType.STATE_ROOM_TOPIC, body = mapOf("topic" to topic), - stateKey = null + stateKey = "" ) } @@ -100,7 +100,7 @@ internal class DefaultStateService @AssistedInject constructor(@Assisted private sendStateEvent( eventType = EventType.STATE_ROOM_NAME, body = mapOf("name" to name), - stateKey = null + stateKey = "" ) } @@ -117,7 +117,7 @@ internal class DefaultStateService @AssistedInject constructor(@Assisted private // Sort for the cleanup .sorted() ).toContent(), - stateKey = null + stateKey = "" ) } @@ -125,7 +125,7 @@ internal class DefaultStateService @AssistedInject constructor(@Assisted private sendStateEvent( eventType = EventType.STATE_ROOM_HISTORY_VISIBILITY, body = mapOf("history_visibility" to readability), - stateKey = null + stateKey = "" ) } @@ -142,14 +142,14 @@ internal class DefaultStateService @AssistedInject constructor(@Assisted private sendStateEvent( eventType = EventType.STATE_ROOM_JOIN_RULES, body = body, - stateKey = null + stateKey = "" ) } if (guestAccess != null) { sendStateEvent( eventType = EventType.STATE_ROOM_GUEST_ACCESS, body = mapOf("guest_access" to guestAccess), - stateKey = null + stateKey = "" ) } } @@ -159,7 +159,7 @@ internal class DefaultStateService @AssistedInject constructor(@Assisted private sendStateEvent( eventType = EventType.STATE_ROOM_AVATAR, body = mapOf("url" to response.contentUri), - stateKey = null + stateKey = "" ) } @@ -167,7 +167,7 @@ internal class DefaultStateService @AssistedInject constructor(@Assisted private sendStateEvent( eventType = EventType.STATE_ROOM_AVATAR, body = emptyMap(), - stateKey = null + stateKey = "" ) } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/state/SendStateTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/state/SendStateTask.kt index 998e116a0ec94de5759a30bc59ba955812448f7a..56c69a05a682e7cbcb89763d586ed4317d15be55 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/state/SendStateTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/state/SendStateTask.kt @@ -26,7 +26,7 @@ import javax.inject.Inject internal interface SendStateTask : Task<SendStateTask.Params, Unit> { data class Params( val roomId: String, - val stateKey: String?, + val stateKey: String, val eventType: String, val body: JsonDict ) @@ -39,7 +39,7 @@ internal class DefaultSendStateTask @Inject constructor( override suspend fun execute(params: SendStateTask.Params) { return executeRequest(globalErrorReceiver) { - if (params.stateKey == null) { + if (params.stateKey.isEmpty()) { roomAPI.sendStateEvent( roomId = params.roomId, stateEventType = params.eventType, 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 621a08a414ec64681926eaae5cc34561a90451c3..1ee62ad774eccaa983f57ce49be68236ea26d2e6 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 @@ -16,10 +16,13 @@ package org.matrix.android.sdk.internal.session.sync +import android.os.SystemClock import okhttp3.ResponseBody import org.matrix.android.sdk.api.logger.LoggerTag +import org.matrix.android.sdk.api.session.Session import org.matrix.android.sdk.api.session.initsync.InitSyncStep import org.matrix.android.sdk.api.session.initsync.SyncStatusService +import org.matrix.android.sdk.api.session.statistics.StatisticEvent import org.matrix.android.sdk.api.session.sync.model.LazyRoomSyncEphemeral import org.matrix.android.sdk.api.session.sync.model.SyncResponse import org.matrix.android.sdk.internal.di.SessionFilesDirectory @@ -28,6 +31,8 @@ import org.matrix.android.sdk.internal.network.GlobalErrorReceiver import org.matrix.android.sdk.internal.network.TimeOutInterceptor import org.matrix.android.sdk.internal.network.executeRequest import org.matrix.android.sdk.internal.network.toFailure +import org.matrix.android.sdk.internal.session.SessionListeners +import org.matrix.android.sdk.internal.session.dispatchTo import org.matrix.android.sdk.internal.session.filter.FilterRepository import org.matrix.android.sdk.internal.session.homeserver.GetHomeServerCapabilitiesTask import org.matrix.android.sdk.internal.session.initsync.DefaultSyncStatusService @@ -49,7 +54,8 @@ internal interface SyncTask : Task<SyncTask.Params, SyncResponse> { data class Params( val timeout: Long, - val presence: SyncPresence? + val presence: SyncPresence?, + val afterPause: Boolean ) } @@ -62,6 +68,8 @@ internal class DefaultSyncTask @Inject constructor( private val syncTokenStore: SyncTokenStore, private val getHomeServerCapabilitiesTask: GetHomeServerCapabilitiesTask, private val userStore: UserStore, + private val session: Session, + private val sessionListeners: SessionListeners, private val syncTaskSequencer: SyncTaskSequencer, private val globalErrorReceiver: GlobalErrorReceiver, @SessionFilesDirectory @@ -105,6 +113,7 @@ internal class DefaultSyncTask @Inject constructor( val readTimeOut = (params.timeout + TIMEOUT_MARGIN).coerceAtLeast(TimeOutInterceptor.DEFAULT_LONG_TIMEOUT) var syncResponseToReturn: SyncResponse? = null + val syncStatisticsData = SyncStatisticsData(isInitialSync, params.afterPause) if (isInitialSync) { Timber.tag(loggerTag.value).d("INIT_SYNC with filter: ${requestParams["filter"]}") val initSyncStrategy = initialSyncStrategy @@ -112,7 +121,7 @@ internal class DefaultSyncTask @Inject constructor( if (initSyncStrategy is InitialSyncStrategy.Optimized) { roomSyncEphemeralTemporaryStore.reset() workingDir.mkdirs() - val file = downloadInitSyncResponse(requestParams) + val file = downloadInitSyncResponse(requestParams, syncStatisticsData) syncResponseToReturn = reportSubtask(defaultSyncStatusService, InitSyncStep.ImportingAccount, 1, 0.7F) { handleSyncFile(file, initSyncStrategy) } @@ -127,6 +136,9 @@ internal class DefaultSyncTask @Inject constructor( ) } } + // We cannot distinguish request and download in this case. + syncStatisticsData.requestInitSyncTime = SystemClock.elapsedRealtime() + syncStatisticsData.downloadInitSyncTime = syncStatisticsData.requestInitSyncTime logDuration("INIT_SYNC Database insertion", loggerTag) { syncResponseHandler.handleResponse(syncResponse, token, defaultSyncStatusService) } @@ -161,12 +173,15 @@ internal class DefaultSyncTask @Inject constructor( Timber.tag(loggerTag.value).d("Incremental sync done") defaultSyncStatusService.setStatus(SyncStatusService.Status.IncrementalSyncDone) } + syncStatisticsData.treatmentSyncTime = SystemClock.elapsedRealtime() + syncStatisticsData.nbOfRooms = syncResponseToReturn?.rooms?.join?.size ?: 0 + sendStatistics(syncStatisticsData) Timber.tag(loggerTag.value).d("Sync task finished on Thread: ${Thread.currentThread().name}") // Should throw if null as it's a mandatory value. return syncResponseToReturn!! } - private suspend fun downloadInitSyncResponse(requestParams: Map<String, String>): File { + private suspend fun downloadInitSyncResponse(requestParams: Map<String, String>, syncStatisticsData: SyncStatisticsData): File { val workingFile = File(workingDir, "initSync.json") val status = initialSyncStatusRepository.getStep() if (workingFile.exists() && status >= InitialSyncStatus.STEP_DOWNLOADED) { @@ -181,7 +196,7 @@ internal class DefaultSyncTask @Inject constructor( getSyncResponse(requestParams, MAX_NUMBER_OF_RETRY_AFTER_TIMEOUT) } } - + syncStatisticsData.requestInitSyncTime = SystemClock.elapsedRealtime() if (syncResponse.isSuccessful) { logDuration("INIT_SYNC Download and save to file", loggerTag) { reportSubtask(defaultSyncStatusService, InitSyncStep.Downloading, 1, 0.1f) { @@ -196,6 +211,7 @@ internal class DefaultSyncTask @Inject constructor( throw syncResponse.toFailure(globalErrorReceiver) .also { Timber.tag(loggerTag.value).w("INIT_SYNC request failure: $this") } } + syncStatisticsData.downloadInitSyncTime = SystemClock.elapsedRealtime() initialSyncStatusRepository.setStep(InitialSyncStatus.STEP_DOWNLOADED) } return workingFile @@ -239,6 +255,45 @@ internal class DefaultSyncTask @Inject constructor( } } + /** + * Aggregator to send stat event. + */ + class SyncStatisticsData( + val isInitSync: Boolean, + val isAfterPause: Boolean + ) { + val startTime = SystemClock.elapsedRealtime() + var requestInitSyncTime = startTime + var downloadInitSyncTime = startTime + var treatmentSyncTime = startTime + var nbOfRooms: Int = 0 + } + + private fun sendStatistics(data: SyncStatisticsData) { + sendStatisticEvent( + if (data.isInitSync) { + (StatisticEvent.InitialSyncRequest( + requestDurationMs = (data.requestInitSyncTime - data.startTime).toInt(), + downloadDurationMs = (data.downloadInitSyncTime - data.requestInitSyncTime).toInt(), + treatmentDurationMs = (data.treatmentSyncTime - data.downloadInitSyncTime).toInt(), + nbOfJoinedRooms = data.nbOfRooms, + )) + } else { + StatisticEvent.SyncTreatment( + durationMs = (data.treatmentSyncTime - data.startTime).toInt(), + afterPause = data.isAfterPause, + nbOfJoinedRooms = data.nbOfRooms + ) + } + ) + } + + private fun sendStatisticEvent(statisticEvent: StatisticEvent) { + session.dispatchTo(sessionListeners) { session, listener -> + listener.onStatisticsEvent(session, statisticEvent) + } + } + companion object { private const val MAX_NUMBER_OF_RETRY_AFTER_TIMEOUT = 50 diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/handler/room/RoomSyncHandler.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/handler/room/RoomSyncHandler.kt index 24722445be4842e186218cdd70ab3fd29ba0254b..170e8b734bbc96a900b434fe40536e6f55ea4bd3 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/handler/room/RoomSyncHandler.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/handler/room/RoomSyncHandler.kt @@ -350,7 +350,7 @@ internal class RoomSyncHandler @Inject constructor(private val readReceiptHandle aggregator: SyncResponsePostTreatmentAggregator): ChunkEntity { val lastChunk = ChunkEntity.findLastForwardChunkOfRoom(realm, roomEntity.roomId) if (isLimited && lastChunk != null) { - lastChunk.deleteOnCascade(deleteStateEvents = true, canDeleteRoot = true) + lastChunk.deleteOnCascade(deleteStateEvents = false, canDeleteRoot = true) } val chunkEntity = if (!isLimited && lastChunk != null) { lastChunk @@ -426,6 +426,10 @@ internal class RoomSyncHandler @Inject constructor(private val readReceiptHandle } } } + + // Handle deletion of [stuck] local echos if needed + deleteLocalEchosIfNeeded(insertType, roomEntity, eventList) + // posting new events to timeline if any is registered timelineInput.onNewTimelineEvents(roomId = roomId, eventIds = eventIds) return chunkEntity @@ -478,4 +482,49 @@ internal class RoomSyncHandler @Inject constructor(private val readReceiptHandle return result } + + /** + * There are multiple issues like #516 that report stuck local echo events + * at the bottom of each room timeline. + * + * That can happen when a message is SENT but not received back from the /sync. + * Until now we use unsignedData.transactionId to determine whether or not the local + * event should be deleted on every /sync. However, this is partially correct, lets have a look + * at the following scenario: + * + * [There is no Internet connection] --> [10 Messages are sent] --> [The 10 messages are in the queue] --> + * [Internet comes back for 1 second] --> [3 messages are sent] --> [Internet drops again] --> + * [No /sync response is triggered | home server can even replied with /sync but never arrived while we are offline] + * + * So the state until now is that we have 7 pending events to send and 3 sent but not received them back from /sync + * Subsequently, those 3 local messages will not be deleted while there is no transactionId from the /sync + * + * lets continue: + * [Now lets assume that in the same room another user sent 15 events] --> + * [We are finally back online!] --> + * [We will receive the 10 latest events for the room and of course sent the pending 7 messages] --> + * Now /sync response will NOT contain the 3 local messages so our events will stuck in the device. + * + * Someone can say, yes but it will come with the rooms/{roomId}/messages while paginating, + * so the problem will be solved. No that is not the case for two reasons: + * 1. rooms/{roomId}/messages response do not contain the unsignedData.transactionId so we cannot know which event + * to delete + * 2. even if transactionId was there, currently we are not deleting it from the pagination + * + * --------------------------------------------------------------------------------------------- + * While we cannot know when a specific event arrived from the pagination (no transactionId included), after each room /sync + * we clear all SENT events, and we are sure that we will receive it from /sync or pagination + */ + private fun deleteLocalEchosIfNeeded(insertType: EventInsertType, roomEntity: RoomEntity, eventList: List<Event>) { + // Skip deletion if we are on initial sync + if (insertType == EventInsertType.INITIAL_SYNC) return + // Skip deletion if there are no timeline events or there is no event received from the current user + if (eventList.firstOrNull { it.senderId == userId } == null) return + roomEntity.sendingTimelineEvents.filter { timelineEvent -> + timelineEvent.root?.sendState == SendState.SENT + }.forEach { + roomEntity.sendingTimelineEvents.remove(it) + it.deleteOnCascade(true) + } + } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/job/SyncService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/job/SyncService.kt index c17b31b910eef9f6c5f453538f89ea7b149e92c8..0ecf91f6fae6137fd13ba99015e729de8b9b959a 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/job/SyncService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/job/SyncService.kt @@ -152,7 +152,7 @@ abstract class SyncService : Service() { private suspend fun doSync() { Timber.v("## Sync: Execute sync request with timeout $syncTimeoutSeconds seconds") - val params = SyncTask.Params(syncTimeoutSeconds * 1000L, SyncPresence.Offline) + val params = SyncTask.Params(syncTimeoutSeconds * 1000L, SyncPresence.Offline, afterPause = false) try { // never do that in foreground, let the syncThread work syncTask.execute(params) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/job/SyncThread.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/job/SyncThread.kt index b6ea7a68f76fdbfc5bc2ce7e3fec2176e2d45946..2460720adc68bc1fd4206b53b267463936f8c007 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/job/SyncThread.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/job/SyncThread.kt @@ -173,13 +173,14 @@ internal class SyncThread @Inject constructor(private val syncTask: SyncTask, if (state !is SyncState.Running) { updateStateTo(SyncState.Running(afterPause = true)) } + val afterPause = state.let { it is SyncState.Running && it.afterPause } val timeout = when { - previousSyncResponseHasToDevice -> 0L /* Force timeout to 0 */ - state.let { it is SyncState.Running && it.afterPause } -> 0L /* No timeout after a pause */ - else -> DEFAULT_LONG_POOL_TIMEOUT + previousSyncResponseHasToDevice -> 0L /* Force timeout to 0 */ + afterPause -> 0L /* No timeout after a pause */ + else -> DEFAULT_LONG_POOL_TIMEOUT } Timber.tag(loggerTag.value).d("Execute sync request with timeout $timeout") - val params = SyncTask.Params(timeout, SyncPresence.Online) + val params = SyncTask.Params(timeout, SyncPresence.Online, afterPause = afterPause) val sync = syncScope.launch { previousSyncResponseHasToDevice = doSync(params) } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/job/SyncWorker.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/job/SyncWorker.kt index 2f1241f4d8f15680ab69ba5d73d61400d9aa22e8..423a4e553f5236be42df039a3db3f81c246b9083 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/job/SyncWorker.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/job/SyncWorker.kt @@ -113,7 +113,7 @@ internal class SyncWorker(context: Context, workerParameters: WorkerParameters, * Will return true if the sync response contains some toDevice events. */ private suspend fun doSync(timeout: Long): Boolean { - val taskParams = SyncTask.Params(timeout * 1000, SyncPresence.Offline) + val taskParams = SyncTask.Params(timeout * 1000, SyncPresence.Offline, afterPause = false) val syncResponse = syncTask.execute(taskParams) return syncResponse.toDevice?.events?.isNotEmpty().orFalse() } @@ -151,6 +151,7 @@ internal class SyncWorker(context: Context, workerParameters: WorkerParameters, sessionId = sessionId, timeout = serverTimeoutInSeconds, delay = delayInSeconds, + periodic = true, forceImmediate = forceImmediate ) ) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/terms/DefaultTermsService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/terms/DefaultTermsService.kt index 313fb6319d5ef3c32a4cbbb06d68d1997b12b7e2..6205e3e4b16e2ef5c7f0f4aaabe7091140795aa2 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/terms/DefaultTermsService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/terms/DefaultTermsService.kt @@ -64,7 +64,7 @@ internal class DefaultTermsService @Inject constructor( */ override suspend fun getHomeserverTerms(baseUrl: String): TermsResponse { return try { - val request = baseUrl + NetworkConstants.URI_API_PREFIX_PATH_R0 + "register" + val request = baseUrl.ensureTrailingSlash() + NetworkConstants.URI_API_PREFIX_PATH_R0 + "register" executeRequest(null) { termsAPI.register(request) }