diff --git a/CHANGES.md b/CHANGES.md index d442df706c80afc1e4da5ca66d07741de89f93a6..8d3820ea8f39d2695d53ce8db7597cd885d8a543 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,5 +1,15 @@ Please also refer to the Changelog of Element Android: https://github.com/vector-im/element-android/blob/master/CHANGES.md +Changes in Matrix-SDK 1.1.4 (2021-04-09) +=================================================== + +Imported from Element 1.1.4. (https://github.com/vector-im/element-android/releases/tag/v1.1.4) + +Changes in Matrix-SDK 1.1.1 (2021-03-10) +=================================================== + +Imported from Element 1.1.1. (https://github.com/vector-im/element-android/releases/tag/v1.1.1) + Changes in Matrix-SDK 1.0.16 (2021-02-08) =================================================== diff --git a/build.gradle b/build.gradle index f8730fb59ed71055c2b27e3faf8a8f2a1651d04b..affee31588a15c069a98159df4f174244dd32f73 100644 --- a/build.gradle +++ b/build.gradle @@ -2,7 +2,7 @@ buildscript { // Ref: https://kotlinlang.org/releases.html - ext.kotlin_version = '1.4.31' + ext.kotlin_version = '1.4.32' ext.kotlin_coroutines_version = "1.4.2" repositories { google() @@ -12,7 +12,7 @@ buildscript { } } dependencies { - classpath 'com.android.tools.build:gradle:4.1.2' + classpath 'com.android.tools.build:gradle:4.1.3' classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" // NOTE: Do not place your application dependencies here; they belong diff --git a/matrix-sdk-android/build.gradle b/matrix-sdk-android/build.gradle index 7053e7f1baa7e65eccd1fa323a44cd6ce63407f7..883f83783da485766e2ff3d10ce567a9e1b75ff6 100644 --- a/matrix-sdk-android/build.gradle +++ b/matrix-sdk-android/build.gradle @@ -6,10 +6,10 @@ apply plugin: 'realm-android' buildscript { repositories { - jcenter() + mavenCentral() } dependencies { - classpath "io.realm:realm-gradle-plugin:10.3.1" + classpath "io.realm:realm-gradle-plugin:10.4.0" } } @@ -21,7 +21,7 @@ android { minSdkVersion 21 targetSdkVersion 30 versionCode 1 - versionName "1.1.1" + versionName "1.1.4" // Multidex is useful for tests multiDexEnabled true testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" @@ -108,7 +108,7 @@ static def gitRevisionDate() { dependencies { def arrow_version = "0.8.2" - def moshi_version = '1.11.0' + def moshi_version = '1.12.0' def lifecycle_version = '2.2.0' def arch_version = '2.1.0' def markwon_version = '3.1.0' @@ -163,16 +163,16 @@ dependencies { // Logging implementation 'com.jakewharton.timber:timber:4.7.1' - implementation 'com.facebook.stetho:stetho-okhttp3:1.5.1' + implementation 'com.facebook.stetho:stetho-okhttp3:1.6.0' // Phone number https://github.com/google/libphonenumber - implementation 'com.googlecode.libphonenumber:libphonenumber:8.12.19' + implementation 'com.googlecode.libphonenumber:libphonenumber:8.12.21' testImplementation 'junit:junit:4.13.2' testImplementation 'org.robolectric:robolectric:4.5.1' //testImplementation 'org.robolectric:shadows-support-v4:3.0' // Note: version sticks to 1.9.2 due to https://github.com/mockk/mockk/issues/281 - testImplementation 'io.mockk:mockk:1.10.6' + testImplementation 'io.mockk:mockk:1.11.0' testImplementation 'org.amshove.kluent:kluent-android:1.65' testImplementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$kotlin_coroutines_version" // Plant Timber tree for test @@ -185,8 +185,7 @@ dependencies { androidTestImplementation 'androidx.test.ext:junit:1.1.2' androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0' androidTestImplementation 'org.amshove.kluent:kluent-android:1.61' - // Note: version sticks to 1.9.2 due to https://github.com/mockk/mockk/issues/281 - androidTestImplementation 'io.mockk:mockk-android:1.10.6' + androidTestImplementation 'io.mockk:mockk-android:1.11.0' androidTestImplementation "androidx.arch.core:core-testing:$arch_version" androidTestImplementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$kotlin_coroutines_version" // Plant Timber tree for test diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/InstrumentedTest.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/InstrumentedTest.kt index b78488436337da26256cff8c93ffd711bd8e44de..583406346eb519353ab57ed93d729e252b18bb82 100644 --- a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/InstrumentedTest.kt +++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/InstrumentedTest.kt @@ -20,7 +20,6 @@ import android.content.Context import androidx.test.core.app.ApplicationProvider import org.matrix.android.sdk.test.shared.createTimberTestRule import org.junit.Rule -import java.io.File interface InstrumentedTest { @@ -30,8 +29,4 @@ interface InstrumentedTest { fun context(): Context { return ApplicationProvider.getApplicationContext() } - - fun cacheDir(): File { - return context().cacheDir - } } diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/api/Matrix.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/api/Matrix.kt index 03943cea1448db7ec867c3a7a8c5426bea7371a8..c439da8407bcde21a93c78b2d78ff4272c2a234c 100644 --- a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/api/Matrix.kt +++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/api/Matrix.kt @@ -27,9 +27,12 @@ import org.matrix.android.sdk.BuildConfig import org.matrix.android.sdk.api.auth.AuthenticationService import org.matrix.android.sdk.api.auth.HomeServerHistoryService import org.matrix.android.sdk.api.legacy.LegacySessionImporter +import org.matrix.android.sdk.api.network.ApiInterceptorListener +import org.matrix.android.sdk.api.network.ApiPath import org.matrix.android.sdk.api.raw.RawService import org.matrix.android.sdk.common.DaggerTestMatrixComponent import org.matrix.android.sdk.internal.SessionManager +import org.matrix.android.sdk.internal.network.ApiInterceptor import org.matrix.android.sdk.internal.network.UserAgentHolder import org.matrix.android.sdk.internal.util.BackgroundDetectionObserver import org.matrix.olm.OlmManager @@ -51,6 +54,7 @@ class Matrix private constructor(context: Context, matrixConfiguration: MatrixCo @Inject internal lateinit var olmManager: OlmManager @Inject internal lateinit var sessionManager: SessionManager @Inject internal lateinit var homeServerHistoryService: HomeServerHistoryService + @Inject internal lateinit var apiInterceptor: ApiInterceptor private val uiHandler = Handler(Looper.getMainLooper()) @@ -79,6 +83,14 @@ class Matrix private constructor(context: Context, matrixConfiguration: MatrixCo return legacySessionImporter } + fun registerApiInterceptorListener(path: ApiPath, listener: ApiInterceptorListener) { + apiInterceptor.addListener(path, listener) + } + + fun unregisterApiInterceptorListener(path: ApiPath, listener: ApiInterceptorListener) { + apiInterceptor.removeListener(path, listener) + } + companion object { private lateinit var instance: Matrix diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/api/network/ApiInterceptorTest.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/api/network/ApiInterceptorTest.kt new file mode 100644 index 0000000000000000000000000000000000000000..9371154aaf0aee016ab47dfccadc11e126aab846 --- /dev/null +++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/api/network/ApiInterceptorTest.kt @@ -0,0 +1,58 @@ +/* + * Copyright (c) 2021 The Matrix.org Foundation C.I.C. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.matrix.android.sdk.api.network + +import org.amshove.kluent.shouldBeEqualTo +import org.junit.FixMethodOrder +import org.junit.Test +import org.junit.runner.RunWith +import org.junit.runners.JUnit4 +import org.junit.runners.MethodSorters +import org.matrix.android.sdk.InstrumentedTest +import org.matrix.android.sdk.common.CommonTestHelper +import org.matrix.android.sdk.common.SessionTestParams +import org.matrix.android.sdk.common.TestConstants +import timber.log.Timber + +@RunWith(JUnit4::class) +@FixMethodOrder(MethodSorters.JVM) +class ApiInterceptorTest : InstrumentedTest { + + private val commonTestHelper = CommonTestHelper(context()) + + @Test + fun apiInterceptorTest() { + val responses = mutableListOf<String>() + + val listener = object : ApiInterceptorListener { + override fun onApiResponse(path: ApiPath, response: String) { + Timber.w("onApiResponse($path): $response") + responses.add(response) + } + } + + commonTestHelper.matrix.registerApiInterceptorListener(ApiPath.REGISTER, listener) + + val session = commonTestHelper.createAccount(TestConstants.USER_ALICE, SessionTestParams(withInitialSync = true)) + + commonTestHelper.signOutAndClose(session) + + commonTestHelper.matrix.unregisterApiInterceptorListener(ApiPath.REGISTER, listener) + + responses.size shouldBeEqualTo 2 + } +} diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/common/CryptoTestHelper.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/common/CryptoTestHelper.kt index eb7e4a9fbec2c3a0f68b242b6f98f2a7cb01f71f..5815b23c0642867848b77f76efd6ca65e1f54e81 100644 --- a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/common/CryptoTestHelper.kt +++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/common/CryptoTestHelper.kt @@ -112,8 +112,8 @@ class CryptoTestHelper(private val mTestHelper: CommonTestHelper) { bobRoomSummariesLive.observeForever(newRoomObserver) } - mTestHelper.doSync<Unit> { - aliceRoom.invite(bobSession.myUserId, callback = it) + mTestHelper.runBlockingTest { + aliceRoom.invite(bobSession.myUserId) } mTestHelper.await(lock1) @@ -172,8 +172,8 @@ class CryptoTestHelper(private val mTestHelper: CommonTestHelper) { fun createSamAccountAndInviteToTheRoom(room: Room): Session { val samSession = mTestHelper.createAccount(TestConstants.USER_SAM, defaultSessionParams) - mTestHelper.doSync<Unit> { - room.invite(samSession.myUserId, null, it) + mTestHelper.runBlockingTest { + room.invite(samSession.myUserId, null) } mTestHelper.doSync<Unit> { @@ -337,8 +337,7 @@ class CryptoTestHelper(private val mTestHelper: CommonTestHelper) { requestID, roomId, bob.myUserId, - bob.sessionParams.credentials.deviceId!!, - null) + bob.sessionParams.credentials.deviceId!!) // we should reach SHOW SAS on both var alicePovTx: OutgoingSasVerificationTransaction? = null @@ -411,7 +410,7 @@ class CryptoTestHelper(private val mTestHelper: CommonTestHelper) { val sessions = mutableListOf(aliceSession) for (index in 1 until numberOfMembers) { val session = mTestHelper.createAccount("User_$index", defaultSessionParams) - mTestHelper.doSync<Unit>(timeout = 600_000) { room.invite(session.myUserId, null, it) } + mTestHelper.runBlockingTest(timeout = 600_000) { room.invite(session.myUserId, null) } println("TEST -> " + session.myUserId + " invited") mTestHelper.doSync<Unit> { session.joinRoom(room.roomId, null, emptyList(), it) } println("TEST -> " + session.myUserId + " joined") diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/common/TestRoomDisplayNameFallbackProvider.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/common/TestRoomDisplayNameFallbackProvider.kt index 7a1d4604f05e4bdfc6006137b93ff3e7af79d5bf..af2d57f9ce2e6d136a3e6470f1d92e2523ec4eeb 100644 --- a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/common/TestRoomDisplayNameFallbackProvider.kt +++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/common/TestRoomDisplayNameFallbackProvider.kt @@ -18,23 +18,26 @@ package org.matrix.android.sdk.common import org.matrix.android.sdk.api.RoomDisplayNameFallbackProvider -class TestRoomDisplayNameFallbackProvider() : RoomDisplayNameFallbackProvider { +class TestRoomDisplayNameFallbackProvider : RoomDisplayNameFallbackProvider { override fun getNameForRoomInvite() = "Room invite" - override fun getNameForEmptyRoom() = + override fun getNameForEmptyRoom(isDirect: Boolean, leftMemberNames: List<String>) = "Empty room" - override fun getNameFor2members(name1: String?, name2: String?) = + override fun getNameFor1member(name: String) = + name + + override fun getNameFor2members(name1: String, name2: String) = "$name1 and $name2" - override fun getNameFor3members(name1: String?, name2: String?, name3: String?) = + override fun getNameFor3members(name1: String, name2: String, name3: String) = "$name1, $name2 and $name3" - override fun getNameFor4members(name1: String?, name2: String?, name3: String?, name4: String?) = + override fun getNameFor4members(name1: String, name2: String, name3: String, name4: String) = "$name1, $name2, $name3 and $name4" - override fun getNameFor4membersAndMore(name1: String?, name2: String?, name3: String?, remainingCount: Int) = + override fun getNameFor4membersAndMore(name1: String, name2: String, name3: String, remainingCount: Int) = "$name1, $name2, $name3 and $remainingCount others" } 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 8c3917adc131e32027c75be629c3790de2c9582f..e6b364f3fb3d9cf0017963f5ec7b3344c9253114 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 @@ -367,8 +367,8 @@ class KeyShareTests : InstrumentedTest { } // Let alice invite bob - mTestHelper.doSync<Unit> { - roomAlicePov.invite(bobSession.myUserId, null, it) + mTestHelper.runBlockingTest { + roomAlicePov.invite(bobSession.myUserId, null) } mTestHelper.doSync<Unit> { 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 0489ee179f52c6fe48d3ae1095c20b451e42d355..eb4773f3c86f1b13ef45e920d64949d8d66af30d 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 @@ -19,7 +19,6 @@ package org.matrix.android.sdk.internal.crypto.ssss import androidx.lifecycle.Observer import androidx.test.ext.junit.runners.AndroidJUnit4 import org.matrix.android.sdk.InstrumentedTest -import org.matrix.android.sdk.api.MatrixCallback import org.matrix.android.sdk.api.session.Session import org.matrix.android.sdk.api.session.securestorage.EncryptedSecretContent import org.matrix.android.sdk.api.session.securestorage.KeySigner @@ -31,7 +30,6 @@ import org.matrix.android.sdk.api.util.Optional import org.matrix.android.sdk.common.CommonTestHelper import org.matrix.android.sdk.common.SessionTestParams import org.matrix.android.sdk.common.TestConstants -import org.matrix.android.sdk.common.TestMatrixCallback import org.matrix.android.sdk.internal.crypto.SSSS_ALGORITHM_AES_HMAC_SHA2 import org.matrix.android.sdk.internal.crypto.crosssigning.toBase64NoPadding import org.matrix.android.sdk.internal.crypto.secrets.DefaultSharedSecretStorageService @@ -40,7 +38,6 @@ import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.launch import kotlinx.coroutines.runBlocking -import org.amshove.kluent.shouldBe import org.junit.Assert.assertEquals import org.junit.Assert.assertNotNull import org.junit.Assert.assertNull @@ -70,8 +67,8 @@ class QuadSTests : InstrumentedTest { val TEST_KEY_ID = "my.test.Key" - mTestHelper.doSync<SsssKeyCreationInfo> { - quadS.generateKey(TEST_KEY_ID, null, "Test Key", emptyKeySigner, it) + mTestHelper.runBlockingTest { + quadS.generateKey(TEST_KEY_ID, null, "Test Key", emptyKeySigner) } // Assert Account data is updated @@ -99,7 +96,9 @@ class QuadSTests : InstrumentedTest { assertNull("Key was not generated from passphrase", parsed.passphrase) // Set as default key - quadS.setDefaultKey(TEST_KEY_ID, object : MatrixCallback<Unit> {}) + GlobalScope.launch { + quadS.setDefaultKey(TEST_KEY_ID) + } var defaultKeyAccountData: UserAccountDataEvent? = null val defaultDataLock = CountDownLatch(1) @@ -133,12 +132,11 @@ class QuadSTests : InstrumentedTest { // Store a secret val clearSecret = "42".toByteArray().toBase64NoPadding() - mTestHelper.doSync<Unit> { + mTestHelper.runBlockingTest { aliceSession.sharedSecretStorageService.storeSecret( "secret.of.life", clearSecret, - listOf(SharedSecretStorageService.KeyRef(null, keySpec)), // default key - it + listOf(SharedSecretStorageService.KeyRef(null, keySpec)) // default key ) } @@ -155,12 +153,11 @@ class QuadSTests : InstrumentedTest { // Try to decrypt?? - val decryptedSecret = mTestHelper.doSync<String> { + val decryptedSecret = mTestHelper.runBlockingTest { aliceSession.sharedSecretStorageService.getSecret( "secret.of.life", null, // default key - keySpec!!, - it + keySpec!! ) } @@ -176,13 +173,13 @@ class QuadSTests : InstrumentedTest { val TEST_KEY_ID = "my.test.Key" - mTestHelper.doSync<SsssKeyCreationInfo> { - quadS.generateKey(TEST_KEY_ID, null, "Test Key", emptyKeySigner, it) + mTestHelper.runBlockingTest { + quadS.generateKey(TEST_KEY_ID, null, "Test Key", emptyKeySigner) } // Test that we don't need to wait for an account data sync to access directly the keyid from DB - mTestHelper.doSync<Unit> { - quadS.setDefaultKey(TEST_KEY_ID, it) + mTestHelper.runBlockingTest { + quadS.setDefaultKey(TEST_KEY_ID) } mTestHelper.signOutAndClose(aliceSession) @@ -198,15 +195,14 @@ class QuadSTests : InstrumentedTest { val mySecretText = "Lorem ipsum dolor sit amet, consectetur adipiscing elit" - mTestHelper.doSync<Unit> { + mTestHelper.runBlockingTest { aliceSession.sharedSecretStorageService.storeSecret( "my.secret", mySecretText.toByteArray().toBase64NoPadding(), listOf( SharedSecretStorageService.KeyRef(keyId1, RawBytesKeySpec.fromRecoveryKey(key1Info.recoveryKey)), SharedSecretStorageService.KeyRef(keyId2, RawBytesKeySpec.fromRecoveryKey(key2Info.recoveryKey)) - ), - it + ) ) } @@ -219,19 +215,17 @@ class QuadSTests : InstrumentedTest { assertNotNull(encryptedContent?.get(keyId2)) // Assert that can decrypt with both keys - mTestHelper.doSync<String> { + mTestHelper.runBlockingTest { aliceSession.sharedSecretStorageService.getSecret("my.secret", keyId1, - RawBytesKeySpec.fromRecoveryKey(key1Info.recoveryKey)!!, - it + RawBytesKeySpec.fromRecoveryKey(key1Info.recoveryKey)!! ) } - mTestHelper.doSync<String> { + mTestHelper.runBlockingTest { aliceSession.sharedSecretStorageService.getSecret("my.secret", keyId2, - RawBytesKeySpec.fromRecoveryKey(key2Info.recoveryKey)!!, - it + RawBytesKeySpec.fromRecoveryKey(key2Info.recoveryKey)!! ) } @@ -247,50 +241,34 @@ class QuadSTests : InstrumentedTest { val mySecretText = "Lorem ipsum dolor sit amet, consectetur adipiscing elit" - mTestHelper.doSync<Unit> { + mTestHelper.runBlockingTest { aliceSession.sharedSecretStorageService.storeSecret( "my.secret", mySecretText.toByteArray().toBase64NoPadding(), - listOf(SharedSecretStorageService.KeyRef(keyId1, RawBytesKeySpec.fromRecoveryKey(key1Info.recoveryKey))), - it + listOf(SharedSecretStorageService.KeyRef(keyId1, RawBytesKeySpec.fromRecoveryKey(key1Info.recoveryKey))) ) } - val decryptCountDownLatch = CountDownLatch(1) - var error = false - aliceSession.sharedSecretStorageService.getSecret("my.secret", - keyId1, - RawBytesKeySpec.fromPassphrase( - "A bad passphrase", - key1Info.content?.passphrase?.salt ?: "", - key1Info.content?.passphrase?.iterations ?: 0, - null), - object : MatrixCallback<String> { - override fun onSuccess(data: String) { - decryptCountDownLatch.countDown() - } - - override fun onFailure(failure: Throwable) { - error = true - decryptCountDownLatch.countDown() - } - } - ) - - mTestHelper.await(decryptCountDownLatch) - - error shouldBe true + mTestHelper.runBlockingTest { + aliceSession.sharedSecretStorageService.getSecret("my.secret", + keyId1, + RawBytesKeySpec.fromPassphrase( + "A bad passphrase", + key1Info.content?.passphrase?.salt ?: "", + key1Info.content?.passphrase?.iterations ?: 0, + null) + ) + } // Now try with correct key - mTestHelper.doSync<String> { + mTestHelper.runBlockingTest { aliceSession.sharedSecretStorageService.getSecret("my.secret", keyId1, RawBytesKeySpec.fromPassphrase( passphrase, key1Info.content?.passphrase?.salt ?: "", key1Info.content?.passphrase?.iterations ?: 0, - null), - it + null) ) } @@ -321,15 +299,15 @@ class QuadSTests : InstrumentedTest { private fun generatedSecret(session: Session, keyId: String, asDefault: Boolean = true): SsssKeyCreationInfo { val quadS = session.sharedSecretStorageService - val creationInfo = mTestHelper.doSync<SsssKeyCreationInfo> { - quadS.generateKey(keyId, null, keyId, emptyKeySigner, it) + val creationInfo = mTestHelper.runBlockingTest { + quadS.generateKey(keyId, null, keyId, emptyKeySigner) } assertAccountData(session, "${DefaultSharedSecretStorageService.KEY_ID_BASE}.$keyId") if (asDefault) { - mTestHelper.doSync<Unit> { - quadS.setDefaultKey(keyId, it) + mTestHelper.runBlockingTest { + quadS.setDefaultKey(keyId) } assertAccountData(session, DefaultSharedSecretStorageService.DEFAULT_KEY_ID) } @@ -340,21 +318,20 @@ class QuadSTests : InstrumentedTest { private fun generatedSecretFromPassphrase(session: Session, passphrase: String, keyId: String, asDefault: Boolean = true): SsssKeyCreationInfo { val quadS = session.sharedSecretStorageService - val creationInfo = mTestHelper.doSync<SsssKeyCreationInfo> { + val creationInfo = mTestHelper.runBlockingTest { quadS.generateKeyWithPassphrase( keyId, keyId, passphrase, emptyKeySigner, - null, - it) + null) } assertAccountData(session, "${DefaultSharedSecretStorageService.KEY_ID_BASE}.$keyId") if (asDefault) { - val setDefaultLatch = CountDownLatch(1) - quadS.setDefaultKey(keyId, TestMatrixCallback(setDefaultLatch)) - mTestHelper.await(setDefaultLatch) + mTestHelper.runBlockingTest { + quadS.setDefaultKey(keyId) + } assertAccountData(session, DefaultSharedSecretStorageService.DEFAULT_KEY_ID) } 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 a81f503e774e38c5467b7ae4f0e4662c616525d9..4ea8cdc074449fb7bba792feda5b8d8f13a1c034 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 @@ -593,16 +593,14 @@ class SASTest : InstrumentedTest { requestID!!, cryptoTestData.roomId, bobSession.myUserId, - bobSession.sessionParams.deviceId!!, - null) + bobSession.sessionParams.deviceId!!) bobVerificationService.beginKeyVerificationInDMs( VerificationMethod.SAS, requestID!!, cryptoTestData.roomId, aliceSession.myUserId, - aliceSession.sessionParams.deviceId!!, - null) + aliceSession.sessionParams.deviceId!!) // we should reach SHOW SAS on both var alicePovTx: SasVerificationTransaction? 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 cadb83ca00bc05fbf53ebc0b389e4e8b59eca4af..1baf490dfc70852ebe41085f4697799e8e542795 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 @@ -17,149 +17,104 @@ package org.matrix.android.sdk.session.search import org.junit.Assert.assertTrue -import org.junit.Assert.fail import org.junit.FixMethodOrder import org.junit.Test import org.junit.runner.RunWith import org.junit.runners.JUnit4 import org.junit.runners.MethodSorters import org.matrix.android.sdk.InstrumentedTest -import org.matrix.android.sdk.api.MatrixCallback import org.matrix.android.sdk.api.extensions.orFalse import org.matrix.android.sdk.api.session.events.model.toModel import org.matrix.android.sdk.api.session.room.model.message.MessageContent import org.matrix.android.sdk.api.session.room.timeline.TimelineSettings import org.matrix.android.sdk.api.session.search.SearchResult import org.matrix.android.sdk.common.CommonTestHelper +import org.matrix.android.sdk.common.CryptoTestData import org.matrix.android.sdk.common.CryptoTestHelper -import org.matrix.android.sdk.common.TestConstants import java.util.concurrent.CountDownLatch -import java.util.concurrent.TimeUnit @RunWith(JUnit4::class) @FixMethodOrder(MethodSorters.JVM) class SearchMessagesTest : InstrumentedTest { - private val MESSAGE = "Lorem ipsum dolor sit amet" + companion object { + private const val MESSAGE = "Lorem ipsum dolor sit amet" + } private val commonTestHelper = CommonTestHelper(context()) private val cryptoTestHelper = CryptoTestHelper(commonTestHelper) @Test fun sendTextMessageAndSearchPartOfItUsingSession() { - val cryptoTestData = cryptoTestHelper.doE2ETestWithAliceAndBobInARoom(false) - val aliceSession = cryptoTestData.firstSession - val aliceRoomId = cryptoTestData.roomId - aliceSession.cryptoService().setWarnOnUnknownDevices(false) - val roomFromAlicePOV = aliceSession.getRoom(aliceRoomId)!! - val aliceTimeline = roomFromAlicePOV.createTimeline(null, TimelineSettings(10)) - aliceTimeline.start() - - commonTestHelper.sendTextMessage( - roomFromAlicePOV, - MESSAGE, - 2) - - run { - val lock = CountDownLatch(1) - - val eventListener = commonTestHelper.createEventListener(lock) { snapshot -> - snapshot.count { it.root.content.toModel<MessageContent>()?.body?.startsWith(MESSAGE).orFalse() } == 2 - } - - aliceTimeline.addListener(eventListener) - commonTestHelper.await(lock) - - val data = commonTestHelper.runBlockingTest { - aliceSession - .searchService() - .search( - searchTerm = "lore", - limit = 10, - includeProfile = true, - afterLimit = 0, - beforeLimit = 10, - orderByRecent = true, - nextBatch = null, - roomId = aliceRoomId - ) - } - assertTrue(data.results?.size == 2) - assertTrue( - data.results - ?.all { - (it.event.content?.get("body") as? String)?.startsWith(MESSAGE).orFalse() - }.orFalse() - ) - - aliceTimeline.removeAllListeners() - cryptoTestData.cleanUp(commonTestHelper) + doTest { cryptoTestData -> + cryptoTestData.firstSession + .searchService() + .search( + searchTerm = "lore", + limit = 10, + includeProfile = true, + afterLimit = 0, + beforeLimit = 10, + orderByRecent = true, + nextBatch = null, + roomId = cryptoTestData.roomId + ) } - - aliceSession.startSync(true) } @Test fun sendTextMessageAndSearchPartOfItUsingRoom() { - val cryptoTestData = cryptoTestHelper.doE2ETestWithAliceAndBobInARoom(false) + doTest { cryptoTestData -> + cryptoTestData.firstSession + .getRoom(cryptoTestData.roomId)!! + .search( + searchTerm = "lore", + limit = 10, + includeProfile = true, + afterLimit = 0, + beforeLimit = 10, + orderByRecent = true, + nextBatch = null + ) + } + } + + private fun doTest(block: suspend (CryptoTestData) -> SearchResult) { + val cryptoTestData = cryptoTestHelper.doE2ETestWithAliceInARoom(false) val aliceSession = cryptoTestData.firstSession val aliceRoomId = cryptoTestData.roomId - aliceSession.cryptoService().setWarnOnUnknownDevices(false) val roomFromAlicePOV = aliceSession.getRoom(aliceRoomId)!! val aliceTimeline = roomFromAlicePOV.createTimeline(null, TimelineSettings(10)) aliceTimeline.start() + val lock = CountDownLatch(1) + + val eventListener = commonTestHelper.createEventListener(lock) { snapshot -> + snapshot.count { it.root.content.toModel<MessageContent>()?.body?.startsWith(MESSAGE).orFalse() } == 2 + } + + aliceTimeline.addListener(eventListener) + commonTestHelper.sendTextMessage( roomFromAlicePOV, MESSAGE, 2) - run { - var lock = CountDownLatch(1) - - val eventListener = commonTestHelper.createEventListener(lock) { snapshot -> - snapshot.count { it.root.content.toModel<MessageContent>()?.body?.startsWith(MESSAGE).orFalse() } == 2 - } + commonTestHelper.await(lock) - aliceTimeline.addListener(eventListener) - commonTestHelper.await(lock) - - lock = CountDownLatch(1) - roomFromAlicePOV - .search( - searchTerm = "lore", - limit = 10, - includeProfile = true, - afterLimit = 0, - beforeLimit = 10, - orderByRecent = true, - nextBatch = null, - callback = object : MatrixCallback<SearchResult> { - override fun onSuccess(data: SearchResult) { - super.onSuccess(data) - assertTrue(data.results?.size == 2) - assertTrue( - data.results - ?.all { - (it.event.content?.get("body") as? String)?.startsWith(MESSAGE).orFalse() - }.orFalse() - ) - lock.countDown() - } - - override fun onFailure(failure: Throwable) { - super.onFailure(failure) - fail(failure.localizedMessage) - lock.countDown() - } - } - ) - lock.await(TestConstants.timeOutMillis, TimeUnit.MILLISECONDS) - - aliceTimeline.removeAllListeners() - cryptoTestData.cleanUp(commonTestHelper) + val data = commonTestHelper.runBlockingTest { + block.invoke(cryptoTestData) } - aliceSession.startSync(true) + assertTrue(data.results?.size == 2) + assertTrue( + data.results + ?.all { + (it.event.content?.get("body") as? String)?.startsWith(MESSAGE).orFalse() + }.orFalse() + ) + + aliceTimeline.removeAllListeners() + cryptoTestData.cleanUp(commonTestHelper) } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/Matrix.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/Matrix.kt index a5d457222f6d9371824d08253f9d27748f77557a..99802592667e956951a946cae612d5bb2e28d662 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/Matrix.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/Matrix.kt @@ -25,9 +25,12 @@ import org.matrix.android.sdk.BuildConfig import org.matrix.android.sdk.api.auth.AuthenticationService import org.matrix.android.sdk.api.auth.HomeServerHistoryService import org.matrix.android.sdk.api.legacy.LegacySessionImporter +import org.matrix.android.sdk.api.network.ApiInterceptorListener +import org.matrix.android.sdk.api.network.ApiPath import org.matrix.android.sdk.api.raw.RawService import org.matrix.android.sdk.internal.SessionManager import org.matrix.android.sdk.internal.di.DaggerMatrixComponent +import org.matrix.android.sdk.internal.network.ApiInterceptor import org.matrix.android.sdk.internal.network.UserAgentHolder import org.matrix.android.sdk.internal.util.BackgroundDetectionObserver import org.matrix.olm.OlmManager @@ -49,6 +52,7 @@ class Matrix private constructor(context: Context, matrixConfiguration: MatrixCo @Inject internal lateinit var olmManager: OlmManager @Inject internal lateinit var sessionManager: SessionManager @Inject internal lateinit var homeServerHistoryService: HomeServerHistoryService + @Inject internal lateinit var apiInterceptor: ApiInterceptor init { Monarchy.init(context) @@ -73,6 +77,14 @@ class Matrix private constructor(context: Context, matrixConfiguration: MatrixCo return legacySessionImporter } + fun registerApiInterceptorListener(path: ApiPath, listener: ApiInterceptorListener) { + apiInterceptor.addListener(path, listener) + } + + fun unregisterApiInterceptorListener(path: ApiPath, listener: ApiInterceptorListener) { + apiInterceptor.removeListener(path, listener) + } + companion object { private lateinit var instance: Matrix diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/RoomDisplayNameFallbackProvider.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/RoomDisplayNameFallbackProvider.kt index 4ac14d5f63afb92d1d552c108db7f25317df7b51..a34dbcc1965a08741ed6d66bf0d2b8e61ca732f8 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/RoomDisplayNameFallbackProvider.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/RoomDisplayNameFallbackProvider.kt @@ -18,9 +18,10 @@ package org.matrix.android.sdk.api interface RoomDisplayNameFallbackProvider { fun getNameForRoomInvite(): String - fun getNameForEmptyRoom(): String - fun getNameFor2members(name1: String?, name2: String?): String - fun getNameFor3members(name1: String?, name2: String?, name3: String?): String - fun getNameFor4members(name1: String?, name2: String?, name3: String?, name4: String?): String - fun getNameFor4membersAndMore(name1: String?, name2: String?, name3: String?, remainingCount: Int): String + fun getNameForEmptyRoom(isDirect: Boolean, leftMemberNames: List<String>): String + fun getNameFor1member(name: String): String + fun getNameFor2members(name1: String, name2: String): String + fun getNameFor3members(name1: String, name2: String, name3: String): String + fun getNameFor4members(name1: String, name2: String, name3: String, name4: String): String + fun getNameFor4membersAndMore(name1: String, name2: String, name3: String, remainingCount: Int): String } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/util/MatrixCallbackDelegate.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/registration/RegistrationAvailability.kt similarity index 62% rename from matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/util/MatrixCallbackDelegate.kt rename to matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/registration/RegistrationAvailability.kt index 63d37f409f12c4e9ca13edebaecc03dadce127f6..f9a7ace7ba1218b8eab27bb326db1d0b21e0569c 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/util/MatrixCallbackDelegate.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/registration/RegistrationAvailability.kt @@ -1,5 +1,5 @@ /* - * Copyright 2020 The Matrix.org Foundation C.I.C. + * Copyright 2021 The Matrix.org Foundation C.I.C. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,11 +14,11 @@ * limitations under the License. */ -package org.matrix.android.sdk.api.util +package org.matrix.android.sdk.api.auth.registration -import org.matrix.android.sdk.api.MatrixCallback +import org.matrix.android.sdk.api.failure.Failure -/** - * Simple MatrixCallback implementation which delegate its calls to another callback - */ -open class MatrixCallbackDelegate<T>(private val callback: MatrixCallback<T>) : MatrixCallback<T> by callback +sealed class RegistrationAvailability { + object Available : RegistrationAvailability() + data class NotAvailable(val failure: Failure.ServerError) : RegistrationAvailability() +} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/registration/RegistrationWizard.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/registration/RegistrationWizard.kt index d00c9a0c8211c01b1dafbbdbf47d97db8f731681..38a5a77291c1e8688b0cd6981ea54cda8350915f 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/registration/RegistrationWizard.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/registration/RegistrationWizard.kt @@ -36,6 +36,8 @@ interface RegistrationWizard { suspend fun checkIfEmailHasBeenValidated(delayMillis: Long): RegistrationResult + suspend fun registrationAvailable(userName: String): RegistrationAvailability + val currentThreePid: String? // True when login and password has been sent with success to the homeserver 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 e0ee9f36ba8f9b9387afa7dd523e2609abf8e16e..0ba61e5890ea38fecab67e156b684206e32e8769 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 @@ -37,6 +37,18 @@ fun Throwable.shouldBeRetried(): Boolean { || (this is Failure.ServerError && error.code == MatrixError.M_LIMIT_EXCEEDED) } +/** + * Get the retry delay in case of rate limit exceeded error, adding 100 ms, of defaultValue otherwise + */ +fun Throwable.getRetryDelay(defaultValue: Long): Long { + return (this as? Failure.ServerError) + ?.error + ?.takeIf { it.code == MatrixError.M_LIMIT_EXCEEDED } + ?.retryAfterMillis + ?.plus(100L) + ?: defaultValue +} + fun Throwable.isInvalidPassword(): Boolean { return this is Failure.ServerError && error.code == MatrixError.M_FORBIDDEN @@ -53,13 +65,16 @@ fun Throwable.isInvalidUIAAuth(): Boolean { * Try to convert to a RegistrationFlowResponse. Return null in the cases it's not possible */ fun Throwable.toRegistrationFlowResponse(): RegistrationFlowResponse? { - return if (this is Failure.OtherServerError && httpCode == 401) { + return if (this is Failure.OtherServerError + && httpCode == HttpsURLConnection.HTTP_UNAUTHORIZED /* 401 */) { tryOrNull { MoshiProvider.providesMoshi() .adapter(RegistrationFlowResponse::class.java) .fromJson(errorBody) } - } else if (this is Failure.ServerError && httpCode == 401 && error.code == MatrixError.M_FORBIDDEN) { + } else if (this is Failure.ServerError + && httpCode == HttpsURLConnection.HTTP_UNAUTHORIZED /* 401 */ + && error.code == MatrixError.M_FORBIDDEN) { // This happens when the submission for this stage was bad (like bad password) if (error.session != null && error.flows != null) { RegistrationFlowResponse( @@ -75,3 +90,11 @@ fun Throwable.toRegistrationFlowResponse(): RegistrationFlowResponse? { null } } + +fun Throwable.isRegistrationAvailabilityError(): Boolean { + return this is Failure.ServerError + && httpCode == HttpsURLConnection.HTTP_BAD_REQUEST /* 400 */ + && (error.code == MatrixError.M_USER_IN_USE + || error.code == MatrixError.M_INVALID_USERNAME + || error.code == MatrixError.M_EXCLUSIVE) +} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/network/ApiInterceptorListener.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/network/ApiInterceptorListener.kt new file mode 100644 index 0000000000000000000000000000000000000000..ad21da8fdff174400957e42ef343c1ea13d60d6a --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/network/ApiInterceptorListener.kt @@ -0,0 +1,21 @@ +/* + * Copyright (c) 2021 The Matrix.org Foundation C.I.C. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.matrix.android.sdk.api.network + +interface ApiInterceptorListener { + fun onApiResponse(path: ApiPath, response: String) +} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/network/ApiPath.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/network/ApiPath.kt new file mode 100644 index 0000000000000000000000000000000000000000..db112a30b26d5e7bbdc98b6592354c0d98419069 --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/network/ApiPath.kt @@ -0,0 +1,183 @@ +/* + * Copyright (c) 2021 The Matrix.org Foundation C.I.C. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.matrix.android.sdk.api.network + +import org.matrix.android.sdk.internal.network.NetworkConstants + +enum class ApiPath(val path: String, val method: String) { + // AuthApi + VERSIONS(NetworkConstants.URI_API_PREFIX_PATH_R0 + "versions", "GET"), + REGISTER(NetworkConstants.URI_API_PREFIX_PATH_R0 + "register", "POST"), + ADD_3PID(NetworkConstants.URI_API_PREFIX_PATH_R0 + "register/{threePid}/requestToken", "POST"), + LOGIN_FLOWS(NetworkConstants.URI_API_PREFIX_PATH_R0 + "login", "GET"), + LOGIN(NetworkConstants.URI_API_PREFIX_PATH_R0 + "login", "POST"), + RESET_PASSWORD(NetworkConstants.URI_API_PREFIX_PATH_R0 + "account/password/email/requestToken", "POST"), + RESET_PASSWORD_MAIL_CONFIRMED(NetworkConstants.URI_API_PREFIX_PATH_R0 + "account/password", "POST"), + + // DirectoryApi + ROOM_ID_BY_ALIAS(NetworkConstants.URI_API_PREFIX_PATH_R0 + "directory/room/{roomAlias}", "GET"), + ROOM_DIRECTORY_VISIBILITY(NetworkConstants.URI_API_PREFIX_PATH_R0 + "directory/list/room/{roomId}", "GET"), + SET_ROOM_DIRECTORY_VISIBILITY(NetworkConstants.URI_API_PREFIX_PATH_R0 + "directory/list/room/{roomId}", "PUT"), + ADD_ROOM_ALIAS(NetworkConstants.URI_API_PREFIX_PATH_R0 + "directory/room/{roomAlias}", "PUT"), + DELETE_ROOM_ALIAS(NetworkConstants.URI_API_PREFIX_PATH_R0 + "directory/room/{roomAlias}", "DELETE"), + + // CryptoApi + GET_DEVICES(NetworkConstants.URI_API_PREFIX_PATH_R0 + "devices", "GET"), + GET_DEVICE_INFO(NetworkConstants.URI_API_PREFIX_PATH_R0 + "devices/{deviceId}", "GET"), + UPLOAD_KEYS(NetworkConstants.URI_API_PREFIX_PATH_R0 + "keys/upload", "POST"), + DOWNLOAD_KEYS_FOR_USERS(NetworkConstants.URI_API_PREFIX_PATH_R0 + "keys/query", "POST"), + UPLOAD_SIGNING_KEYS(NetworkConstants.URI_API_PREFIX_PATH_UNSTABLE + "keys/device_signing/upload", "POST"), + UPLOAD_SIGNATURES(NetworkConstants.URI_API_PREFIX_PATH_UNSTABLE + "keys/signatures/upload", "POST"), + CLAIM_ONE_TIME_KEYS_FOR_USERS_DEVICES(NetworkConstants.URI_API_PREFIX_PATH_R0 + "keys/claim", "POST"), + SEND_TO_DEVICE(NetworkConstants.URI_API_PREFIX_PATH_R0 + "sendToDevice/{eventType}/{txnId}", "PUT"), + DELETE_DEVICE(NetworkConstants.URI_API_PREFIX_PATH_R0 + "devices/{device_id}", "DELETE"), + UPDATE_DEVICE_INFO(NetworkConstants.URI_API_PREFIX_PATH_R0 + "devices/{device_id}", "PUT"), + GET_KEY_CHANGES(NetworkConstants.URI_API_PREFIX_PATH_R0 + "keys/changes", "GET"), + + // RoomKeysApi + CREATE_KEYS_BACKUP_VERSION(NetworkConstants.URI_API_PREFIX_PATH_UNSTABLE + "room_keys/version", "POST"), + GET_KEYS_BACKUP_LAST_VERSION(NetworkConstants.URI_API_PREFIX_PATH_UNSTABLE + "room_keys/version", "GET"), + GET_KEYS_BACKUP_VERSION(NetworkConstants.URI_API_PREFIX_PATH_UNSTABLE + "room_keys/version/{version}", "GET"), + UPDATE_KEYS_BACKUP_VERSION(NetworkConstants.URI_API_PREFIX_PATH_UNSTABLE + "room_keys/version/{version}", "PUT"), + STORE_ROOM_SESSION_DATA(NetworkConstants.URI_API_PREFIX_PATH_UNSTABLE + "room_keys/keys/{roomId}/{sessionId}", "PUT"), + STORE_ROOM_SESSIONS_DATA(NetworkConstants.URI_API_PREFIX_PATH_UNSTABLE + "room_keys/keys/{roomId}", "PUT"), + STORE_SESSIONS_DATA(NetworkConstants.URI_API_PREFIX_PATH_UNSTABLE + "room_keys/keys", "PUT"), + GET_ROOM_SESSION_DATA(NetworkConstants.URI_API_PREFIX_PATH_UNSTABLE + "room_keys/keys/{roomId}/{sessionId}", "GET"), + GET_ROOM_SESSIONS_DATA(NetworkConstants.URI_API_PREFIX_PATH_UNSTABLE + "room_keys/keys/{roomId}", "GET"), + GET_SESSIONS_DATA(NetworkConstants.URI_API_PREFIX_PATH_UNSTABLE + "room_keys/keys", "GET"), + DELETE_ROOM_SESSION_DATA(NetworkConstants.URI_API_PREFIX_PATH_UNSTABLE + "room_keys/keys/{roomId}/{sessionId}", "DELETE"), + DELETE_ROOM_SESSIONS_DATA(NetworkConstants.URI_API_PREFIX_PATH_UNSTABLE + "room_keys/keys/{roomId}", "DELETE"), + DELETE_SESSIONS_DATA(NetworkConstants.URI_API_PREFIX_PATH_UNSTABLE + "room_keys/keys", "DELETE"), + DELETE_BACKUP(NetworkConstants.URI_API_PREFIX_PATH_UNSTABLE + "room_keys/version/{version}", "DELETE"), + + // AccountApi + CHANGE_PASSWORD(NetworkConstants.URI_API_PREFIX_PATH_R0 + "account/password", "POST"), + DEACTIVATE_ACCOUNT(NetworkConstants.URI_API_PREFIX_PATH_R0 + "account/deactivate", "POST"), + + // SearchApi + SEARCH(NetworkConstants.URI_API_PREFIX_PATH_R0 + "search", "POST"), + + // FederationApi + GET_FEDERATION_VERSION(NetworkConstants.URI_FEDERATION_PATH + "version", "GET"), + + // VoipApi + GET_TURN_SERVER(NetworkConstants.URI_API_PREFIX_PATH_R0 + "voip/turnServer", "GET"), + + // PushGatewayApi + NOTIFY_PUSH_GATEWAY(NetworkConstants.URI_PUSH_GATEWAY_PREFIX_PATH + "notify", "POST"), + + // GroupApi + GET_GROUP_SUMMARY(NetworkConstants.URI_API_PREFIX_PATH_R0 + "groups/{groupId}/summary", "GET"), + GET_GROUP_ROOMS(NetworkConstants.URI_API_PREFIX_PATH_R0 + "groups/{groupId}/rooms", "GET"), + GET_GROUP_USERS(NetworkConstants.URI_API_PREFIX_PATH_R0 + "groups/{groupId}/users", "GET"), + + // CapabilitiesApi + GET_CAPABILITIES(NetworkConstants.URI_API_PREFIX_PATH_R0 + "capabilities", "GET"), + GET_VERSIONS(NetworkConstants.URI_API_PREFIX_PATH_ + "versions", "GET"), + PING(NetworkConstants.URI_API_PREFIX_PATH_ + "versions", "GET"), + + // IdentityApi + GET_ACCOUNT(NetworkConstants.URI_IDENTITY_PATH_V2 + "account", "GET"), + LOGOUT(NetworkConstants.URI_IDENTITY_PATH_V2 + "account/logout", "POST"), + IDENTITY_HAS_DETAILS(NetworkConstants.URI_IDENTITY_PATH_V2 + "hash_details", "GET"), + LOOKUP(NetworkConstants.URI_IDENTITY_PATH_V2 + "lookup", "POST"), + REQUEST_TOKEN_TO_BIND_EMAIL(NetworkConstants.URI_IDENTITY_PATH_V2 + "validate/email/requestToken", "POST"), + REQUEST_TOKEN_TO_BIND_MSISDN(NetworkConstants.URI_IDENTITY_PATH_V2 + "validate/msisdn/requestToken", "POST"), + SUBMIT_TOKEN(NetworkConstants.URI_IDENTITY_PATH_V2 + "validate/{medium}/submitToken", "POST"), + + // FilterApi + UPLOAD_FILTER(NetworkConstants.URI_API_PREFIX_PATH_R0 + "user/{userId}/filter", "POST"), + GET_FILTER_BY_ID(NetworkConstants.URI_API_PREFIX_PATH_R0 + "user/{userId}/filter/{filterId}", "GET"), + + // IndentityAuthApi + IDENTITY_REGISTER(NetworkConstants.URI_IDENTITY_PATH_V2 + "account/register", "POST"), + + // MediaApi + GET_MEDIA_CONFIG(NetworkConstants.URI_API_MEDIA_PREFIX_PATH_R0 + "config", "GET"), + GET_PREVIEW_URL_DATA(NetworkConstants.URI_API_MEDIA_PREFIX_PATH_R0 + "preview_url", "GET"), + + // OpenIdApi + OPEN_ID_TOKEN(NetworkConstants.URI_API_PREFIX_PATH_R0 + "user/{userId}/openid/request_token", "POST"), + + // ProfileApi + GET_PROFILE(NetworkConstants.URI_API_PREFIX_PATH_R0 + "profile/{userId}", "GET"), + GET_THREE_PIDS(NetworkConstants.URI_API_PREFIX_PATH_R0 + "account/3pid", "GET"), + SET_DISPLAY_NAME(NetworkConstants.URI_API_PREFIX_PATH_R0 + "profile/{userId}/displayname", "PUT"), + SET_AVATAR_URL(NetworkConstants.URI_API_PREFIX_PATH_R0 + "profile/{userId}/avatar_url", "PUT"), + BIND_THREE_PID(NetworkConstants.URI_API_PREFIX_PATH_UNSTABLE + "account/3pid/bind", "POST"), + UNBIND_THREE_PID(NetworkConstants.URI_API_PREFIX_PATH_UNSTABLE + "account/3pid/unbind", "POST"), + ADD_EMAIL(NetworkConstants.URI_API_PREFIX_PATH_R0 + "account/3pid/email/requestToken", "POST"), + ADD_MSISDN(NetworkConstants.URI_API_PREFIX_PATH_R0 + "account/3pid/msisdn/requestToken", "POST"), + FINALIZE_ADD_THREE_PID(NetworkConstants.URI_API_PREFIX_PATH_R0 + "account/3pid/add", "POST"), + DELETE_THREE_PID(NetworkConstants.URI_API_PREFIX_PATH_R0 + "account/3pid/delete", "POST"), + + // PusherRulesApi + GET_ALL_PUSHER_RULES(NetworkConstants.URI_API_PREFIX_PATH_R0 + "pushrules/", "GET"), + UPDATE_ENABLE_PUSH_RULE_STATUS(NetworkConstants.URI_API_PREFIX_PATH_R0 + "pushrules/global/{kind}/{ruleId}/enabled", "PUT"), + UPDATE_PUSH_RULE_ACTIONS(NetworkConstants.URI_API_PREFIX_PATH_R0 + "pushrules/global/{kind}/{ruleId}/actions", "PUT"), + DELETE_PUSH_RULE(NetworkConstants.URI_API_PREFIX_PATH_R0 + "pushrules/global/{kind}/{ruleId}", "DELETE"), + ADD_PUSH_RULE(NetworkConstants.URI_API_PREFIX_PATH_R0 + "pushrules/global/{kind}/{ruleId}", "PUT"), + + // PusherApi + GET_PUSHERS(NetworkConstants.URI_API_PREFIX_PATH_R0 + "pushers", "GET"), + SET_PUSHER(NetworkConstants.URI_API_PREFIX_PATH_R0 + "pushers/set", "POST"), + + // SignOutApi + LOGIN_AGAIN(NetworkConstants.URI_API_PREFIX_PATH_R0 + "login", "POST"), + SIGN_OUT(NetworkConstants.URI_API_PREFIX_PATH_R0 + "logout", "POST"), + + // RoomApi + GET_PUBLIC_ROOMS(NetworkConstants.URI_API_PREFIX_PATH_R0 + "publicRooms", "POST"), + CREATE_ROOM(NetworkConstants.URI_API_PREFIX_PATH_R0 + "createRoom", "POST"), + GET_ROOM_MESSAGES_FROM(NetworkConstants.URI_API_PREFIX_PATH_R0 + "rooms/{roomId}/messages", "GET"), + GET_MEMBERS(NetworkConstants.URI_API_PREFIX_PATH_R0 + "rooms/{roomId}/members", "GET"), + SEND_EVENT(NetworkConstants.URI_API_PREFIX_PATH_R0 + "rooms/{roomId}/send/{eventType}/{txId}", "PUT"), + GET_CONTEXT_OF_EVENT(NetworkConstants.URI_API_PREFIX_PATH_R0 + "rooms/{roomId}/context/{eventId}", "GET"), + GET_EVENT(NetworkConstants.URI_API_PREFIX_PATH_R0 + "rooms/{roomId}/event/{eventId}", "GET"), + SEND_READ_MARKER(NetworkConstants.URI_API_PREFIX_PATH_R0 + "rooms/{roomId}/read_markers", "POST"), + INVITE(NetworkConstants.URI_API_PREFIX_PATH_R0 + "rooms/{roomId}/invite", "POST"), + INVITE_USING_THREE_PID(NetworkConstants.URI_API_PREFIX_PATH_R0 + "rooms/{roomId}/invite", "POST"), + SEND_STATE_EVENT(NetworkConstants.URI_API_PREFIX_PATH_R0 + "rooms/{roomId}/state/{state_event_type}", "PUT"), + SEND_STATE_EVENT_WITH_STATE_KEY(NetworkConstants.URI_API_PREFIX_PATH_R0 + "rooms/{roomId}/state/{state_event_type}/{state_key}", "PUT"), + GET_ROOM_STATE(NetworkConstants.URI_API_PREFIX_PATH_R0 + "rooms/{roomId}/state", "GET"), + SEND_RELATION(NetworkConstants.URI_API_PREFIX_PATH_R0 + "rooms/{roomId}/send_relation/{parent_id}/{relation_type}/{event_type}", "POST"), + GET_RELATIONS(NetworkConstants.URI_API_PREFIX_PATH_UNSTABLE + "rooms/{roomId}/relations/{eventId}/{relationType}/{eventType}", "GET"), + JOIN_ROOM(NetworkConstants.URI_API_PREFIX_PATH_R0 + "join/{roomIdOrAlias}", "POST"), + LEAVE_ROOM(NetworkConstants.URI_API_PREFIX_PATH_R0 + "rooms/{roomId}/leave", "POST"), + BAN_USER(NetworkConstants.URI_API_PREFIX_PATH_R0 + "rooms/{roomId}/ban", "POST"), + UNBAN_USER(NetworkConstants.URI_API_PREFIX_PATH_R0 + "rooms/{roomId}/unban", "POST"), + KICK_USER(NetworkConstants.URI_API_PREFIX_PATH_R0 + "rooms/{roomId}/kick", "POST"), + REDACT_EVENT(NetworkConstants.URI_API_PREFIX_PATH_R0 + "rooms/{roomId}/redact/{eventId}/{txnId}", "PUT"), + REPORT_CONTENT(NetworkConstants.URI_API_PREFIX_PATH_R0 + "rooms/{roomId}/report/{eventId}", "POST"), + GET_ALIASES(NetworkConstants.URI_API_PREFIX_PATH_UNSTABLE + "org.matrix.msc2432/rooms/{roomId}/aliases", "GET"), + SEND_TYPING_STATE(NetworkConstants.URI_API_PREFIX_PATH_R0 + "rooms/{roomId}/typing/{userId}", "PUT"), + PUT_TAG(NetworkConstants.URI_API_PREFIX_PATH_R0 + "user/{userId}/rooms/{roomId}/tags/{tag}", "PUT"), + DELETE_TAG(NetworkConstants.URI_API_PREFIX_PATH_R0 + "user/{userId}/rooms/{roomId}/tags/{tag}", "DELETE"), + + // SyncApi + SYNC(NetworkConstants.URI_API_PREFIX_PATH_R0 + "sync", "GET"), + + // ThirdPartyApi + THIRD_PARTY_PROTOCOLS(NetworkConstants.URI_API_PREFIX_PATH_R0 + "thirdparty/protocols", "GET"), + THIRD_PARTY_USER(NetworkConstants.URI_API_PREFIX_PATH_R0 + "thirdparty/protocols/user/{protocol}", "GET"), + + // SearchUserApi + SEARCH_USERS(NetworkConstants.URI_API_PREFIX_PATH_R0 + "user_directory/search", "POST"), + + // AccountDataApi + SET_ACCOUNT_DATA(NetworkConstants.URI_API_PREFIX_PATH_R0 + "user/{userId}/account_data/{type}", "PUT") +} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/pushrules/PushRuleService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/pushrules/PushRuleService.kt index 4da1662681e08c5dd6a13ffdca049509796adc63..d9bf5cfd13b2b0bb3123df5943b82d5d1f30f46a 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/pushrules/PushRuleService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/pushrules/PushRuleService.kt @@ -39,6 +39,8 @@ interface PushRuleService { fun removePushRuleListener(listener: PushRuleListener) + fun getActions(event: Event): List<Action> + // fun fulfilledBingRule(event: Event, rules: List<PushRule>): PushRule? interface PushRuleListener { diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/query/RoomCategoryFilter.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/query/RoomCategoryFilter.kt new file mode 100644 index 0000000000000000000000000000000000000000..c8ccc4c8a3ec3d8e9abce5ffee7591675028120f --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/query/RoomCategoryFilter.kt @@ -0,0 +1,24 @@ +/* + * Copyright (c) 2021 The Matrix.org Foundation C.I.C. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.matrix.android.sdk.api.query + +enum class RoomCategoryFilter { + ONLY_DM, + ONLY_ROOMS, + ONLY_WITH_NOTIFICATIONS, + ALL +} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/query/RoomTagQueryFilter.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/query/RoomTagQueryFilter.kt new file mode 100644 index 0000000000000000000000000000000000000000..613916bc18a3a4b0d569595bc7a444a40bde45c7 --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/query/RoomTagQueryFilter.kt @@ -0,0 +1,23 @@ +/* + * Copyright 2021 The Matrix.org Foundation C.I.C. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.matrix.android.sdk.api.query + +data class RoomTagQueryFilter( + val isFavorite: Boolean?, + val isLowPriority: Boolean?, + val isServerNotice: Boolean? +) 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 7a24ccac111224013585540df912b5f6bb4324dc..a15799d862ff4a601deb57ea1fb0df2e4e291228 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 @@ -30,6 +30,7 @@ import org.matrix.android.sdk.api.session.call.CallSignalingService import org.matrix.android.sdk.api.session.content.ContentUploadStateTracker import org.matrix.android.sdk.api.session.content.ContentUrlResolver import org.matrix.android.sdk.api.session.crypto.CryptoService +import org.matrix.android.sdk.api.session.events.EventService import org.matrix.android.sdk.api.session.file.ContentDownloadStateTracker import org.matrix.android.sdk.api.session.file.FileService import org.matrix.android.sdk.api.session.group.GroupService @@ -68,6 +69,7 @@ interface Session : SignOutService, FilterService, TermsService, + EventService, ProfileService, PushRuleService, PushersService, diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/accountdata/AccountDataService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/accountdata/AccountDataService.kt index f5d2a7df3e7e973c1594fa8fa924ed658fad5107..5ebeaad3de6fb8a1819024b154973df3a80e9968 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/accountdata/AccountDataService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/accountdata/AccountDataService.kt @@ -17,9 +17,7 @@ package org.matrix.android.sdk.api.session.accountdata import androidx.lifecycle.LiveData -import org.matrix.android.sdk.api.MatrixCallback import org.matrix.android.sdk.api.session.events.model.Content -import org.matrix.android.sdk.api.util.Cancelable import org.matrix.android.sdk.api.util.Optional interface AccountDataService { @@ -48,5 +46,5 @@ interface AccountDataService { /** * Update the account data with the provided type and the provided account data content */ - fun updateAccountData(type: String, content: Content, callback: MatrixCallback<Unit>? = null): Cancelable + suspend fun updateAccountData(type: String, content: Content) } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/verification/VerificationService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/verification/VerificationService.kt index 2413786ea90cd85f7d0fef382670c9dbbfcf4e5c..54a1e896ae04088bfc4c638f3f53dddcde3e1af3 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/verification/VerificationService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/verification/VerificationService.kt @@ -16,7 +16,6 @@ package org.matrix.android.sdk.api.session.crypto.verification -import org.matrix.android.sdk.api.MatrixCallback import org.matrix.android.sdk.api.session.events.model.Event import org.matrix.android.sdk.api.session.events.model.LocalEcho @@ -79,8 +78,7 @@ interface VerificationService { transactionId: String, roomId: String, otherUserId: String, - otherDeviceId: String, - callback: MatrixCallback<String>?): String? + otherDeviceId: String): String /** * Returns false if the request is unknown diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/MXQueuedEncryption.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/events/EventService.kt old mode 100755 new mode 100644 similarity index 56% rename from matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/MXQueuedEncryption.kt rename to matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/events/EventService.kt index fe6b3a74bb0f73f50fcbfed790ac3749c7ffc5b7..297f277497f13e48f59392b00a08c55eaaab9b98 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/MXQueuedEncryption.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/events/EventService.kt @@ -1,5 +1,5 @@ /* - * Copyright 2020 The Matrix.org Foundation C.I.C. + * Copyright (c) 2021 The Matrix.org Foundation C.I.C. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,21 +14,16 @@ * limitations under the License. */ -package org.matrix.android.sdk.internal.crypto.model +package org.matrix.android.sdk.api.session.events -import org.matrix.android.sdk.api.MatrixCallback -import org.matrix.android.sdk.api.session.events.model.Content +import org.matrix.android.sdk.api.session.events.model.Event -class MXQueuedEncryption { +interface EventService { /** - * The data to encrypt. + * Ask the homeserver for an event content. The SDK will try to decrypt it if it is possible + * The result will not be stored into cache */ - var eventContent: Content? = null - var eventType: String? = null - - /** - * the asynchronous callback - */ - var apiCallback: MatrixCallback<Content>? = null + suspend fun getEvent(roomId: String, + eventId: String): Event } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/events/model/Event.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/events/model/Event.kt index 844e8dbbab575abfde48aa878390234f65b1087d..89b873febbb6d593d93fe470acf11a7e1a2b4ee0 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/events/model/Event.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/events/model/Event.kt @@ -289,3 +289,7 @@ fun Event.getRelationContent(): RelationDefaultContent? { fun Event.isReply(): Boolean { return getRelationContent()?.inReplyTo?.eventId != null } + +fun Event.isEdition(): Boolean { + return getRelationContent()?.takeIf { it.type == RelationType.REPLACE }?.eventId != null +} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/file/FileService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/file/FileService.kt index bcdb5ea2575bd132e70f8329acecbe5033078056..adfdc2498e40c45e4b05d2e1ce0f86224b4ca97d 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/file/FileService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/file/FileService.kt @@ -17,11 +17,9 @@ package org.matrix.android.sdk.api.session.file import android.net.Uri -import org.matrix.android.sdk.api.MatrixCallback import org.matrix.android.sdk.api.session.room.model.message.MessageWithAttachmentContent import org.matrix.android.sdk.api.session.room.model.message.getFileName import org.matrix.android.sdk.api.session.room.model.message.getFileUrl -import org.matrix.android.sdk.api.util.Cancelable import org.matrix.android.sdk.internal.crypto.attachments.ElementToDecrypt import org.matrix.android.sdk.internal.crypto.attachments.toElementToDecrypt import java.io.File @@ -41,20 +39,17 @@ interface FileService { * Download a file. * Result will be a decrypted file, stored in the cache folder. url parameter will be used to create unique filename to avoid name collision. */ - fun downloadFile(fileName: String, + suspend fun downloadFile(fileName: String, mimeType: String?, url: String?, - elementToDecrypt: ElementToDecrypt?, - callback: MatrixCallback<File>): Cancelable + elementToDecrypt: ElementToDecrypt?): File - fun downloadFile(messageContent: MessageWithAttachmentContent, - callback: MatrixCallback<File>): Cancelable = + suspend fun downloadFile(messageContent: MessageWithAttachmentContent): File = downloadFile( fileName = messageContent.getFileName(), mimeType = messageContent.mimeType, url = messageContent.getFileUrl(), - elementToDecrypt = messageContent.encryptedFileInfo?.toElementToDecrypt(), - callback = callback + elementToDecrypt = messageContent.encryptedFileInfo?.toElementToDecrypt() ) fun isFileInCache(mxcUrl: String?, diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/identity/IdentityService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/identity/IdentityService.kt index aedb81373585412377705b40aec3ee1d115311fd..8f8967e8fb25994d35339f50b09fe348e8662d39 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/identity/IdentityService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/identity/IdentityService.kt @@ -16,9 +16,6 @@ package org.matrix.android.sdk.api.session.identity -import org.matrix.android.sdk.api.MatrixCallback -import org.matrix.android.sdk.api.util.Cancelable - /** * Provides access to the identity server configuration and services identity server can provide */ @@ -40,55 +37,55 @@ interface IdentityService { * See https://matrix.org/docs/spec/identity_service/latest#status-check * RiotX SDK only supports identity server API v2 */ - fun isValidIdentityServer(url: String, callback: MatrixCallback<Unit>): Cancelable + suspend fun isValidIdentityServer(url: String) /** * Update the identity server url. * If successful, any previous identity server will be disconnected. * In case of error, any previous identity server will remain configured. * @param url the new url. - * @param callback will notify the user if change is successful. The String will be the final url of the identity server. + * @return The String will be the final url of the identity server. * The SDK can prepend "https://" for instance. */ - fun setNewIdentityServer(url: String, callback: MatrixCallback<String>): Cancelable + suspend fun setNewIdentityServer(url: String): String /** * Disconnect (logout) from the current identity server */ - fun disconnect(callback: MatrixCallback<Unit>): Cancelable + suspend fun disconnect() /** * This will ask the identity server to send an email or an SMS to let the user confirm he owns the ThreePid */ - fun startBindThreePid(threePid: ThreePid, callback: MatrixCallback<Unit>): Cancelable + suspend fun startBindThreePid(threePid: ThreePid) /** * This will cancel a pending binding of threePid. */ - fun cancelBindThreePid(threePid: ThreePid, callback: MatrixCallback<Unit>): Cancelable + suspend fun cancelBindThreePid(threePid: ThreePid) /** * This will ask the identity server to send an new email or a new SMS to let the user confirm he owns the ThreePid */ - fun sendAgainValidationCode(threePid: ThreePid, callback: MatrixCallback<Unit>): Cancelable + suspend fun sendAgainValidationCode(threePid: ThreePid) /** * Submit the code that the identity server has sent to the user (in email or SMS) * Once successful, you will have to call [finalizeBindThreePid] * @param code the code sent to the user */ - fun submitValidationToken(threePid: ThreePid, code: String, callback: MatrixCallback<Unit>): Cancelable + suspend fun submitValidationToken(threePid: ThreePid, code: String) /** * This will perform the actual association of ThreePid and Matrix account */ - fun finalizeBindThreePid(threePid: ThreePid, callback: MatrixCallback<Unit>): Cancelable + suspend fun finalizeBindThreePid(threePid: ThreePid) /** * Unbind a threePid * The request will actually be done on the homeserver */ - fun unbindThreePid(threePid: ThreePid, callback: MatrixCallback<Unit>): Cancelable + suspend fun unbindThreePid(threePid: ThreePid) /** * Search MatrixId of users providing email and phone numbers @@ -96,7 +93,7 @@ interface IdentityService { * Application has to explicitly ask for the user consent, and the answer can be stored using [setUserConsent] * Please see https://support.google.com/googleplay/android-developer/answer/9888076?hl=en for more details. */ - fun lookUp(threePids: List<ThreePid>, callback: MatrixCallback<List<FoundThreePid>>): Cancelable + suspend fun lookUp(threePids: List<ThreePid>): List<FoundThreePid> /** * Return the current user consent for the current identity server, which has been stored using [setUserConsent]. @@ -120,9 +117,9 @@ interface IdentityService { * A lookup will be performed, but also pending binding state will be restored * * @param threePids the list of threePid the user owns (retrieved form the homeserver) - * @param callback onSuccess will be called with a map of ThreePid -> SharedState + * @return a map of ThreePid -> SharedState */ - fun getShareStatus(threePids: List<ThreePid>, callback: MatrixCallback<Map<ThreePid, SharedState>>): Cancelable + suspend fun getShareStatus(threePids: List<ThreePid>): Map<ThreePid, SharedState> fun addListener(listener: IdentityServiceListener) fun removeListener(listener: IdentityServiceListener) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/profile/ProfileService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/profile/ProfileService.kt index a4d5b665c648f453056e68623db2408e817a1050..e493adeaf273ad5cb4421a659d48de1764094e5e 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/profile/ProfileService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/profile/ProfileService.kt @@ -19,10 +19,8 @@ package org.matrix.android.sdk.api.session.profile import android.net.Uri import androidx.lifecycle.LiveData -import org.matrix.android.sdk.api.MatrixCallback import org.matrix.android.sdk.api.auth.UserInteractiveAuthInterceptor import org.matrix.android.sdk.api.session.identity.ThreePid -import org.matrix.android.sdk.api.util.Cancelable import org.matrix.android.sdk.api.util.JsonDict import org.matrix.android.sdk.api.util.Optional @@ -41,14 +39,14 @@ interface ProfileService { * @param userId the userId param to look for * */ - fun getDisplayName(userId: String, matrixCallback: MatrixCallback<Optional<String>>): Cancelable + suspend fun getDisplayName(userId: String): Optional<String> /** * Update the display name for this user * @param userId the userId to update the display name of * @param newDisplayName the new display name of the user */ - fun setDisplayName(userId: String, newDisplayName: String, matrixCallback: MatrixCallback<Unit>): Cancelable + suspend fun setDisplayName(userId: String, newDisplayName: String) /** * Update the avatar for this user @@ -56,14 +54,14 @@ interface ProfileService { * @param newAvatarUri the new avatar uri of the user * @param fileName the fileName of selected image */ - fun updateAvatar(userId: String, newAvatarUri: Uri, fileName: String, matrixCallback: MatrixCallback<Unit>): Cancelable + suspend fun updateAvatar(userId: String, newAvatarUri: Uri, fileName: String) /** * Return the current avatarUrl for this user. * @param userId the userId param to look for * */ - fun getAvatarUrl(userId: String, matrixCallback: MatrixCallback<Optional<String>>): Cancelable + suspend fun getAvatarUrl(userId: String): Optional<String> /** * Get the combined profile information for this user. @@ -71,7 +69,7 @@ interface ProfileService { * @param userId the userId param to look for * */ - fun getProfile(userId: String, matrixCallback: MatrixCallback<JsonDict>): Cancelable + suspend fun getProfile(userId: String): JsonDict /** * Get the current user 3Pids @@ -97,28 +95,26 @@ interface ProfileService { /** * Add a 3Pids. This is the first step to add a ThreePid to an account. Then the threePid will be added to the pending threePid list. */ - fun addThreePid(threePid: ThreePid, matrixCallback: MatrixCallback<Unit>): Cancelable + suspend fun addThreePid(threePid: ThreePid) /** * Validate a code received by text message */ - fun submitSmsCode(threePid: ThreePid.Msisdn, code: String, matrixCallback: MatrixCallback<Unit>): Cancelable + suspend fun submitSmsCode(threePid: ThreePid.Msisdn, code: String) /** * Finalize adding a 3Pids. Call this method once the user has validated that he owns the ThreePid */ - fun finalizeAddingThreePid(threePid: ThreePid, - userInteractiveAuthInterceptor: UserInteractiveAuthInterceptor, - matrixCallback: MatrixCallback<Unit>): Cancelable + suspend fun finalizeAddingThreePid(threePid: ThreePid, + userInteractiveAuthInterceptor: UserInteractiveAuthInterceptor) /** * Cancel adding a threepid. It will remove locally stored data about this ThreePid */ - fun cancelAddingThreePid(threePid: ThreePid, - matrixCallback: MatrixCallback<Unit>): Cancelable + suspend fun cancelAddingThreePid(threePid: ThreePid) /** * Remove a 3Pid from the Matrix account. */ - fun deleteThreePid(threePid: ThreePid, matrixCallback: MatrixCallback<Unit>): Cancelable + suspend fun deleteThreePid(threePid: ThreePid) } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/pushers/PushersService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/pushers/PushersService.kt index 3993422b1d1d27e471ea4478c225995b4842da8a..9ea820f5b3d80280d19833976e01dbbf32e09d2c 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/pushers/PushersService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/pushers/PushersService.kt @@ -16,8 +16,6 @@ package org.matrix.android.sdk.api.session.pushers import androidx.lifecycle.LiveData -import org.matrix.android.sdk.api.MatrixCallback -import org.matrix.android.sdk.api.util.Cancelable import java.util.UUID interface PushersService { @@ -75,16 +73,15 @@ interface PushersService { * @param callback callback to know if the push gateway has accepted the request. In this case, the app should receive a Push with the provided eventId. * In case of error, PusherRejected failure can happen. In this case it means that the pushkey is not valid. */ - fun testPush(url: String, - appId: String, - pushkey: String, - eventId: String, - callback: MatrixCallback<Unit>): Cancelable + suspend fun testPush(url: String, + appId: String, + pushkey: String, + eventId: String) /** * Remove the http pusher */ - fun removeHttpPusher(pushkey: String, appId: String, callback: MatrixCallback<Unit>): Cancelable + suspend fun removeHttpPusher(pushkey: String, appId: String) /** * Get the current pushers, as a LiveData diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/Room.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/Room.kt index cb6690b5c5eb56cfe8ac6dc40df035cc329895c8..257c83564e0c2df663e485b5947a9899b4cbcd5d 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/Room.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/Room.kt @@ -17,7 +17,6 @@ package org.matrix.android.sdk.api.session.room import androidx.lifecycle.LiveData -import org.matrix.android.sdk.api.MatrixCallback import org.matrix.android.sdk.api.session.room.alias.AliasService import org.matrix.android.sdk.api.session.room.call.RoomCallService import org.matrix.android.sdk.api.session.room.crypto.RoomCryptoService @@ -35,7 +34,6 @@ import org.matrix.android.sdk.api.session.room.timeline.TimelineService import org.matrix.android.sdk.api.session.room.typing.TypingService import org.matrix.android.sdk.api.session.room.uploads.UploadsService import org.matrix.android.sdk.api.session.search.SearchResult -import org.matrix.android.sdk.api.util.Cancelable import org.matrix.android.sdk.api.util.Optional /** @@ -86,12 +84,11 @@ interface Room : * @param includeProfile requests that the server returns the historic profile information for the users that sent the events that were returned. * @param callback Callback to get the search result */ - fun search(searchTerm: String, + suspend fun search(searchTerm: String, nextBatch: String?, orderByRecent: Boolean, limit: Int, beforeLimit: Int, afterLimit: Int, - includeProfile: Boolean, - callback: MatrixCallback<SearchResult>): Cancelable + includeProfile: Boolean): SearchResult } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/RoomService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/RoomService.kt index 5f02b77a1e43c56dfa48b2e6e2c16d9fff2f9e93..8c833644ee51af5b04e541c905855018d5e5ee11 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/RoomService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/RoomService.kt @@ -17,6 +17,7 @@ package org.matrix.android.sdk.api.session.room import androidx.lifecycle.LiveData +import androidx.paging.PagedList 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.members.ChangeMembershipState @@ -24,6 +25,7 @@ import org.matrix.android.sdk.api.session.room.model.RoomMemberSummary import org.matrix.android.sdk.api.session.room.model.RoomSummary import org.matrix.android.sdk.api.session.room.model.create.CreateRoomParams import org.matrix.android.sdk.api.session.room.peeking.PeekResult +import org.matrix.android.sdk.api.session.room.summary.RoomAggregateNotificationCount import org.matrix.android.sdk.api.util.Cancelable import org.matrix.android.sdk.api.util.Optional import org.matrix.android.sdk.internal.session.room.alias.RoomAliasDescription @@ -178,4 +180,29 @@ interface RoomService { * This call will try to gather some information on this room, but it could fail and get nothing more */ fun peekRoom(roomIdOrAlias: String, callback: MatrixCallback<PeekResult>) + + /** + * TODO Doc + */ + fun getPagedRoomSummariesLive(queryParams: RoomSummaryQueryParams, + pagedListConfig: PagedList.Config = defaultPagedListConfig): LiveData<PagedList<RoomSummary>> + + /** + * TODO Doc + */ + fun getFilteredPagedRoomSummariesLive(queryParams: RoomSummaryQueryParams, + pagedListConfig: PagedList.Config = defaultPagedListConfig): UpdatableFilterLivePageResult + + /** + * TODO Doc + */ + fun getNotificationCountForRooms(queryParams: RoomSummaryQueryParams): RoomAggregateNotificationCount + + private val defaultPagedListConfig + get() = PagedList.Config.Builder() + .setPageSize(10) + .setInitialLoadSizeHint(20) + .setEnablePlaceholders(false) + .setPrefetchDistance(10) + .build() } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/RoomSummaryQueryParams.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/RoomSummaryQueryParams.kt index f859d74a6f3d02919c625af9c66103e1fda932a1..7e04ebb5f295fbed1ec39a22f444a63fdae81fc7 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/RoomSummaryQueryParams.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/RoomSummaryQueryParams.kt @@ -17,6 +17,8 @@ package org.matrix.android.sdk.api.session.room import org.matrix.android.sdk.api.query.QueryStringValue +import org.matrix.android.sdk.api.query.RoomCategoryFilter +import org.matrix.android.sdk.api.query.RoomTagQueryFilter import org.matrix.android.sdk.api.session.room.model.Membership fun roomSummaryQueryParams(init: (RoomSummaryQueryParams.Builder.() -> Unit) = {}): RoomSummaryQueryParams { @@ -31,7 +33,9 @@ data class RoomSummaryQueryParams( val roomId: QueryStringValue, val displayName: QueryStringValue, val canonicalAlias: QueryStringValue, - val memberships: List<Membership> + val memberships: List<Membership>, + val roomCategoryFilter: RoomCategoryFilter?, + val roomTagQueryFilter: RoomTagQueryFilter? ) { class Builder { @@ -40,12 +44,16 @@ data class RoomSummaryQueryParams( var displayName: QueryStringValue = QueryStringValue.IsNotEmpty var canonicalAlias: QueryStringValue = QueryStringValue.NoCondition var memberships: List<Membership> = Membership.all() + var roomCategoryFilter: RoomCategoryFilter? = RoomCategoryFilter.ALL + var roomTagQueryFilter: RoomTagQueryFilter? = null fun build() = RoomSummaryQueryParams( roomId = roomId, displayName = displayName, canonicalAlias = canonicalAlias, - memberships = memberships + memberships = memberships, + roomCategoryFilter = roomCategoryFilter, + roomTagQueryFilter = roomTagQueryFilter ) } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/UpdatableFilterLivePageResult.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/UpdatableFilterLivePageResult.kt new file mode 100644 index 0000000000000000000000000000000000000000..71b3c665e709b2bbff8c4b41db0e91f9af016402 --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/UpdatableFilterLivePageResult.kt @@ -0,0 +1,27 @@ +/* + * Copyright (c) 2021 The Matrix.org Foundation C.I.C. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.matrix.android.sdk.api.session.room + +import androidx.lifecycle.LiveData +import androidx.paging.PagedList +import org.matrix.android.sdk.api.session.room.model.RoomSummary + +interface UpdatableFilterLivePageResult { + val livePagedList: LiveData<PagedList<RoomSummary>> + + fun updateQuery(queryParams: RoomSummaryQueryParams) +} 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 2c3ffac6875e899871f1693524a4910ee9924d80..198d6677a0bb319e764fd48ccb953616ac1ce015 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 @@ -17,10 +17,8 @@ package org.matrix.android.sdk.api.session.room.members import androidx.lifecycle.LiveData -import org.matrix.android.sdk.api.MatrixCallback import org.matrix.android.sdk.api.session.identity.ThreePid import org.matrix.android.sdk.api.session.room.model.RoomMemberSummary -import org.matrix.android.sdk.api.util.Cancelable /** * This interface defines methods to handling membership. It's implemented at the room level. @@ -29,9 +27,8 @@ interface MembershipService { /** * This methods load all room members if it was done yet. - * @return a [Cancelable] */ - fun loadRoomMembersIfNeeded(matrixCallback: MatrixCallback<Unit>): Cancelable + suspend fun loadRoomMembersIfNeeded() /** * Return the roomMember with userId or null. @@ -60,47 +57,35 @@ interface MembershipService { /** * Invite a user in the room */ - fun invite(userId: String, - reason: String? = null, - callback: MatrixCallback<Unit>): Cancelable + suspend fun invite(userId: String, reason: String? = null) /** * Invite a user with email or phone number in the room */ - fun invite3pid(threePid: ThreePid, - callback: MatrixCallback<Unit>): Cancelable + suspend fun invite3pid(threePid: ThreePid) /** * Ban a user from the room */ - fun ban(userId: String, - reason: String? = null, - callback: MatrixCallback<Unit>): Cancelable + suspend fun ban(userId: String, reason: String? = null) /** * Unban a user from the room */ - fun unban(userId: String, - reason: String? = null, - callback: MatrixCallback<Unit>): Cancelable + suspend fun unban(userId: String, reason: String? = null) /** * Kick a user from the room */ - fun kick(userId: String, - reason: String? = null, - callback: MatrixCallback<Unit>): Cancelable + suspend fun kick(userId: String, reason: String? = null) /** * Join the room, or accept an invitation. */ - fun join(reason: String? = null, - viaServers: List<String> = emptyList(), - callback: MatrixCallback<Unit>): Cancelable + suspend fun join(reason: String? = null, viaServers: List<String> = emptyList()) /** * Leave the room, or reject an invitation. */ - fun leave(reason: String? = null, - callback: MatrixCallback<Unit>): Cancelable + suspend fun leave(reason: String? = null) } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/Membership.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/Membership.kt index 5844aead8d23d05db0f3f52c2aeccc1683dac4f9..a5d0f63722c43dc1f110f4de549f76fc0f838dce 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/Membership.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/Membership.kt @@ -23,24 +23,13 @@ import com.squareup.moshi.JsonClass * Represents the membership of a user on a room */ @JsonClass(generateAdapter = false) -enum class Membership(val value: String) { - - NONE("none"), - - @Json(name = "invite") - INVITE("invite"), - - @Json(name = "join") - JOIN("join"), - - @Json(name = "knock") - KNOCK("knock"), - - @Json(name = "leave") - LEAVE("leave"), - - @Json(name = "ban") - BAN("ban"); +enum class Membership { + NONE, + @Json(name = "invite") INVITE, + @Json(name = "join") JOIN, + @Json(name = "knock") KNOCK, + @Json(name = "leave") LEAVE, + @Json(name = "ban") BAN; fun isLeft(): Boolean { return this == KNOCK || this == LEAVE || this == BAN diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/RoomGuestAccessContent.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/RoomGuestAccessContent.kt index 99b035d30e593a996affe7c6542556318b034509..0760c6f1b42900df128467e6e0795b266b5de284 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/RoomGuestAccessContent.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/RoomGuestAccessContent.kt @@ -18,6 +18,7 @@ package org.matrix.android.sdk.api.session.room.model import com.squareup.moshi.Json import com.squareup.moshi.JsonClass +import timber.log.Timber /** * Class representing the EventType.STATE_ROOM_GUEST_ACCESS state event content @@ -26,14 +27,20 @@ import com.squareup.moshi.JsonClass @JsonClass(generateAdapter = true) data class RoomGuestAccessContent( // Required. Whether guests can join the room. One of: ["can_join", "forbidden"] - @Json(name = "guest_access") val guestAccess: GuestAccess? = null -) + @Json(name = "guest_access") val _guestAccess: String? = null +) { + val guestAccess: GuestAccess? = when (_guestAccess) { + "can_join" -> GuestAccess.CanJoin + "forbidden" -> GuestAccess.Forbidden + else -> { + Timber.w("Invalid value for GuestAccess: `$_guestAccess`") + null + } + } +} @JsonClass(generateAdapter = false) -enum class GuestAccess(val value: String) { - @Json(name = "can_join") - CanJoin("can_join"), - - @Json(name = "forbidden") - Forbidden("forbidden") +enum class GuestAccess { + @Json(name = "can_join") CanJoin, + @Json(name = "forbidden") Forbidden } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/RoomHistoryVisibilityContent.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/RoomHistoryVisibilityContent.kt index 31493be7eacd6799bf59b322c013ed0ac7d52260..3ac14e48de77e592e05fcb75ed80b6ce371e4423 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/RoomHistoryVisibilityContent.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/RoomHistoryVisibilityContent.kt @@ -18,8 +18,20 @@ package org.matrix.android.sdk.api.session.room.model import com.squareup.moshi.Json import com.squareup.moshi.JsonClass +import timber.log.Timber @JsonClass(generateAdapter = true) data class RoomHistoryVisibilityContent( - @Json(name = "history_visibility") val historyVisibility: RoomHistoryVisibility? = null -) + @Json(name = "history_visibility") val _historyVisibility: String? = null +) { + val historyVisibility: RoomHistoryVisibility? = when (_historyVisibility) { + "world_readable" -> RoomHistoryVisibility.WORLD_READABLE + "shared" -> RoomHistoryVisibility.SHARED + "invited" -> RoomHistoryVisibility.INVITED + "joined" -> RoomHistoryVisibility.JOINED + else -> { + Timber.w("Invalid value for RoomHistoryVisibility: `$_historyVisibility`") + null + } + } +} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/RoomJoinRules.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/RoomJoinRules.kt index 09aacfabbe74d92f2002aa2eceacf78ba0c288f7..f3e8d357f3149f6990ba9a259d0e57a8f34c9da8 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/RoomJoinRules.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/RoomJoinRules.kt @@ -24,17 +24,9 @@ import com.squareup.moshi.JsonClass * Enum for [RoomJoinRulesContent] : https://matrix.org/docs/spec/client_server/r0.4.0#m-room-join-rules */ @JsonClass(generateAdapter = false) -enum class RoomJoinRules(val value: String) { - - @Json(name = "public") - PUBLIC("public"), - - @Json(name = "invite") - INVITE("invite"), - - @Json(name = "knock") - KNOCK("knock"), - - @Json(name = "private") - PRIVATE("private") +enum class RoomJoinRules { + @Json(name = "public") PUBLIC, + @Json(name = "invite") INVITE, + @Json(name = "knock") KNOCK, + @Json(name = "private") PRIVATE } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/RoomJoinRulesContent.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/RoomJoinRulesContent.kt index 3be2d38be76903f79f696f452ab5742614c1325c..8082486b22f1c8174704eee4c50d715b037e851b 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/RoomJoinRulesContent.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/RoomJoinRulesContent.kt @@ -19,11 +19,23 @@ package org.matrix.android.sdk.api.session.room.model import com.squareup.moshi.Json import com.squareup.moshi.JsonClass +import timber.log.Timber /** * Class representing the EventType.STATE_ROOM_JOIN_RULES state event content */ @JsonClass(generateAdapter = true) data class RoomJoinRulesContent( - @Json(name = "join_rule") val joinRules: RoomJoinRules? = null -) + @Json(name = "join_rule") val _joinRules: String? = null +) { + val joinRules: RoomJoinRules? = when (_joinRules) { + "public" -> RoomJoinRules.PUBLIC + "invite" -> RoomJoinRules.INVITE + "knock" -> RoomJoinRules.KNOCK + "private" -> RoomJoinRules.PRIVATE + else -> { + Timber.w("Invalid value for RoomJoinRules: `$_joinRules`") + null + } + } +} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/MessageType.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/MessageType.kt index a2b4e135d122d7f5822d205ac84eef023ff0adea..c96a800ee586b32895bbd6bd9ebf3fcab8bb6890 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/MessageType.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/MessageType.kt @@ -35,5 +35,5 @@ object MessageType { const val MSGTYPE_STICKER_LOCAL = "org.matrix.android.sdk.sticker" const val MSGTYPE_CONFETTI = "nic.custom.confetti" - const val MSGTYPE_SNOW = "nic.custom.snow" + const val MSGTYPE_SNOW = "io.element.effect.snowfall" } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/read/ReadService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/read/ReadService.kt index 4f44c9a912630fe818c9fd2c4bfe75213eee5c81..b037a3f3668846b4b6f54e9bbe99b2c9fe573cee 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/read/ReadService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/read/ReadService.kt @@ -17,7 +17,6 @@ package org.matrix.android.sdk.api.session.room.read import androidx.lifecycle.LiveData -import org.matrix.android.sdk.api.MatrixCallback import org.matrix.android.sdk.api.session.room.model.ReadReceipt import org.matrix.android.sdk.api.util.Optional @@ -35,17 +34,17 @@ interface ReadService { /** * Force the read marker to be set on the latest event. */ - fun markAsRead(params: MarkAsReadParams = MarkAsReadParams.BOTH, callback: MatrixCallback<Unit>) + suspend fun markAsRead(params: MarkAsReadParams = MarkAsReadParams.BOTH) /** * Set the read receipt on the event with provided eventId. */ - fun setReadReceipt(eventId: String, callback: MatrixCallback<Unit>) + suspend fun setReadReceipt(eventId: String) /** * Set the read marker on the event with provided eventId. */ - fun setReadMarker(fullyReadEventId: String, callback: MatrixCallback<Unit>) + suspend fun setReadMarker(fullyReadEventId: String) /** * Check if an event is already read, ie. your read receipt is set on a more recent event. diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/summary/RoomAggregateNotificationCount.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/summary/RoomAggregateNotificationCount.kt new file mode 100644 index 0000000000000000000000000000000000000000..066178b1ecfc5f36ea25dbf6f5b876e7455cf584 --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/summary/RoomAggregateNotificationCount.kt @@ -0,0 +1,25 @@ +/* + * Copyright 2021 The Matrix.org Foundation C.I.C. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.matrix.android.sdk.api.session.room.summary + +data class RoomAggregateNotificationCount( + val notificationCount: Int, + val highlightCount: Int +) { + val totalCount = notificationCount + highlightCount + val isHighlight = highlightCount > 0 +} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/timeline/Timeline.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/timeline/Timeline.kt index 8932d0734e5570e4e5fe36c6fd543162eb48e1f4..06c88db83162afe5a50ce55fa45c721484ea7df1 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/timeline/Timeline.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/timeline/Timeline.kt @@ -95,12 +95,6 @@ interface Timeline { */ fun getTimelineEventWithId(eventId: String?): TimelineEvent? - /** - * Returns the first displayable events starting from eventId. - * It does depend on the provided [TimelineSettings]. - */ - fun getFirstDisplayableEventId(eventId: String): String? - interface Listener { /** * Call when the timeline has been updated through pagination or sync. diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/timeline/TimelineSettings.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/timeline/TimelineSettings.kt index 25c63d6fbc38da81d406dd05b0f575295cf3ba62..ceffedb234775e286efa12d53e5bf5e4e1e3caee 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/timeline/TimelineSettings.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/timeline/TimelineSettings.kt @@ -24,10 +24,6 @@ data class TimelineSettings( * The initial number of events to retrieve from cache. You might get less events if you don't have loaded enough yet. */ val initialSize: Int, - /** - * Filters for timeline event - */ - val filters: TimelineEventFilters = TimelineEventFilters(), /** * If true, will build read receipts for each event. */ diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/securestorage/SharedSecretStorageService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/securestorage/SharedSecretStorageService.kt index 37ecf99f9ad67fe92a8459f0069d1746cf5ccb28..721a2bc8af11f03577ac931014bdf1f869fbcb8c 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/securestorage/SharedSecretStorageService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/securestorage/SharedSecretStorageService.kt @@ -16,7 +16,6 @@ package org.matrix.android.sdk.api.session.securestorage -import org.matrix.android.sdk.api.MatrixCallback import org.matrix.android.sdk.api.listeners.ProgressListener import org.matrix.android.sdk.api.session.crypto.crosssigning.KEYBACKUP_SECRET_SSSS_NAME import org.matrix.android.sdk.api.session.crypto.crosssigning.MASTER_KEY_SSSS_NAME @@ -43,13 +42,12 @@ interface SharedSecretStorageService { * @param keyName a human readable name * @param keySigner Used to add a signature to the key (client should check key signature before storing secret) * - * @param callback Get key creation info + * @return key creation info */ - fun generateKey(keyId: String, - key: SsssKeySpec?, - keyName: String, - keySigner: KeySigner?, - callback: MatrixCallback<SsssKeyCreationInfo>) + suspend fun generateKey(keyId: String, + key: SsssKeySpec?, + keyName: String, + keySigner: KeySigner?): SsssKeyCreationInfo /** * Generates a SSSS key using the given passphrase. @@ -61,14 +59,13 @@ interface SharedSecretStorageService { * @param keySigner Used to add a signature to the key (client should check key signature before retrieving secret) * @param progressListener The derivation of the passphrase may take long depending on the device, use this to report progress * - * @param callback Get key creation info + * @return key creation info */ - fun generateKeyWithPassphrase(keyId: String, - keyName: String, - passphrase: String, - keySigner: KeySigner, - progressListener: ProgressListener?, - callback: MatrixCallback<SsssKeyCreationInfo>) + suspend fun generateKeyWithPassphrase(keyId: String, + keyName: String, + passphrase: String, + keySigner: KeySigner, + progressListener: ProgressListener?): SsssKeyCreationInfo fun getKey(keyId: String): KeyInfoResult @@ -80,7 +77,7 @@ interface SharedSecretStorageService { */ fun getDefaultKey(): KeyInfoResult - fun setDefaultKey(keyId: String, callback: MatrixCallback<Unit>) + suspend fun setDefaultKey(keyId: String) /** * Check whether we have a key with a given ID. @@ -98,7 +95,7 @@ interface SharedSecretStorageService { * @param secret The secret contents. * @param keys The list of (ID,privateKey) of the keys to use to encrypt the secret. */ - fun storeSecret(name: String, secretBase64: String, keys: List<KeyRef>, callback: MatrixCallback<Unit>) + suspend fun storeSecret(name: String, secretBase64: String, keys: List<KeyRef>) /** * Use this call to determine which SSSSKeySpec to use for requesting secret @@ -113,7 +110,7 @@ interface SharedSecretStorageService { * @param secretKey the secret key to use (@see #RawBytesKeySpec) * */ - fun getSecret(name: String, keyId: String?, secretKey: SsssKeySpec, callback: MatrixCallback<String>) + suspend fun getSecret(name: String, keyId: String?, secretKey: SsssKeySpec): String /** * Return true if SSSS is configured diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/user/UserService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/user/UserService.kt index ab85f979bf6ca591a31031ea1b5b91b2103074ff..cd4fb216d327331b943f46aee28e960b2c096ef1 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/user/UserService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/user/UserService.kt @@ -18,9 +18,7 @@ package org.matrix.android.sdk.api.session.user import androidx.lifecycle.LiveData import androidx.paging.PagedList -import org.matrix.android.sdk.api.MatrixCallback import org.matrix.android.sdk.api.session.user.model.User -import org.matrix.android.sdk.api.util.Cancelable import org.matrix.android.sdk.api.util.Optional /** @@ -38,17 +36,16 @@ interface UserService { /** * Try to resolve user from known users, or using profile api */ - fun resolveUser(userId: String, callback: MatrixCallback<User>) + suspend fun resolveUser(userId: String): User /** * Search list of users on server directory. * @param search the searched term * @param limit the max number of users to return * @param excludedUserIds the user ids to filter from the search - * @param callback the async callback * @return Cancelable */ - fun searchUsersDirectory(search: String, limit: Int, excludedUserIds: Set<String>, callback: MatrixCallback<List<User>>): Cancelable + suspend fun searchUsersDirectory(search: String, limit: Int, excludedUserIds: Set<String>): List<User> /** * Observe a live user from a userId @@ -79,10 +76,10 @@ interface UserService { /** * Ignore users */ - fun ignoreUserIds(userIds: List<String>, callback: MatrixCallback<Unit>): Cancelable + suspend fun ignoreUserIds(userIds: List<String>) /** * Un-ignore some users */ - fun unIgnoreUserIds(userIds: List<String>, callback: MatrixCallback<Unit>): Cancelable + suspend fun unIgnoreUserIds(userIds: List<String>) } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/widgets/WidgetService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/widgets/WidgetService.kt index bf3ff8959dc21d5e99fe39d9c21ea08d8a9557ff..8f35ff0e4a4532c8efe5da900feb02e66e969664 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/widgets/WidgetService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/widgets/WidgetService.kt @@ -17,10 +17,8 @@ package org.matrix.android.sdk.api.session.widgets import androidx.lifecycle.LiveData -import org.matrix.android.sdk.api.MatrixCallback import org.matrix.android.sdk.api.query.QueryStringValue import org.matrix.android.sdk.api.session.events.model.Content -import org.matrix.android.sdk.api.util.Cancelable import org.matrix.android.sdk.api.session.widgets.model.Widget /** @@ -107,20 +105,16 @@ interface WidgetService { * @param roomId the room where you want to create the widget. * @param widgetId the widget to create. * @param content the content of the widget - * @param callback the matrix callback to listen for result. - * @return Cancelable */ - fun createRoomWidget(roomId: String, widgetId: String, content: Content, callback: MatrixCallback<Widget>): Cancelable + suspend fun createRoomWidget(roomId: String, widgetId: String, content: Content): Widget /** * Deactivate a widget in a room. It makes sure you have the rights to handle this. * * @param roomId: the room where you want to deactivate the widget. * @param widgetId: the widget to deactivate. - * @param callback the matrix callback to listen for result. - * @return Cancelable */ - fun destroyRoomWidget(roomId: String, widgetId: String, callback: MatrixCallback<Unit>): Cancelable + suspend fun destroyRoomWidget(roomId: String, widgetId: String) /** * Returns true if you can add/remove widgets. It goes through diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/AuthAPI.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/AuthAPI.kt index f92ae7e0eec972c28d6363a6476640285fb99621..f93f285c6eaca6e4a730bcb69d8a52aba30fb444 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/AuthAPI.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/AuthAPI.kt @@ -17,6 +17,7 @@ package org.matrix.android.sdk.internal.auth import org.matrix.android.sdk.api.auth.data.Credentials +import org.matrix.android.sdk.internal.auth.data.Availability import org.matrix.android.sdk.internal.auth.data.LoginFlowResponse import org.matrix.android.sdk.internal.auth.data.PasswordLoginParams import org.matrix.android.sdk.internal.auth.data.RiotConfig @@ -29,12 +30,12 @@ import org.matrix.android.sdk.internal.auth.registration.SuccessResult import org.matrix.android.sdk.internal.auth.registration.ValidationCodeBody import org.matrix.android.sdk.internal.auth.version.Versions import org.matrix.android.sdk.internal.network.NetworkConstants -import retrofit2.Call import retrofit2.http.Body import retrofit2.http.GET import retrofit2.http.Headers import retrofit2.http.POST import retrofit2.http.Path +import retrofit2.http.Query import retrofit2.http.Url /** @@ -45,26 +46,32 @@ internal interface AuthAPI { * Get a Riot config file, using the name including the domain */ @GET("config.{domain}.json") - fun getRiotConfigDomain(@Path("domain") domain: String): Call<RiotConfig> + suspend fun getRiotConfigDomain(@Path("domain") domain: String): RiotConfig /** * Get a Riot config file */ @GET("config.json") - fun getRiotConfig(): Call<RiotConfig> + suspend fun getRiotConfig(): RiotConfig /** * Get the version information of the homeserver */ @GET(NetworkConstants.URI_API_PREFIX_PATH_ + "versions") - fun versions(): Call<Versions> + suspend fun versions(): Versions /** * Register to the homeserver, or get error 401 with a RegistrationFlowResponse object if registration is incomplete * Ref: https://matrix.org/docs/spec/client_server/latest#account-registration-and-management */ @POST(NetworkConstants.URI_API_PREFIX_PATH_R0 + "register") - fun register(@Body registrationParams: RegistrationParams): Call<Credentials> + suspend fun register(@Body registrationParams: RegistrationParams): Credentials + + /** + * Checks to see if a username is available, and valid, for the server. + */ + @GET(NetworkConstants.URI_API_PREFIX_PATH_R0 + "register/available") + suspend fun registerAvailable(@Query("username") username: String): Availability /** * Add 3Pid during registration @@ -72,22 +79,22 @@ internal interface AuthAPI { * https://github.com/matrix-org/matrix-doc/pull/2290 */ @POST(NetworkConstants.URI_API_PREFIX_PATH_R0 + "register/{threePid}/requestToken") - fun add3Pid(@Path("threePid") threePid: String, - @Body params: AddThreePidRegistrationParams): Call<AddThreePidRegistrationResponse> + suspend fun add3Pid(@Path("threePid") threePid: String, + @Body params: AddThreePidRegistrationParams): AddThreePidRegistrationResponse /** * Validate 3pid */ @POST - fun validate3Pid(@Url url: String, - @Body params: ValidationCodeBody): Call<SuccessResult> + suspend fun validate3Pid(@Url url: String, + @Body params: ValidationCodeBody): SuccessResult /** * Get the supported login flow * Ref: https://matrix.org/docs/spec/client_server/latest#get-matrix-client-r0-login */ @GET(NetworkConstants.URI_API_PREFIX_PATH_R0 + "login") - fun getLoginFlows(): Call<LoginFlowResponse> + suspend fun getLoginFlows(): LoginFlowResponse /** * Pass params to the server for the current login phase. @@ -97,22 +104,22 @@ internal interface AuthAPI { */ @Headers("CONNECT_TIMEOUT:60000", "READ_TIMEOUT:60000", "WRITE_TIMEOUT:60000") @POST(NetworkConstants.URI_API_PREFIX_PATH_R0 + "login") - fun login(@Body loginParams: PasswordLoginParams): Call<Credentials> + suspend fun login(@Body loginParams: PasswordLoginParams): Credentials // Unfortunately we cannot use interface for @Body parameter, so I duplicate the method for the type TokenLoginParams @Headers("CONNECT_TIMEOUT:60000", "READ_TIMEOUT:60000", "WRITE_TIMEOUT:60000") @POST(NetworkConstants.URI_API_PREFIX_PATH_R0 + "login") - fun login(@Body loginParams: TokenLoginParams): Call<Credentials> + suspend fun login(@Body loginParams: TokenLoginParams): Credentials /** * Ask the homeserver to reset the password associated with the provided email. */ @POST(NetworkConstants.URI_API_PREFIX_PATH_R0 + "account/password/email/requestToken") - fun resetPassword(@Body params: AddThreePidRegistrationParams): Call<AddThreePidRegistrationResponse> + suspend fun resetPassword(@Body params: AddThreePidRegistrationParams): AddThreePidRegistrationResponse /** * Ask the homeserver to reset the password with the provided new password once the email is validated. */ @POST(NetworkConstants.URI_API_PREFIX_PATH_R0 + "account/password") - fun resetPasswordMailConfirmed(@Body params: ResetPasswordMailConfirmed): Call<Unit> + suspend fun resetPasswordMailConfirmed(@Body params: ResetPasswordMailConfirmed) } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/DefaultAuthenticationService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/DefaultAuthenticationService.kt index 4f3451cf307dc9ce85c1b03a29ae3055665c28a7..e26286ad2f5b64bca88b469a69ddd4e6cd9a4196 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/DefaultAuthenticationService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/DefaultAuthenticationService.kt @@ -31,7 +31,6 @@ import org.matrix.android.sdk.api.failure.Failure import org.matrix.android.sdk.api.session.Session import org.matrix.android.sdk.api.util.appendParamToUrl import org.matrix.android.sdk.internal.SessionManager -import org.matrix.android.sdk.internal.auth.data.LoginFlowResponse import org.matrix.android.sdk.internal.auth.data.RiotConfig import org.matrix.android.sdk.internal.auth.db.PendingSessionData import org.matrix.android.sdk.internal.auth.login.DefaultLoginWizard @@ -172,8 +171,8 @@ internal class DefaultAuthenticationService @Inject constructor( // First check the homeserver version return runCatching { - executeRequest<Versions>(null) { - apiCall = authAPI.versions() + executeRequest(null) { + authAPI.versions() } } .map { versions -> @@ -204,8 +203,8 @@ internal class DefaultAuthenticationService @Inject constructor( // Ok, try to get the config.domain.json file of a RiotWeb client return runCatching { - executeRequest<RiotConfig>(null) { - apiCall = authAPI.getRiotConfigDomain(domain) + executeRequest(null) { + authAPI.getRiotConfigDomain(domain) } } .map { riotConfig -> @@ -232,8 +231,8 @@ internal class DefaultAuthenticationService @Inject constructor( // Ok, try to get the config.json file of a RiotWeb client return runCatching { - executeRequest<RiotConfig>(null) { - apiCall = authAPI.getRiotConfig() + executeRequest(null) { + authAPI.getRiotConfig() } } .map { riotConfig -> @@ -265,8 +264,8 @@ internal class DefaultAuthenticationService @Inject constructor( val newAuthAPI = buildAuthAPI(newHomeServerConnectionConfig) - val versions = executeRequest<Versions>(null) { - apiCall = newAuthAPI.versions() + val versions = executeRequest(null) { + newAuthAPI.versions() } return getLoginFlowResult(newAuthAPI, versions, defaultHomeServerUrl) @@ -293,8 +292,8 @@ internal class DefaultAuthenticationService @Inject constructor( val newAuthAPI = buildAuthAPI(newHomeServerConnectionConfig) - val versions = executeRequest<Versions>(null) { - apiCall = newAuthAPI.versions() + val versions = executeRequest(null) { + newAuthAPI.versions() } getLoginFlowResult(newAuthAPI, versions, wellknownResult.homeServerUrl) @@ -305,8 +304,8 @@ internal class DefaultAuthenticationService @Inject constructor( private suspend fun getLoginFlowResult(authAPI: AuthAPI, versions: Versions, homeServerUrl: String): LoginFlowResult { // Get the login flow - val loginFlowResponse = executeRequest<LoginFlowResponse>(null) { - apiCall = authAPI.getLoginFlows() + val loginFlowResponse = executeRequest(null) { + authAPI.getLoginFlows() } return LoginFlowResult.Success( loginFlowResponse.flows.orEmpty().mapNotNull { it.type }, diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/IsValidClientServerApiTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/IsValidClientServerApiTask.kt index b8416d69bfef0c1fa87f3c268392f940a0553f77..867cf46b8dbef8fd47e4601da74345c3bda21ad8 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/IsValidClientServerApiTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/IsValidClientServerApiTask.kt @@ -20,7 +20,6 @@ import dagger.Lazy import okhttp3.OkHttpClient import org.matrix.android.sdk.api.auth.data.HomeServerConnectionConfig import org.matrix.android.sdk.api.failure.Failure -import org.matrix.android.sdk.internal.auth.data.LoginFlowResponse import org.matrix.android.sdk.internal.di.Unauthenticated import org.matrix.android.sdk.internal.network.RetrofitFactory import org.matrix.android.sdk.internal.network.executeRequest @@ -49,8 +48,8 @@ internal class DefaultIsValidClientServerApiTask @Inject constructor( .create(AuthAPI::class.java) return try { - executeRequest<LoginFlowResponse>(null) { - apiCall = authAPI.getLoginFlows() + executeRequest(null) { + authAPI.getLoginFlows() } // We get a response, so the API is valid true diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/data/Availability.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/data/Availability.kt new file mode 100644 index 0000000000000000000000000000000000000000..5ef3c0d06a1f4bc5b1be555db09798cb3e409e11 --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/data/Availability.kt @@ -0,0 +1,29 @@ +/* + * Copyright 2021 The Matrix.org Foundation C.I.C. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.matrix.android.sdk.internal.auth.data + +import com.squareup.moshi.Json +import com.squareup.moshi.JsonClass + +@JsonClass(generateAdapter = true) +internal data class Availability( + /** + * A flag to indicate that the username is available. This should always be true when the server replies with 200 OK. + */ + @Json(name = "available") + val available: Boolean? = null +) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/login/DefaultLoginWizard.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/login/DefaultLoginWizard.kt index 41678758497352821957812b5397c1a35254dbd9..8b81f42e03873f47c2108c90039141fe5c90f1fb 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/login/DefaultLoginWizard.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/login/DefaultLoginWizard.kt @@ -17,7 +17,6 @@ package org.matrix.android.sdk.internal.auth.login import android.util.Patterns -import org.matrix.android.sdk.api.auth.data.Credentials import org.matrix.android.sdk.api.auth.login.LoginWizard import org.matrix.android.sdk.api.auth.registration.RegisterThreePid import org.matrix.android.sdk.api.session.Session @@ -29,7 +28,6 @@ import org.matrix.android.sdk.internal.auth.data.ThreePidMedium import org.matrix.android.sdk.internal.auth.data.TokenLoginParams import org.matrix.android.sdk.internal.auth.db.PendingSessionData import org.matrix.android.sdk.internal.auth.registration.AddThreePidRegistrationParams -import org.matrix.android.sdk.internal.auth.registration.AddThreePidRegistrationResponse import org.matrix.android.sdk.internal.auth.registration.RegisterAddThreePidTask import org.matrix.android.sdk.internal.network.executeRequest @@ -49,8 +47,8 @@ internal class DefaultLoginWizard( } else { PasswordLoginParams.userIdentifier(login, password, deviceName) } - val credentials = executeRequest<Credentials>(null) { - apiCall = authAPI.login(loginParams) + val credentials = executeRequest(null) { + authAPI.login(loginParams) } return sessionCreator.createSession(credentials, pendingSessionData.homeServerConnectionConfig) @@ -63,8 +61,8 @@ internal class DefaultLoginWizard( val loginParams = TokenLoginParams( token = loginToken ) - val credentials = executeRequest<Credentials>(null) { - apiCall = authAPI.login(loginParams) + val credentials = executeRequest(null) { + authAPI.login(loginParams) } return sessionCreator.createSession(credentials, pendingSessionData.homeServerConnectionConfig) @@ -80,8 +78,8 @@ internal class DefaultLoginWizard( pendingSessionData = pendingSessionData.copy(sendAttempt = pendingSessionData.sendAttempt + 1) .also { pendingSessionStore.savePendingSessionData(it) } - val result = executeRequest<AddThreePidRegistrationResponse>(null) { - apiCall = authAPI.resetPassword(AddThreePidRegistrationParams.from(param)) + val result = executeRequest(null) { + authAPI.resetPassword(AddThreePidRegistrationParams.from(param)) } pendingSessionData = pendingSessionData.copy(resetPasswordData = ResetPasswordData(newPassword, result)) @@ -98,8 +96,8 @@ internal class DefaultLoginWizard( safeResetPasswordData.newPassword ) - executeRequest<Unit>(null) { - apiCall = authAPI.resetPasswordMailConfirmed(param) + executeRequest(null) { + authAPI.resetPasswordMailConfirmed(param) } // Set to null? diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/login/DirectLoginTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/login/DirectLoginTask.kt index be6ff389312d74072776dd8d5418db7d23ad95f2..77bbb8096fe943c1a5e7b4cbb8f5bf5f1beb638c 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/login/DirectLoginTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/login/DirectLoginTask.kt @@ -17,7 +17,6 @@ package org.matrix.android.sdk.internal.auth.login import dagger.Lazy -import org.matrix.android.sdk.api.auth.data.Credentials import org.matrix.android.sdk.api.auth.data.HomeServerConnectionConfig import org.matrix.android.sdk.api.failure.Failure import org.matrix.android.sdk.api.session.Session @@ -59,19 +58,16 @@ internal class DefaultDirectLoginTask @Inject constructor( val loginParams = PasswordLoginParams.userIdentifier(params.userId, params.password, params.deviceName) val credentials = try { - executeRequest<Credentials>(null) { - apiCall = authAPI.login(loginParams) + executeRequest(null) { + authAPI.login(loginParams) } } catch (throwable: Throwable) { - when (throwable) { - is UnrecognizedCertificateException -> { - throw Failure.UnrecognizedCertificateFailure( - homeServerUrl, - throwable.fingerprint - ) - } - else -> - throw throwable + throw when (throwable) { + is UnrecognizedCertificateException -> Failure.UnrecognizedCertificateFailure( + homeServerUrl, + throwable.fingerprint + ) + else -> throwable } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/registration/DefaultRegistrationWizard.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/registration/DefaultRegistrationWizard.kt index 91e414e689771e8f6939eb60d817ada4a82bcb55..4a3d53a8fc9328b80da39bdc83ee6a2781fcd5b9 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/registration/DefaultRegistrationWizard.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/registration/DefaultRegistrationWizard.kt @@ -19,6 +19,7 @@ package org.matrix.android.sdk.internal.auth.registration import kotlinx.coroutines.delay import org.matrix.android.sdk.api.auth.data.LoginFlowTypes import org.matrix.android.sdk.api.auth.registration.RegisterThreePid +import org.matrix.android.sdk.api.auth.registration.RegistrationAvailability import org.matrix.android.sdk.api.auth.registration.RegistrationResult import org.matrix.android.sdk.api.auth.registration.RegistrationWizard import org.matrix.android.sdk.api.auth.registration.toFlowResult @@ -40,9 +41,10 @@ internal class DefaultRegistrationWizard( private var pendingSessionData: PendingSessionData = pendingSessionStore.getPendingSessionData() ?: error("Pending session data should exist here") - private val registerTask = DefaultRegisterTask(authAPI) - private val registerAddThreePidTask = DefaultRegisterAddThreePidTask(authAPI) - private val validateCodeTask = DefaultValidateCodeTask(authAPI) + private val registerTask: RegisterTask = DefaultRegisterTask(authAPI) + private val registerAvailableTask: RegisterAvailableTask = DefaultRegisterAvailableTask(authAPI) + private val registerAddThreePidTask: RegisterAddThreePidTask = DefaultRegisterAddThreePidTask(authAPI) + private val validateCodeTask: ValidateCodeTask = DefaultValidateCodeTask(authAPI) override val currentThreePid: String? get() { @@ -203,4 +205,8 @@ internal class DefaultRegistrationWizard( val session = sessionCreator.createSession(credentials, pendingSessionData.homeServerConnectionConfig) return RegistrationResult.Success(session) } + + override suspend fun registrationAvailable(userName: String): RegistrationAvailability { + return registerAvailableTask.execute(RegisterAvailableTask.Params(userName)) + } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/registration/RegisterAddThreePidTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/registration/RegisterAddThreePidTask.kt index 57c4b72b8a23e8268d81722b825b6c4728b06071..54a8ba0e6ce94786fd7e61f9cae3ee9da1c1ed20 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/registration/RegisterAddThreePidTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/registration/RegisterAddThreePidTask.kt @@ -35,7 +35,7 @@ internal class DefaultRegisterAddThreePidTask( override suspend fun execute(params: RegisterAddThreePidTask.Params): AddThreePidRegistrationResponse { return executeRequest(null) { - apiCall = authAPI.add3Pid(params.threePid.toPath(), AddThreePidRegistrationParams.from(params)) + authAPI.add3Pid(params.threePid.toPath(), AddThreePidRegistrationParams.from(params)) } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/registration/RegisterAvailableTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/registration/RegisterAvailableTask.kt new file mode 100644 index 0000000000000000000000000000000000000000..314a24dad4cb23e7d0c15e192ea20f0985af068b --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/registration/RegisterAvailableTask.kt @@ -0,0 +1,47 @@ +/* + * Copyright 2021 The Matrix.org Foundation C.I.C. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.matrix.android.sdk.internal.auth.registration + +import org.matrix.android.sdk.api.auth.registration.RegistrationAvailability +import org.matrix.android.sdk.api.failure.Failure +import org.matrix.android.sdk.api.failure.isRegistrationAvailabilityError +import org.matrix.android.sdk.internal.auth.AuthAPI +import org.matrix.android.sdk.internal.network.executeRequest +import org.matrix.android.sdk.internal.task.Task + +internal interface RegisterAvailableTask : Task<RegisterAvailableTask.Params, RegistrationAvailability> { + data class Params( + val userName: String + ) +} + +internal class DefaultRegisterAvailableTask(private val authAPI: AuthAPI) : RegisterAvailableTask { + override suspend fun execute(params: RegisterAvailableTask.Params): RegistrationAvailability { + return try { + executeRequest(null) { + authAPI.registerAvailable(params.userName) + } + RegistrationAvailability.Available + } catch (exception: Throwable) { + if (exception.isRegistrationAvailabilityError()) { + RegistrationAvailability.NotAvailable(exception as Failure.ServerError) + } else { + throw exception + } + } + } +} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/registration/RegisterTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/registration/RegisterTask.kt index bf5d899276933bbb2b086191d4ea7a38fd86ff84..45668cb8ad0d2a7f44974c5642912b84d42d29d1 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/registration/RegisterTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/registration/RegisterTask.kt @@ -36,7 +36,7 @@ internal class DefaultRegisterTask( override suspend fun execute(params: RegisterTask.Params): Credentials { try { return executeRequest(null) { - apiCall = authAPI.register(params.registrationParams) + authAPI.register(params.registrationParams) } } catch (throwable: Throwable) { throw throwable.toRegistrationFlowResponse() diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/registration/ValidateCodeTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/registration/ValidateCodeTask.kt index b297c9849d0fcf3d2790f4993f1f62c10c3d64bb..d68b7cd9eb68484968b2ee50148417c2d82eaaee 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/registration/ValidateCodeTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/registration/ValidateCodeTask.kt @@ -33,7 +33,7 @@ internal class DefaultValidateCodeTask( override suspend fun execute(params: ValidateCodeTask.Params): SuccessResult { return executeRequest(null) { - apiCall = authAPI.validate3Pid(params.url, params.body) + authAPI.validate3Pid(params.url, params.body) } } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/CryptoSessionInfoProvider.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/CryptoSessionInfoProvider.kt index e8a70615e1497acebf65bce12701d33938227ac6..5338e7e92f537bca9d63113311ec1b844ef05eef 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/CryptoSessionInfoProvider.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/CryptoSessionInfoProvider.kt @@ -38,7 +38,7 @@ internal class CryptoSessionInfoProvider @Inject constructor( val encryptionEvent = monarchy.fetchCopied { realm -> EventEntity.whereType(realm, roomId = roomId, type = EventType.STATE_ROOM_ENCRYPTION) .contains(EventEntityFields.CONTENT, "\"algorithm\":\"$MXCRYPTO_ALGORITHM_MEGOLM\"") - .isNotNull(EventEntityFields.STATE_KEY) // should be an empty key + .isEmpty(EventEntityFields.STATE_KEY) .findFirst() } return encryptionEvent != null diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/DefaultCryptoService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/DefaultCryptoService.kt index 17d25736ebd52b105cd70295eab5dc5f8a64a7d5..2163b2a5e0276046d414bb1e4da8ff4d5d112284 100755 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/DefaultCryptoService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/DefaultCryptoService.kt @@ -856,15 +856,8 @@ internal class DefaultCryptoService @Inject constructor( return } cryptoCoroutineScope.launch(coroutineDispatchers.crypto) { - val params = LoadRoomMembersTask.Params(roomId) - try { - loadRoomMembersTask.execute(params) - } catch (throwable: Throwable) { - Timber.e(throwable, "## CRYPTO | onRoomEncryptionEvent ERROR FAILED TO SETUP CRYPTO ") - } finally { - val userIds = getRoomUserIds(roomId) - setEncryptionInRoom(roomId, event.content?.get("algorithm")?.toString(), true, userIds) - } + val userIds = getRoomUserIds(roomId) + setEncryptionInRoom(roomId, event.content?.get("algorithm")?.toString(), true, userIds) } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/DeviceListManager.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/DeviceListManager.kt index 42df6b354b53aec8bc3b0e86efd12b3eeca1ff66..e5f1c011f8c2c349ca9d4bac26eb67c8c7beb9b8 100755 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/DeviceListManager.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/DeviceListManager.kt @@ -16,6 +16,7 @@ package org.matrix.android.sdk.internal.crypto +import kotlinx.coroutines.launch import org.matrix.android.sdk.api.MatrixPatterns import org.matrix.android.sdk.api.auth.data.Credentials import org.matrix.android.sdk.internal.crypto.crosssigning.DeviceTrustLevel @@ -28,7 +29,7 @@ import org.matrix.android.sdk.internal.session.SessionScope import org.matrix.android.sdk.internal.session.sync.SyncTokenStore import org.matrix.android.sdk.internal.task.TaskExecutor import org.matrix.android.sdk.internal.util.MatrixCoroutineDispatchers -import kotlinx.coroutines.launch +import org.matrix.android.sdk.internal.util.logLimit import timber.log.Timber import javax.inject.Inject @@ -39,8 +40,9 @@ internal class DeviceListManager @Inject constructor(private val cryptoStore: IM private val syncTokenStore: SyncTokenStore, private val credentials: Credentials, private val downloadKeysForUsersTask: DownloadKeysForUsersTask, + private val cryptoSessionInfoProvider: CryptoSessionInfoProvider, coroutineDispatchers: MatrixCoroutineDispatchers, - taskExecutor: TaskExecutor) { + private val taskExecutor: TaskExecutor) { interface UserDevicesUpdateListener { fun onUsersDeviceUpdate(userIds: List<String>) @@ -75,8 +77,10 @@ internal class DeviceListManager @Inject constructor(private val cryptoStore: IM // HS not ready for retry private val notReadyToRetryHS = mutableSetOf<String>() + private val cryptoCoroutineContext = coroutineDispatchers.crypto + init { - taskExecutor.executorScope.launch(coroutineDispatchers.crypto) { + taskExecutor.executorScope.launch(cryptoCoroutineContext) { var isUpdated = false val deviceTrackingStatuses = cryptoStore.getDeviceTrackingStatuses().toMutableMap() for ((userId, status) in deviceTrackingStatuses) { @@ -104,7 +108,7 @@ internal class DeviceListManager @Inject constructor(private val cryptoStore: IM if (':' in userId) { try { synchronized(notReadyToRetryHS) { - res = !notReadyToRetryHS.contains(userId.substringAfterLast(':')) + res = !notReadyToRetryHS.contains(userId.substringAfter(':')) } } catch (e: Exception) { Timber.e(e, "## CRYPTO | canRetryKeysDownload() failed") @@ -123,28 +127,37 @@ internal class DeviceListManager @Inject constructor(private val cryptoStore: IM } } + fun onRoomMembersLoadedFor(roomId: String) { + taskExecutor.executorScope.launch(cryptoCoroutineContext) { + if (cryptoSessionInfoProvider.isRoomEncrypted(roomId)) { + // It's OK to track also device for invited users + val userIds = cryptoSessionInfoProvider.getRoomUserIds(roomId, true) + startTrackingDeviceList(userIds) + refreshOutdatedDeviceLists() + } + } + } + /** * Mark the cached device list for the given user outdated * flag the given user for device-list tracking, if they are not already. * * @param userIds the user ids list */ - fun startTrackingDeviceList(userIds: List<String>?) { - if (null != userIds) { - var isUpdated = false - val deviceTrackingStatuses = cryptoStore.getDeviceTrackingStatuses().toMutableMap() + fun startTrackingDeviceList(userIds: List<String>) { + var isUpdated = false + val deviceTrackingStatuses = cryptoStore.getDeviceTrackingStatuses().toMutableMap() - for (userId in userIds) { - if (!deviceTrackingStatuses.containsKey(userId) || TRACKING_STATUS_NOT_TRACKED == deviceTrackingStatuses[userId]) { - Timber.v("## CRYPTO | startTrackingDeviceList() : Now tracking device list for $userId") - deviceTrackingStatuses[userId] = TRACKING_STATUS_PENDING_DOWNLOAD - isUpdated = true - } + for (userId in userIds) { + if (!deviceTrackingStatuses.containsKey(userId) || TRACKING_STATUS_NOT_TRACKED == deviceTrackingStatuses[userId]) { + Timber.v("## CRYPTO | startTrackingDeviceList() : Now tracking device list for $userId") + deviceTrackingStatuses[userId] = TRACKING_STATUS_PENDING_DOWNLOAD + isUpdated = true } + } - if (isUpdated) { - cryptoStore.saveDeviceTrackingStatuses(deviceTrackingStatuses) - } + if (isUpdated) { + cryptoStore.saveDeviceTrackingStatuses(deviceTrackingStatuses) } } @@ -155,13 +168,17 @@ internal class DeviceListManager @Inject constructor(private val cryptoStore: IM * @param left the user ids list which left a room */ fun handleDeviceListsChanges(changed: Collection<String>, left: Collection<String>) { - Timber.v("## CRYPTO: handleDeviceListsChanges changed:$changed / left:$left") + Timber.v("## CRYPTO: handleDeviceListsChanges changed: ${changed.logLimit()} / left: ${left.logLimit()}") var isUpdated = false val deviceTrackingStatuses = cryptoStore.getDeviceTrackingStatuses().toMutableMap() + if (changed.isNotEmpty() || left.isNotEmpty()) { + clearUnavailableServersList() + } + for (userId in changed) { if (deviceTrackingStatuses.containsKey(userId)) { - Timber.v("## CRYPTO | invalidateUserDeviceList() : Marking device list outdated for $userId") + Timber.v("## CRYPTO | handleDeviceListsChanges() : Marking device list outdated for $userId") deviceTrackingStatuses[userId] = TRACKING_STATUS_PENDING_DOWNLOAD isUpdated = true } @@ -169,7 +186,7 @@ internal class DeviceListManager @Inject constructor(private val cryptoStore: IM for (userId in left) { if (deviceTrackingStatuses.containsKey(userId)) { - Timber.v("## CRYPTO | invalidateUserDeviceList() : No longer tracking device list for $userId") + Timber.v("## CRYPTO | handleDeviceListsChanges() : No longer tracking device list for $userId") deviceTrackingStatuses[userId] = TRACKING_STATUS_NOT_TRACKED isUpdated = true } @@ -307,7 +324,7 @@ internal class DeviceListManager @Inject constructor(private val cryptoStore: IM * @param downloadUsers the user ids list */ private suspend fun doKeyDownloadForUsers(downloadUsers: List<String>): MXUsersDevicesMap<CryptoDeviceInfo> { - Timber.v("## CRYPTO | doKeyDownloadForUsers() : doKeyDownloadForUsers $downloadUsers") + Timber.v("## CRYPTO | doKeyDownloadForUsers() : doKeyDownloadForUsers ${downloadUsers.logLimit()}") // get the user ids which did not already trigger a keys download val filteredUsers = downloadUsers.filter { MatrixPatterns.isUserId(it) } if (filteredUsers.isEmpty()) { diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/GossipingWorkManager.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/GossipingWorkManager.kt index c9451aa0028afcbad2b62334992399677b86de54..0013c31eead3319a5977e72d9dad4275bc944328 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/GossipingWorkManager.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/GossipingWorkManager.kt @@ -39,7 +39,7 @@ internal class GossipingWorkManager @Inject constructor( .setConstraints(WorkManagerProvider.workConstraints) .startChain(startChain) .setInputData(data) - .setBackoffCriteria(BackoffPolicy.LINEAR, 10_000L, TimeUnit.MILLISECONDS) + .setBackoffCriteria(BackoffPolicy.LINEAR, WorkManagerProvider.BACKOFF_DELAY_MILLIS, TimeUnit.MILLISECONDS) .build() } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/MXOlmDevice.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/MXOlmDevice.kt index b1e91e8d50760b84915b63237ed1773051035291..b8f1a9abea84cfc0f6c39f9cfb5a051851f249c9 100755 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/MXOlmDevice.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/MXOlmDevice.kt @@ -312,7 +312,7 @@ internal class MXOlmDevice @Inject constructor( * @param theirDeviceIdentityKey the Curve25519 identity key for the remote device. * @return a list of known session ids for the device. */ - fun getSessionIds(theirDeviceIdentityKey: String): Set<String>? { + fun getSessionIds(theirDeviceIdentityKey: String): List<String>? { return store.getDeviceSessionIds(theirDeviceIdentityKey) } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/megolm/MXMegolmEncryption.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/megolm/MXMegolmEncryption.kt index 6b91c0b859adf52242d2b77713f7fa49d934bbea..697711d05161d8440b14edaa9b9dac78c119654e 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/megolm/MXMegolmEncryption.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/megolm/MXMegolmEncryption.kt @@ -67,8 +67,9 @@ internal class MXMegolmEncryption( init { // restore existing outbound session if any - outboundSession = olmDevice.restoreOutboundGroupSessionForRoom(roomId) + outboundSession = olmDevice.restoreOutboundGroupSessionForRoom(roomId) } + // Default rotation periods // TODO: Make it configurable via parameters // Session rotation periods @@ -125,6 +126,7 @@ internal class MXMegolmEncryption( Timber.v("## CRYPTO | preshareKey ${System.currentTimeMillis() - ts} millis") } + /** * Prepare a new session. * @@ -240,6 +242,7 @@ internal class MXMegolmEncryption( val contentMap = MXUsersDevicesMap<Any>() var haveTargets = false val userIds = results.userIds + val noOlmToNotify = mutableListOf<UserDevice>() for (userId in userIds) { val devicesToShareWith = devicesByUser[userId] for ((deviceID) in devicesToShareWith!!) { @@ -251,13 +254,7 @@ internal class MXMegolmEncryption( // MSC 2399 // send withheld m.no_olm: an olm session could not be established. // This may happen, for example, if the sender was unable to obtain a one-time key from the recipient. - notifyKeyWithHeld( - listOf(UserDevice(userId, deviceID)), - session.sessionId, - olmDevice.deviceCurve25519Key, - WithHeldCode.NO_OLM - ) - + noOlmToNotify.add(UserDevice(userId, deviceID)) continue } Timber.i("## CRYPTO | shareUserDevicesKey() : Add to share keys contentMap for $userId:$deviceID") @@ -277,14 +274,14 @@ internal class MXMegolmEncryption( session.sharedWithHelper.markedSessionAsShared(userId, deviceId, chainIndex) gossipingEventBuffer.add( Event( - type = EventType.ROOM_KEY, - senderId = this.userId, - content = submap.apply { - this["session_key"] = "" - // we add a fake key for trail - this["_dest"] = "$userId|$deviceId" - } - )) + type = EventType.ROOM_KEY, + senderId = this.userId, + content = submap.apply { + this["session_key"] = "" + // we add a fake key for trail + this["_dest"] = "$userId|$deviceId" + } + )) } } @@ -304,6 +301,16 @@ internal class MXMegolmEncryption( } else { Timber.i("## CRYPTO | shareUserDevicesKey() : no need to sharekey") } + + if (noOlmToNotify.isNotEmpty()) { + // XXX offload?, as they won't read the message anyhow? + notifyKeyWithHeld( + noOlmToNotify, + session.sessionId, + olmDevice.deviceCurve25519Key, + WithHeldCode.NO_OLM + ) + } } private suspend fun notifyKeyWithHeld(targets: List<UserDevice>, diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/olm/MXOlmDecryption.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/olm/MXOlmDecryption.kt index 541f62de2c322a8d708de7800b23ae7673120a8f..082b86c9da67226003ef1431c7b23ccb02612dd8 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/olm/MXOlmDecryption.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/olm/MXOlmDecryption.kt @@ -154,7 +154,7 @@ internal class MXOlmDecryption( * @return payload, if decrypted successfully. */ private fun decryptMessage(message: JsonDict, theirDeviceIdentityKey: String): String? { - val sessionIds = olmDevice.getSessionIds(theirDeviceIdentityKey) ?: emptySet() + val sessionIds = olmDevice.getSessionIds(theirDeviceIdentityKey).orEmpty() val messageBody = message["body"] as? String ?: return null val messageType = when (val typeAsVoid = message["type"]) { diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/api/CryptoApi.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/api/CryptoApi.kt index 5604e97152981128dce6753fdfc90888de5da740..cef86e8b5ec03f1fde51e4ca1f5d909f9187072a 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/api/CryptoApi.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/api/CryptoApi.kt @@ -30,7 +30,6 @@ import org.matrix.android.sdk.internal.crypto.model.rest.SignatureUploadResponse import org.matrix.android.sdk.internal.crypto.model.rest.UpdateDeviceInfoBody import org.matrix.android.sdk.internal.crypto.model.rest.UploadSigningKeysBody import org.matrix.android.sdk.internal.network.NetworkConstants -import retrofit2.Call import retrofit2.http.Body import retrofit2.http.GET import retrofit2.http.HTTP @@ -46,14 +45,14 @@ internal interface CryptoApi { * Doc: https://matrix.org/docs/spec/client_server/latest#get-matrix-client-r0-devices */ @GET(NetworkConstants.URI_API_PREFIX_PATH_R0 + "devices") - fun getDevices(): Call<DevicesListResponse> + suspend fun getDevices(): DevicesListResponse /** * Get the device info by id * Doc: https://matrix.org/docs/spec/client_server/latest#get-matrix-client-r0-devices-deviceid */ @GET(NetworkConstants.URI_API_PREFIX_PATH_R0 + "devices/{deviceId}") - fun getDeviceInfo(@Path("deviceId") deviceId: String): Call<DeviceInfo> + suspend fun getDeviceInfo(@Path("deviceId") deviceId: String): DeviceInfo /** * Upload device and/or one-time keys. @@ -62,7 +61,7 @@ internal interface CryptoApi { * @param body the keys to be sent. */ @POST(NetworkConstants.URI_API_PREFIX_PATH_R0 + "keys/upload") - fun uploadKeys(@Body body: KeysUploadBody): Call<KeysUploadResponse> + suspend fun uploadKeys(@Body body: KeysUploadBody): KeysUploadResponse /** * Download device keys. @@ -71,7 +70,7 @@ internal interface CryptoApi { * @param params the params. */ @POST(NetworkConstants.URI_API_PREFIX_PATH_R0 + "keys/query") - fun downloadKeysForUsers(@Body params: KeysQueryBody): Call<KeysQueryResponse> + suspend fun downloadKeysForUsers(@Body params: KeysQueryBody): KeysQueryResponse /** * CrossSigning - Uploading signing keys @@ -79,7 +78,7 @@ internal interface CryptoApi { * This endpoint requires UI Auth. */ @POST(NetworkConstants.URI_API_PREFIX_PATH_UNSTABLE + "keys/device_signing/upload") - fun uploadSigningKeys(@Body params: UploadSigningKeysBody): Call<KeysQueryResponse> + suspend fun uploadSigningKeys(@Body params: UploadSigningKeysBody): KeysQueryResponse /** * CrossSigning - Uploading signatures @@ -98,7 +97,7 @@ internal interface CryptoApi { * However, signatures made for other users' keys, made by her user-signing key, will not be included. */ @POST(NetworkConstants.URI_API_PREFIX_PATH_UNSTABLE + "keys/signatures/upload") - fun uploadSignatures(@Body params: Map<String, @JvmSuppressWildcards Any>?): Call<SignatureUploadResponse> + suspend fun uploadSignatures(@Body params: Map<String, @JvmSuppressWildcards Any>?): SignatureUploadResponse /** * Claim one-time keys. @@ -107,7 +106,7 @@ internal interface CryptoApi { * @param params the params. */ @POST(NetworkConstants.URI_API_PREFIX_PATH_R0 + "keys/claim") - fun claimOneTimeKeysForUsersDevices(@Body body: KeysClaimBody): Call<KeysClaimResponse> + suspend fun claimOneTimeKeysForUsersDevices(@Body body: KeysClaimBody): KeysClaimResponse /** * Send an event to a specific list of devices @@ -118,9 +117,9 @@ internal interface CryptoApi { * @param body the body */ @PUT(NetworkConstants.URI_API_PREFIX_PATH_R0 + "sendToDevice/{eventType}/{txnId}") - fun sendToDevice(@Path("eventType") eventType: String, - @Path("txnId") transactionId: String, - @Body body: SendToDeviceBody): Call<Unit> + suspend fun sendToDevice(@Path("eventType") eventType: String, + @Path("txnId") transactionId: String, + @Body body: SendToDeviceBody) /** * Delete a device. @@ -130,8 +129,8 @@ internal interface CryptoApi { * @param params the deletion parameters */ @HTTP(path = NetworkConstants.URI_API_PREFIX_PATH_R0 + "devices/{device_id}", method = "DELETE", hasBody = true) - fun deleteDevice(@Path("device_id") deviceId: String, - @Body params: DeleteDeviceParams): Call<Unit> + suspend fun deleteDevice(@Path("device_id") deviceId: String, + @Body params: DeleteDeviceParams) /** * Update the device information. @@ -141,8 +140,8 @@ internal interface CryptoApi { * @param params the params */ @PUT(NetworkConstants.URI_API_PREFIX_PATH_R0 + "devices/{device_id}") - fun updateDeviceInfo(@Path("device_id") deviceId: String, - @Body params: UpdateDeviceInfoBody): Call<Unit> + suspend fun updateDeviceInfo(@Path("device_id") deviceId: String, + @Body params: UpdateDeviceInfoBody) /** * Get the update devices list from two sync token. @@ -152,6 +151,6 @@ internal interface CryptoApi { * @param newToken the up-to token. */ @GET(NetworkConstants.URI_API_PREFIX_PATH_R0 + "keys/changes") - fun getKeyChanges(@Query("from") oldToken: String, - @Query("to") newToken: String): Call<KeyChangesResponse> + suspend fun getKeyChanges(@Query("from") oldToken: String, + @Query("to") newToken: String): KeyChangesResponse } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/crosssigning/DefaultCrossSigningService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/crosssigning/DefaultCrossSigningService.kt index 9b282f0a84bd2235b15bc9415a3d5503891dd533..0289fadbd8f8fa980c9bff80da6e86d4c0a6d683 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/crosssigning/DefaultCrossSigningService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/crosssigning/DefaultCrossSigningService.kt @@ -43,6 +43,7 @@ import org.matrix.android.sdk.internal.task.TaskThread import org.matrix.android.sdk.internal.task.configureWith import org.matrix.android.sdk.internal.util.JsonCanonicalizer import org.matrix.android.sdk.internal.util.MatrixCoroutineDispatchers +import org.matrix.android.sdk.internal.util.logLimit import org.matrix.android.sdk.internal.worker.WorkerParamsFactory import org.matrix.olm.OlmPkSigning import org.matrix.olm.OlmUtility @@ -750,7 +751,7 @@ internal class DefaultCrossSigningService @Inject constructor( } override fun onUsersDeviceUpdate(userIds: List<String>) { - Timber.d("## CrossSigning - onUsersDeviceUpdate for ${userIds.size} users: $userIds") + Timber.d("## CrossSigning - onUsersDeviceUpdate for users: ${userIds.logLimit()}") val workerParams = UpdateTrustWorker.Params( sessionId = sessionId, filename = updateTrustWorkerDataRepository.createParam(userIds) @@ -759,7 +760,7 @@ internal class DefaultCrossSigningService @Inject constructor( val workRequest = workManagerProvider.matrixOneTimeWorkRequestBuilder<UpdateTrustWorker>() .setInputData(workerData) - .setBackoffCriteria(BackoffPolicy.LINEAR, 2_000L, TimeUnit.MILLISECONDS) + .setBackoffCriteria(BackoffPolicy.LINEAR, WorkManagerProvider.BACKOFF_DELAY_MILLIS, TimeUnit.MILLISECONDS) .build() workManagerProvider.workManager diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/crosssigning/UpdateTrustWorker.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/crosssigning/UpdateTrustWorker.kt index 1660bae0b7f1bd5635943bf5705c2240713c1ed0..76b63b7798e9e66675761255ed0fd0a5e8fc9591 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/crosssigning/UpdateTrustWorker.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/crosssigning/UpdateTrustWorker.kt @@ -33,15 +33,18 @@ import org.matrix.android.sdk.internal.crypto.store.db.model.CryptoMapper import org.matrix.android.sdk.internal.crypto.store.db.model.TrustLevelEntity import org.matrix.android.sdk.internal.crypto.store.db.model.UserEntity import org.matrix.android.sdk.internal.crypto.store.db.model.UserEntityFields +import org.matrix.android.sdk.internal.database.awaitTransaction import org.matrix.android.sdk.internal.database.model.RoomMemberSummaryEntity import org.matrix.android.sdk.internal.database.model.RoomMemberSummaryEntityFields import org.matrix.android.sdk.internal.database.model.RoomSummaryEntity +import org.matrix.android.sdk.internal.database.model.RoomSummaryEntityFields import org.matrix.android.sdk.internal.database.query.where import org.matrix.android.sdk.internal.di.CryptoDatabase import org.matrix.android.sdk.internal.di.SessionDatabase import org.matrix.android.sdk.internal.di.UserId import org.matrix.android.sdk.internal.session.SessionComponent import org.matrix.android.sdk.internal.session.room.membership.RoomMemberHelper +import org.matrix.android.sdk.internal.util.logLimit import org.matrix.android.sdk.internal.worker.SessionSafeCoroutineWorker import org.matrix.android.sdk.internal.worker.SessionWorkerParams import timber.log.Timber @@ -65,11 +68,16 @@ internal class UpdateTrustWorker(context: Context, @Inject lateinit var crossSigningService: DefaultCrossSigningService // It breaks the crypto store contract, but we need to batch things :/ - @CryptoDatabase @Inject lateinit var realmConfiguration: RealmConfiguration - @UserId @Inject lateinit var myUserId: String + @CryptoDatabase + @Inject lateinit var cryptoRealmConfiguration: RealmConfiguration + + @SessionDatabase + @Inject lateinit var sessionRealmConfiguration: RealmConfiguration + + @UserId + @Inject lateinit var myUserId: String @Inject lateinit var crossSigningKeysMapper: CrossSigningKeysMapper @Inject lateinit var updateTrustWorkerDataRepository: UpdateTrustWorkerDataRepository - @SessionDatabase @Inject lateinit var sessionRealmConfiguration: RealmConfiguration // @Inject lateinit var roomSummaryUpdater: RoomSummaryUpdater @Inject lateinit var cryptoStore: IMXCryptoStore @@ -79,118 +87,109 @@ internal class UpdateTrustWorker(context: Context, } override suspend fun doSafeWork(params: Params): Result { - var userList = params.filename + val userList = params.filename ?.let { updateTrustWorkerDataRepository.getParam(it) } ?.userIds ?: params.updatedUserIds.orEmpty() - if (userList.isEmpty()) { - // This should not happen, but let's avoid go further in case of empty list - cleanup(params) - return Result.success() + // List should not be empty, but let's avoid go further in case of empty list + if (userList.isNotEmpty()) { + // Unfortunately we don't have much info on what did exactly changed (is it the cross signing keys of that user, + // or a new device?) So we check all again :/ + Timber.d("## CrossSigning - Updating trust for users: ${userList.logLimit()}") + updateTrust(userList) } - // Unfortunately we don't have much info on what did exactly changed (is it the cross signing keys of that user, - // or a new device?) So we check all again :/ + cleanup(params) + return Result.success() + } - Timber.d("## CrossSigning - Updating trust for $userList") + private suspend fun updateTrust(userListParam: List<String>) { + var userList = userListParam + var myCrossSigningInfo: MXCrossSigningInfo? = null // First we check that the users MSK are trusted by mine // After that we check the trust chain for each devices of each users - Realm.getInstance(realmConfiguration).use { realm -> - realm.executeTransaction { - // By mapping here to model, this object is not live - // I should update it if needed - var myCrossSigningInfo = realm.where(CrossSigningInfoEntity::class.java) - .equalTo(CrossSigningInfoEntityFields.USER_ID, myUserId) - .findFirst()?.let { mapCrossSigningInfoEntity(it) } - - var myTrustResult: UserTrustResult? = null - - if (userList.contains(myUserId)) { - Timber.d("## CrossSigning - Clear all trust as a change on my user was detected") - // i am in the list.. but i don't know exactly the delta of change :/ - // If it's my cross signing keys we should refresh all trust - // do it anyway ? - userList = realm.where(CrossSigningInfoEntity::class.java) - .findAll().mapNotNull { it.userId } - Timber.d("## CrossSigning - Updating trust for all $userList") - - // check right now my keys and mark it as trusted as other trust depends on it - val myDevices = realm.where<UserEntity>() - .equalTo(UserEntityFields.USER_ID, myUserId) - .findFirst() - ?.devices - ?.map { deviceInfo -> - CryptoMapper.mapToModel(deviceInfo) - } - myTrustResult = crossSigningService.checkSelfTrust(myCrossSigningInfo, myDevices).also { - updateCrossSigningKeysTrust(realm, myUserId, it.isVerified()) - // update model reference - myCrossSigningInfo = realm.where(CrossSigningInfoEntity::class.java) - .equalTo(CrossSigningInfoEntityFields.USER_ID, myUserId) - .findFirst()?.let { mapCrossSigningInfoEntity(it) } - } - } + awaitTransaction(cryptoRealmConfiguration) { cryptoRealm -> + // By mapping here to model, this object is not live + // I should update it if needed + myCrossSigningInfo = getCrossSigningInfo(cryptoRealm, myUserId) + + var myTrustResult: UserTrustResult? = null + + if (userList.contains(myUserId)) { + Timber.d("## CrossSigning - Clear all trust as a change on my user was detected") + // i am in the list.. but i don't know exactly the delta of change :/ + // If it's my cross signing keys we should refresh all trust + // do it anyway ? + userList = cryptoRealm.where(CrossSigningInfoEntity::class.java) + .findAll() + .mapNotNull { it.userId } + + // check right now my keys and mark it as trusted as other trust depends on it + val myDevices = cryptoRealm.where<UserEntity>() + .equalTo(UserEntityFields.USER_ID, myUserId) + .findFirst() + ?.devices + ?.map { CryptoMapper.mapToModel(it) } + + myTrustResult = crossSigningService.checkSelfTrust(myCrossSigningInfo, myDevices) + updateCrossSigningKeysTrust(cryptoRealm, myUserId, myTrustResult.isVerified()) + // update model reference + myCrossSigningInfo = getCrossSigningInfo(cryptoRealm, myUserId) + } - val otherInfos = userList.map { - it to realm.where(CrossSigningInfoEntity::class.java) - .equalTo(CrossSigningInfoEntityFields.USER_ID, it) - .findFirst()?.let { mapCrossSigningInfoEntity(it) } - } - .toMap() - - val trusts = otherInfos.map { infoEntry -> - infoEntry.key to when (infoEntry.key) { - myUserId -> myTrustResult - else -> { - crossSigningService.checkOtherMSKTrusted(myCrossSigningInfo, infoEntry.value).also { - Timber.d("## CrossSigning - user:${infoEntry.key} result:$it") - } + val otherInfos = userList.associateWith { userId -> + getCrossSigningInfo(cryptoRealm, userId) + } + + val trusts = otherInfos.mapValues { entry -> + when (entry.key) { + myUserId -> myTrustResult + else -> { + crossSigningService.checkOtherMSKTrusted(myCrossSigningInfo, entry.value).also { + Timber.d("## CrossSigning - user:${entry.key} result:$it") } } - }.toMap() + } + } - // TODO! if it's me and my keys has changed... I have to reset trust for everyone! - // i have all the new trusts, update DB - trusts.forEach { - val verified = it.value?.isVerified() == true - updateCrossSigningKeysTrust(realm, it.key, verified) + // TODO! if it's me and my keys has changed... I have to reset trust for everyone! + // i have all the new trusts, update DB + trusts.forEach { + val verified = it.value?.isVerified() == true + updateCrossSigningKeysTrust(cryptoRealm, it.key, verified) + } + + // Ok so now we have to check device trust for all these users.. + Timber.v("## CrossSigning - Updating devices cross trust users: ${trusts.keys.logLimit()}") + trusts.keys.forEach { userId -> + val devicesEntities = cryptoRealm.where<UserEntity>() + .equalTo(UserEntityFields.USER_ID, userId) + .findFirst() + ?.devices + + val trustMap = devicesEntities?.associateWith { device -> + // get up to date from DB has could have been updated + val otherInfo = getCrossSigningInfo(cryptoRealm, userId) + crossSigningService.checkDeviceTrust(myCrossSigningInfo, otherInfo, CryptoMapper.mapToModel(device)) } - // Ok so now we have to check device trust for all these users.. - Timber.v("## CrossSigning - Updating devices cross trust users ${trusts.keys}") - trusts.keys.forEach { - val devicesEntities = realm.where<UserEntity>() - .equalTo(UserEntityFields.USER_ID, it) - .findFirst() - ?.devices - - val trustMap = devicesEntities?.map { device -> - // get up to date from DB has could have been updated - val otherInfo = realm.where(CrossSigningInfoEntity::class.java) - .equalTo(CrossSigningInfoEntityFields.USER_ID, it) - .findFirst()?.let { mapCrossSigningInfoEntity(it) } - device to crossSigningService.checkDeviceTrust(myCrossSigningInfo, otherInfo, CryptoMapper.mapToModel(device)) - }?.toMap() - - // Update trust if needed - devicesEntities?.forEach { device -> - val crossSignedVerified = trustMap?.get(device)?.isCrossSignedVerified() - Timber.d("## CrossSigning - Trust for ${device.userId}|${device.deviceId} : cross verified: ${trustMap?.get(device)}") - if (device.trustLevelEntity?.crossSignedVerified != crossSignedVerified) { - Timber.d("## CrossSigning - Trust change detected for ${device.userId}|${device.deviceId} : cross verified: $crossSignedVerified") - // need to save - val trustEntity = device.trustLevelEntity - if (trustEntity == null) { - realm.createObject(TrustLevelEntity::class.java).let { - it.locallyVerified = false - it.crossSignedVerified = crossSignedVerified - device.trustLevelEntity = it - } - } else { - trustEntity.crossSignedVerified = crossSignedVerified + // Update trust if needed + devicesEntities?.forEach { device -> + val crossSignedVerified = trustMap?.get(device)?.isCrossSignedVerified() + Timber.d("## CrossSigning - Trust for ${device.userId}|${device.deviceId} : cross verified: ${trustMap?.get(device)}") + if (device.trustLevelEntity?.crossSignedVerified != crossSignedVerified) { + Timber.d("## CrossSigning - Trust change detected for ${device.userId}|${device.deviceId} : cross verified: $crossSignedVerified") + // need to save + val trustEntity = device.trustLevelEntity + if (trustEntity == null) { + device.trustLevelEntity = cryptoRealm.createObject(TrustLevelEntity::class.java).also { + it.locallyVerified = false + it.crossSignedVerified = crossSignedVerified } + } else { + trustEntity.crossSignedVerified = crossSignedVerified } } } @@ -199,37 +198,51 @@ internal class UpdateTrustWorker(context: Context, // So Cross Signing keys trust is updated, device trust is updated // We can now update room shields? in the session DB? + updateTrustStep2(userList, myCrossSigningInfo) + } + private suspend fun updateTrustStep2(userList: List<String>, myCrossSigningInfo: MXCrossSigningInfo?) { Timber.d("## CrossSigning - Updating shields for impacted rooms...") - Realm.getInstance(sessionRealmConfiguration).use { it -> - it.executeTransaction { realm -> - val distinctRoomIds = realm.where(RoomMemberSummaryEntity::class.java) + awaitTransaction(sessionRealmConfiguration) { sessionRealm -> + Realm.getInstance(cryptoRealmConfiguration).use { cryptoRealm -> + sessionRealm.where(RoomMemberSummaryEntity::class.java) .`in`(RoomMemberSummaryEntityFields.USER_ID, userList.toTypedArray()) .distinct(RoomMemberSummaryEntityFields.ROOM_ID) .findAll() .map { it.roomId } - Timber.d("## CrossSigning - ... impacted rooms $distinctRoomIds") - distinctRoomIds.forEach { roomId -> - val roomSummary = RoomSummaryEntity.where(realm, roomId).findFirst() - if (roomSummary?.isEncrypted == true) { - Timber.d("## CrossSigning - Check shield state for room $roomId") - val allActiveRoomMembers = RoomMemberHelper(realm, roomId).getActiveRoomMemberIds() - try { - val updatedTrust = computeRoomShield(allActiveRoomMembers, roomSummary) - if (roomSummary.roomEncryptionTrustLevel != updatedTrust) { - Timber.d("## CrossSigning - Shield change detected for $roomId -> $updatedTrust") - roomSummary.roomEncryptionTrustLevel = updatedTrust - } - } catch (failure: Throwable) { - Timber.e(failure) + .also { Timber.d("## CrossSigning - ... impacted rooms ${it.logLimit()}") } + .forEach { roomId -> + RoomSummaryEntity.where(sessionRealm, roomId) + .equalTo(RoomSummaryEntityFields.IS_ENCRYPTED, true) + .findFirst() + ?.let { roomSummary -> + Timber.d("## CrossSigning - Check shield state for room $roomId") + val allActiveRoomMembers = RoomMemberHelper(sessionRealm, roomId).getActiveRoomMemberIds() + try { + val updatedTrust = computeRoomShield( + myCrossSigningInfo, + cryptoRealm, + allActiveRoomMembers, + roomSummary + ) + if (roomSummary.roomEncryptionTrustLevel != updatedTrust) { + Timber.d("## CrossSigning - Shield change detected for $roomId -> $updatedTrust") + roomSummary.roomEncryptionTrustLevel = updatedTrust + } + } catch (failure: Throwable) { + Timber.e(failure) + } + } } - } - } } } + } - cleanup(params) - return Result.success() + private fun getCrossSigningInfo(cryptoRealm: Realm, userId: String): MXCrossSigningInfo? { + return cryptoRealm.where(CrossSigningInfoEntity::class.java) + .equalTo(CrossSigningInfoEntityFields.USER_ID, userId) + .findFirst() + ?.let { mapCrossSigningInfoEntity(it) } } private fun cleanup(params: Params) { @@ -237,30 +250,34 @@ internal class UpdateTrustWorker(context: Context, ?.let { updateTrustWorkerDataRepository.delete(it) } } - private fun updateCrossSigningKeysTrust(realm: Realm, userId: String, verified: Boolean) { - val xInfoEntity = realm.where(CrossSigningInfoEntity::class.java) + private fun updateCrossSigningKeysTrust(cryptoRealm: Realm, userId: String, verified: Boolean) { + cryptoRealm.where(CrossSigningInfoEntity::class.java) .equalTo(CrossSigningInfoEntityFields.USER_ID, userId) .findFirst() - xInfoEntity?.crossSigningKeys?.forEach { info -> - // optimization to avoid trigger updates when there is no change.. - if (info.trustLevelEntity?.isVerified() != verified) { - Timber.d("## CrossSigning - Trust change for $userId : $verified") - val level = info.trustLevelEntity - if (level == null) { - val newLevel = realm.createObject(TrustLevelEntity::class.java) - newLevel.locallyVerified = verified - newLevel.crossSignedVerified = verified - info.trustLevelEntity = newLevel - } else { - level.locallyVerified = verified - level.crossSignedVerified = verified + ?.crossSigningKeys + ?.forEach { info -> + // optimization to avoid trigger updates when there is no change.. + if (info.trustLevelEntity?.isVerified() != verified) { + Timber.d("## CrossSigning - Trust change for $userId : $verified") + val level = info.trustLevelEntity + if (level == null) { + info.trustLevelEntity = cryptoRealm.createObject(TrustLevelEntity::class.java).also { + it.locallyVerified = verified + it.crossSignedVerified = verified + } + } else { + level.locallyVerified = verified + level.crossSignedVerified = verified + } + } } - } - } } - private fun computeRoomShield(activeMemberUserIds: List<String>, roomSummaryEntity: RoomSummaryEntity): RoomEncryptionTrustLevel { - Timber.d("## CrossSigning - computeRoomShield ${roomSummaryEntity.roomId} -> $activeMemberUserIds") + private fun computeRoomShield(myCrossSigningInfo: MXCrossSigningInfo?, + cryptoRealm: Realm, + activeMemberUserIds: List<String>, + roomSummaryEntity: RoomSummaryEntity): RoomEncryptionTrustLevel { + Timber.d("## CrossSigning - computeRoomShield ${roomSummaryEntity.roomId} -> ${activeMemberUserIds.logLimit()}") // The set of “all users†depends on the type of room: // For regular / topic rooms which have more than 2 members (including yourself) are considered when decorating a room // For 1:1 and group DM rooms, all other users (i.e. excluding yourself) are considered when decorating a room @@ -272,17 +289,8 @@ internal class UpdateTrustWorker(context: Context, val allTrustedUserIds = listToCheck .filter { userId -> - Realm.getInstance(realmConfiguration).use { - it.where(CrossSigningInfoEntity::class.java) - .equalTo(CrossSigningInfoEntityFields.USER_ID, userId) - .findFirst()?.let { mapCrossSigningInfoEntity(it) }?.isTrusted() == true - } + getCrossSigningInfo(cryptoRealm, userId)?.isTrusted() == true } - val myCrossKeys = Realm.getInstance(realmConfiguration).use { - it.where(CrossSigningInfoEntity::class.java) - .equalTo(CrossSigningInfoEntityFields.USER_ID, myUserId) - .findFirst()?.let { mapCrossSigningInfoEntity(it) } - } return if (allTrustedUserIds.isEmpty()) { RoomEncryptionTrustLevel.Default @@ -291,21 +299,17 @@ internal class UpdateTrustWorker(context: Context, // If all devices of all verified users are trusted -> green // else -> black allTrustedUserIds - .mapNotNull { uid -> - Realm.getInstance(realmConfiguration).use { - it.where<UserEntity>() - .equalTo(UserEntityFields.USER_ID, uid) - .findFirst() - ?.devices - ?.map { - CryptoMapper.mapToModel(it) - } - } + .mapNotNull { userId -> + cryptoRealm.where<UserEntity>() + .equalTo(UserEntityFields.USER_ID, userId) + .findFirst() + ?.devices + ?.map { CryptoMapper.mapToModel(it) } } .flatten() .let { allDevices -> - Timber.v("## CrossSigning - computeRoomShield ${roomSummaryEntity.roomId} devices ${allDevices.map { it.deviceId }}") - if (myCrossKeys != null) { + Timber.v("## CrossSigning - computeRoomShield ${roomSummaryEntity.roomId} devices ${allDevices.map { it.deviceId }.logLimit()}") + if (myCrossSigningInfo != null) { allDevices.any { !it.trustLevel?.crossSigningVerified.orFalse() } } else { // Legacy method diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/api/RoomKeysApi.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/api/RoomKeysApi.kt index 3f8333528f8fbe65dd0389002e5da6a50aafa295..eb4c55a3e7f93343c88b99945d6ec603cd71c285 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/api/RoomKeysApi.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/api/RoomKeysApi.kt @@ -25,7 +25,6 @@ import org.matrix.android.sdk.internal.crypto.keysbackup.model.rest.KeysVersionR import org.matrix.android.sdk.internal.crypto.keysbackup.model.rest.RoomKeysBackupData import org.matrix.android.sdk.internal.crypto.keysbackup.model.rest.UpdateKeysBackupVersionBody import org.matrix.android.sdk.internal.network.NetworkConstants -import retrofit2.Call import retrofit2.http.Body import retrofit2.http.DELETE import retrofit2.http.GET @@ -48,14 +47,14 @@ internal interface RoomKeysApi { * @param createKeysBackupVersionBody the body */ @POST(NetworkConstants.URI_API_PREFIX_PATH_UNSTABLE + "room_keys/version") - fun createKeysBackupVersion(@Body createKeysBackupVersionBody: CreateKeysBackupVersionBody): Call<KeysVersion> + suspend fun createKeysBackupVersion(@Body createKeysBackupVersionBody: CreateKeysBackupVersionBody): KeysVersion /** * Get the key backup last version * If not supported by the server, an error is returned: {"errcode":"M_NOT_FOUND","error":"No backup found"} */ @GET(NetworkConstants.URI_API_PREFIX_PATH_UNSTABLE + "room_keys/version") - fun getKeysBackupLastVersion(): Call<KeysVersionResult> + suspend fun getKeysBackupLastVersion(): KeysVersionResult /** * Get information about the given version. @@ -64,7 +63,7 @@ internal interface RoomKeysApi { * @param version version */ @GET(NetworkConstants.URI_API_PREFIX_PATH_UNSTABLE + "room_keys/version/{version}") - fun getKeysBackupVersion(@Path("version") version: String): Call<KeysVersionResult> + suspend fun getKeysBackupVersion(@Path("version") version: String): KeysVersionResult /** * Update information about the given version. @@ -72,8 +71,8 @@ internal interface RoomKeysApi { * @param updateKeysBackupVersionBody the body */ @PUT(NetworkConstants.URI_API_PREFIX_PATH_UNSTABLE + "room_keys/version/{version}") - fun updateKeysBackupVersion(@Path("version") version: String, - @Body keysBackupVersionBody: UpdateKeysBackupVersionBody): Call<Unit> + suspend fun updateKeysBackupVersion(@Path("version") version: String, + @Body keysBackupVersionBody: UpdateKeysBackupVersionBody) /* ========================================================================================== * Storing keys @@ -94,10 +93,10 @@ internal interface RoomKeysApi { * @param keyBackupData the data to send */ @PUT(NetworkConstants.URI_API_PREFIX_PATH_UNSTABLE + "room_keys/keys/{roomId}/{sessionId}") - fun storeRoomSessionData(@Path("roomId") roomId: String, - @Path("sessionId") sessionId: String, - @Query("version") version: String, - @Body keyBackupData: KeyBackupData): Call<BackupKeysResult> + suspend fun storeRoomSessionData(@Path("roomId") roomId: String, + @Path("sessionId") sessionId: String, + @Query("version") version: String, + @Body keyBackupData: KeyBackupData): BackupKeysResult /** * Store several keys for the given room, using the given backup version. @@ -107,9 +106,9 @@ internal interface RoomKeysApi { * @param roomKeysBackupData the data to send */ @PUT(NetworkConstants.URI_API_PREFIX_PATH_UNSTABLE + "room_keys/keys/{roomId}") - fun storeRoomSessionsData(@Path("roomId") roomId: String, - @Query("version") version: String, - @Body roomKeysBackupData: RoomKeysBackupData): Call<BackupKeysResult> + suspend fun storeRoomSessionsData(@Path("roomId") roomId: String, + @Query("version") version: String, + @Body roomKeysBackupData: RoomKeysBackupData): BackupKeysResult /** * Store several keys, using the given backup version. @@ -118,8 +117,8 @@ internal interface RoomKeysApi { * @param keysBackupData the data to send */ @PUT(NetworkConstants.URI_API_PREFIX_PATH_UNSTABLE + "room_keys/keys") - fun storeSessionsData(@Query("version") version: String, - @Body keysBackupData: KeysBackupData): Call<BackupKeysResult> + suspend fun storeSessionsData(@Query("version") version: String, + @Body keysBackupData: KeysBackupData): BackupKeysResult /* ========================================================================================== * Retrieving keys @@ -133,9 +132,9 @@ internal interface RoomKeysApi { * @param version the version of the backup, or empty String to retrieve the last version */ @GET(NetworkConstants.URI_API_PREFIX_PATH_UNSTABLE + "room_keys/keys/{roomId}/{sessionId}") - fun getRoomSessionData(@Path("roomId") roomId: String, - @Path("sessionId") sessionId: String, - @Query("version") version: String): Call<KeyBackupData> + suspend fun getRoomSessionData(@Path("roomId") roomId: String, + @Path("sessionId") sessionId: String, + @Query("version") version: String): KeyBackupData /** * Retrieve all the keys for the given room from the backup. @@ -144,8 +143,8 @@ internal interface RoomKeysApi { * @param version the version of the backup, or empty String to retrieve the last version */ @GET(NetworkConstants.URI_API_PREFIX_PATH_UNSTABLE + "room_keys/keys/{roomId}") - fun getRoomSessionsData(@Path("roomId") roomId: String, - @Query("version") version: String): Call<RoomKeysBackupData> + suspend fun getRoomSessionsData(@Path("roomId") roomId: String, + @Query("version") version: String): RoomKeysBackupData /** * Retrieve all the keys from the backup. @@ -153,7 +152,7 @@ internal interface RoomKeysApi { * @param version the version of the backup, or empty String to retrieve the last version */ @GET(NetworkConstants.URI_API_PREFIX_PATH_UNSTABLE + "room_keys/keys") - fun getSessionsData(@Query("version") version: String): Call<KeysBackupData> + suspend fun getSessionsData(@Query("version") version: String): KeysBackupData /* ========================================================================================== * Deleting keys @@ -163,22 +162,22 @@ internal interface RoomKeysApi { * Deletes keys from the backup. */ @DELETE(NetworkConstants.URI_API_PREFIX_PATH_UNSTABLE + "room_keys/keys/{roomId}/{sessionId}") - fun deleteRoomSessionData(@Path("roomId") roomId: String, - @Path("sessionId") sessionId: String, - @Query("version") version: String): Call<Unit> + suspend fun deleteRoomSessionData(@Path("roomId") roomId: String, + @Path("sessionId") sessionId: String, + @Query("version") version: String) /** * Deletes keys from the backup. */ @DELETE(NetworkConstants.URI_API_PREFIX_PATH_UNSTABLE + "room_keys/keys/{roomId}") - fun deleteRoomSessionsData(@Path("roomId") roomId: String, - @Query("version") version: String): Call<Unit> + suspend fun deleteRoomSessionsData(@Path("roomId") roomId: String, + @Query("version") version: String) /** * Deletes keys from the backup. */ @DELETE(NetworkConstants.URI_API_PREFIX_PATH_UNSTABLE + "room_keys/keys") - fun deleteSessionsData(@Query("version") version: String): Call<Unit> + suspend fun deleteSessionsData(@Query("version") version: String) /* ========================================================================================== * Deleting backup @@ -188,5 +187,5 @@ internal interface RoomKeysApi { * Deletes a backup. */ @DELETE(NetworkConstants.URI_API_PREFIX_PATH_UNSTABLE + "room_keys/version/{version}") - fun deleteBackup(@Path("version") version: String): Call<Unit> + suspend fun deleteBackup(@Path("version") version: String) } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/tasks/CreateKeysBackupVersionTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/tasks/CreateKeysBackupVersionTask.kt index 5c59cfd80e3215ae47e104f15e03548bc02bd6c5..62610a0b7bd686783346e62b1d6848b72bd54366 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/tasks/CreateKeysBackupVersionTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/tasks/CreateKeysBackupVersionTask.kt @@ -33,7 +33,7 @@ internal class DefaultCreateKeysBackupVersionTask @Inject constructor( override suspend fun execute(params: CreateKeysBackupVersionBody): KeysVersion { return executeRequest(globalErrorReceiver) { - apiCall = roomKeysApi.createKeysBackupVersion(params) + roomKeysApi.createKeysBackupVersion(params) } } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/tasks/DeleteBackupTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/tasks/DeleteBackupTask.kt index ec09da7240cc48a97116fa0cb71ea191613253c8..7ee6f2358d3edecc445b69ba24eb9ac4b826b18a 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/tasks/DeleteBackupTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/tasks/DeleteBackupTask.kt @@ -35,7 +35,7 @@ internal class DefaultDeleteBackupTask @Inject constructor( override suspend fun execute(params: DeleteBackupTask.Params) { return executeRequest(globalErrorReceiver) { - apiCall = roomKeysApi.deleteBackup(params.version) + roomKeysApi.deleteBackup(params.version) } } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/tasks/DeleteRoomSessionDataTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/tasks/DeleteRoomSessionDataTask.kt index 9c477efb7821e6af51e2bca87fec6621b545d267..7f1b03b9324a0ad8e898bd0827589e5d0420e9a3 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/tasks/DeleteRoomSessionDataTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/tasks/DeleteRoomSessionDataTask.kt @@ -37,7 +37,7 @@ internal class DefaultDeleteRoomSessionDataTask @Inject constructor( override suspend fun execute(params: DeleteRoomSessionDataTask.Params) { return executeRequest(globalErrorReceiver) { - apiCall = roomKeysApi.deleteRoomSessionData( + roomKeysApi.deleteRoomSessionData( params.roomId, params.sessionId, params.version) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/tasks/DeleteRoomSessionsDataTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/tasks/DeleteRoomSessionsDataTask.kt index 82d022f3abdb90478cfa620567bc5643721eb9d8..394cc861d625008d44c8a6e8ca19e1f928dda3ea 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/tasks/DeleteRoomSessionsDataTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/tasks/DeleteRoomSessionsDataTask.kt @@ -36,7 +36,7 @@ internal class DefaultDeleteRoomSessionsDataTask @Inject constructor( override suspend fun execute(params: DeleteRoomSessionsDataTask.Params) { return executeRequest(globalErrorReceiver) { - apiCall = roomKeysApi.deleteRoomSessionsData( + roomKeysApi.deleteRoomSessionsData( params.roomId, params.version) } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/tasks/DeleteSessionsDataTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/tasks/DeleteSessionsDataTask.kt index e4df379963db0a2864d13efba815acc3c6c4e303..808c6c9956d4c0396bd8203baf4c092cd3ef074b 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/tasks/DeleteSessionsDataTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/tasks/DeleteSessionsDataTask.kt @@ -35,7 +35,7 @@ internal class DefaultDeleteSessionsDataTask @Inject constructor( override suspend fun execute(params: DeleteSessionsDataTask.Params) { return executeRequest(globalErrorReceiver) { - apiCall = roomKeysApi.deleteSessionsData(params.version) + roomKeysApi.deleteSessionsData(params.version) } } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/tasks/GetKeysBackupLastVersionTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/tasks/GetKeysBackupLastVersionTask.kt index 3566ff0e6856832ac3346b81946e7016a512744a..54dbf85e302730784737b23b97bf0c23f0ab7964 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/tasks/GetKeysBackupLastVersionTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/tasks/GetKeysBackupLastVersionTask.kt @@ -32,7 +32,7 @@ internal class DefaultGetKeysBackupLastVersionTask @Inject constructor( override suspend fun execute(params: Unit): KeysVersionResult { return executeRequest(globalErrorReceiver) { - apiCall = roomKeysApi.getKeysBackupLastVersion() + roomKeysApi.getKeysBackupLastVersion() } } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/tasks/GetKeysBackupVersionTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/tasks/GetKeysBackupVersionTask.kt index 13c99fb0f4235a1278fdb23129e778e428b78858..390873eb68dc249eef0f1628b6299339c6591b68 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/tasks/GetKeysBackupVersionTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/tasks/GetKeysBackupVersionTask.kt @@ -32,7 +32,7 @@ internal class DefaultGetKeysBackupVersionTask @Inject constructor( override suspend fun execute(params: String): KeysVersionResult { return executeRequest(globalErrorReceiver) { - apiCall = roomKeysApi.getKeysBackupVersion(params) + roomKeysApi.getKeysBackupVersion(params) } } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/tasks/GetRoomSessionDataTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/tasks/GetRoomSessionDataTask.kt index 168020d9cd233459645b4860a1a7887bd3fc682e..ff515ed80fa34df7256e639be9c4cdda94edde84 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/tasks/GetRoomSessionDataTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/tasks/GetRoomSessionDataTask.kt @@ -38,7 +38,7 @@ internal class DefaultGetRoomSessionDataTask @Inject constructor( override suspend fun execute(params: GetRoomSessionDataTask.Params): KeyBackupData { return executeRequest(globalErrorReceiver) { - apiCall = roomKeysApi.getRoomSessionData( + roomKeysApi.getRoomSessionData( params.roomId, params.sessionId, params.version) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/tasks/GetRoomSessionsDataTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/tasks/GetRoomSessionsDataTask.kt index 95d5ef2e53ffdee71a311236ba69ba81baa398d4..1b4fe2d96622719d80b6becd3d489b749312c2f5 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/tasks/GetRoomSessionsDataTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/tasks/GetRoomSessionsDataTask.kt @@ -37,7 +37,7 @@ internal class DefaultGetRoomSessionsDataTask @Inject constructor( override suspend fun execute(params: GetRoomSessionsDataTask.Params): RoomKeysBackupData { return executeRequest(globalErrorReceiver) { - apiCall = roomKeysApi.getRoomSessionsData( + roomKeysApi.getRoomSessionsData( params.roomId, params.version) } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/tasks/GetSessionsDataTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/tasks/GetSessionsDataTask.kt index e41a13e3eb258df56fe922c83794642020ab1c98..707125f4cda68328ad1976562a7e3e0e7560dac8 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/tasks/GetSessionsDataTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/tasks/GetSessionsDataTask.kt @@ -36,7 +36,7 @@ internal class DefaultGetSessionsDataTask @Inject constructor( override suspend fun execute(params: GetSessionsDataTask.Params): KeysBackupData { return executeRequest(globalErrorReceiver) { - apiCall = roomKeysApi.getSessionsData(params.version) + roomKeysApi.getSessionsData(params.version) } } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/tasks/StoreRoomSessionDataTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/tasks/StoreRoomSessionDataTask.kt index 3954277e3925d75c88d90b31697aa2c5c4d50080..180aaecf82000b020b7c67d78bfc69357ebeae5b 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/tasks/StoreRoomSessionDataTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/tasks/StoreRoomSessionDataTask.kt @@ -40,7 +40,7 @@ internal class DefaultStoreRoomSessionDataTask @Inject constructor( override suspend fun execute(params: StoreRoomSessionDataTask.Params): BackupKeysResult { return executeRequest(globalErrorReceiver) { - apiCall = roomKeysApi.storeRoomSessionData( + roomKeysApi.storeRoomSessionData( params.roomId, params.sessionId, params.version, diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/tasks/StoreRoomSessionsDataTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/tasks/StoreRoomSessionsDataTask.kt index 4e209b4abc4f6eb0d9aca677cd9803be1c8c5112..d1aa9d2eb03831701c3c71a0d5cdefa8814b24ba 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/tasks/StoreRoomSessionsDataTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/tasks/StoreRoomSessionsDataTask.kt @@ -39,7 +39,7 @@ internal class DefaultStoreRoomSessionsDataTask @Inject constructor( override suspend fun execute(params: StoreRoomSessionsDataTask.Params): BackupKeysResult { return executeRequest(globalErrorReceiver) { - apiCall = roomKeysApi.storeRoomSessionsData( + roomKeysApi.storeRoomSessionsData( params.roomId, params.version, params.roomKeysBackupData) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/tasks/StoreSessionsDataTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/tasks/StoreSessionsDataTask.kt index a607477d21712a6369428d2927cf236b4cc342d0..3dbeafe9dee2cb866e98c33b04e762fd5713119c 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/tasks/StoreSessionsDataTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/tasks/StoreSessionsDataTask.kt @@ -38,7 +38,7 @@ internal class DefaultStoreSessionsDataTask @Inject constructor( override suspend fun execute(params: StoreSessionsDataTask.Params): BackupKeysResult { return executeRequest(globalErrorReceiver) { - apiCall = roomKeysApi.storeSessionsData( + roomKeysApi.storeSessionsData( params.version, params.keysBackupData) } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/tasks/UpdateKeysBackupVersionTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/tasks/UpdateKeysBackupVersionTask.kt index f012cd13ebd48ce1e1dda4d19b6988a93ddf905a..2b3d044ab796e4573f50eebbe0627cfda4e7fc28 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/tasks/UpdateKeysBackupVersionTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/tasks/UpdateKeysBackupVersionTask.kt @@ -37,7 +37,7 @@ internal class DefaultUpdateKeysBackupVersionTask @Inject constructor( override suspend fun execute(params: UpdateKeysBackupVersionTask.Params) { return executeRequest(globalErrorReceiver) { - apiCall = roomKeysApi.updateKeysBackupVersion(params.version, params.keysBackupVersionBody) + roomKeysApi.updateKeysBackupVersion(params.version, params.keysBackupVersionBody) } } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/secrets/DefaultSharedSecretStorageService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/secrets/DefaultSharedSecretStorageService.kt index 82b5185fe8d9e8d6693b61e96972b2c974ec74cd..1f80ce2c812ab1b359fad9dd355c7a252060534e 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/secrets/DefaultSharedSecretStorageService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/secrets/DefaultSharedSecretStorageService.kt @@ -16,7 +16,6 @@ package org.matrix.android.sdk.internal.crypto.secrets -import org.matrix.android.sdk.api.MatrixCallback import org.matrix.android.sdk.api.extensions.orFalse import org.matrix.android.sdk.api.listeners.ProgressListener import org.matrix.android.sdk.api.session.accountdata.AccountDataService @@ -43,10 +42,9 @@ import org.matrix.android.sdk.internal.crypto.keysbackup.util.computeRecoveryKey import org.matrix.android.sdk.internal.crypto.tools.HkdfSha256 import org.matrix.android.sdk.internal.crypto.tools.withOlmDecryption import org.matrix.android.sdk.internal.di.UserId -import org.matrix.android.sdk.internal.extensions.foldToCallback import org.matrix.android.sdk.internal.util.MatrixCoroutineDispatchers import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext import org.matrix.olm.OlmPkMessage import java.security.SecureRandom import javax.crypto.Cipher @@ -64,21 +62,15 @@ internal class DefaultSharedSecretStorageService @Inject constructor( private val cryptoCoroutineScope: CoroutineScope ) : SharedSecretStorageService { - override fun generateKey(keyId: String, - key: SsssKeySpec?, - keyName: String, - keySigner: KeySigner?, - callback: MatrixCallback<SsssKeyCreationInfo>) { - cryptoCoroutineScope.launch(coroutineDispatchers.main) { - val bytes = try { - (key as? RawBytesKeySpec)?.privateKey - ?: ByteArray(32).also { - SecureRandom().nextBytes(it) - } - } catch (failure: Throwable) { - callback.onFailure(failure) - return@launch - } + override suspend fun generateKey(keyId: String, + key: SsssKeySpec?, + keyName: String, + keySigner: KeySigner?): SsssKeyCreationInfo { + return withContext(cryptoCoroutineScope.coroutineContext + coroutineDispatchers.main) { + val bytes = (key as? RawBytesKeySpec)?.privateKey + ?: ByteArray(32).also { + SecureRandom().nextBytes(it) + } val storageKeyContent = SecretStorageKeyContent( name = keyName, @@ -92,34 +84,22 @@ internal class DefaultSharedSecretStorageService @Inject constructor( ) } ?: storageKeyContent - accountDataService.updateAccountData( - "$KEY_ID_BASE.$keyId", - signedContent.toContent(), - object : MatrixCallback<Unit> { - override fun onFailure(failure: Throwable) { - callback.onFailure(failure) - } - - override fun onSuccess(data: Unit) { - callback.onSuccess(SsssKeyCreationInfo( - keyId = keyId, - content = storageKeyContent, - recoveryKey = computeRecoveryKey(bytes), - keySpec = RawBytesKeySpec(bytes) - )) - } - } + accountDataService.updateAccountData("$KEY_ID_BASE.$keyId", signedContent.toContent()) + SsssKeyCreationInfo( + keyId = keyId, + content = storageKeyContent, + recoveryKey = computeRecoveryKey(bytes), + keySpec = RawBytesKeySpec(bytes) ) } } - override fun generateKeyWithPassphrase(keyId: String, - keyName: String, - passphrase: String, - keySigner: KeySigner, - progressListener: ProgressListener?, - callback: MatrixCallback<SsssKeyCreationInfo>) { - cryptoCoroutineScope.launch(coroutineDispatchers.main) { + override suspend fun generateKeyWithPassphrase(keyId: String, + keyName: String, + passphrase: String, + keySigner: KeySigner, + progressListener: ProgressListener?): SsssKeyCreationInfo { + return withContext(cryptoCoroutineScope.coroutineContext + coroutineDispatchers.main) { val privatePart = generatePrivateKeyWithPassword(passphrase, progressListener) val storageKeyContent = SecretStorageKeyContent( @@ -135,21 +115,13 @@ internal class DefaultSharedSecretStorageService @Inject constructor( accountDataService.updateAccountData( "$KEY_ID_BASE.$keyId", - signedContent.toContent(), - object : MatrixCallback<Unit> { - override fun onFailure(failure: Throwable) { - callback.onFailure(failure) - } - - override fun onSuccess(data: Unit) { - callback.onSuccess(SsssKeyCreationInfo( - keyId = keyId, - content = storageKeyContent, - recoveryKey = computeRecoveryKey(privatePart.privateKey), - keySpec = RawBytesKeySpec(privatePart.privateKey) - )) - } - } + signedContent.toContent() + ) + SsssKeyCreationInfo( + keyId = keyId, + content = storageKeyContent, + recoveryKey = computeRecoveryKey(privatePart.privateKey), + keySpec = RawBytesKeySpec(privatePart.privateKey) ) } } @@ -168,15 +140,12 @@ internal class DefaultSharedSecretStorageService @Inject constructor( } ?: KeyInfoResult.Error(SharedSecretStorageError.UnknownAlgorithm(keyId)) } - override fun setDefaultKey(keyId: String, callback: MatrixCallback<Unit>) { + override suspend fun setDefaultKey(keyId: String) { val existingKey = getKey(keyId) if (existingKey is KeyInfoResult.Success) { - accountDataService.updateAccountData(DEFAULT_KEY_ID, - mapOf("key" to keyId), - callback - ) + accountDataService.updateAccountData(DEFAULT_KEY_ID, mapOf("key" to keyId)) } else { - callback.onFailure(SharedSecretStorageError.UnknownKey(keyId)) + throw SharedSecretStorageError.UnknownKey(keyId) } } @@ -188,42 +157,31 @@ internal class DefaultSharedSecretStorageService @Inject constructor( return getKey(keyId) } - override fun storeSecret(name: String, secretBase64: String, keys: List<SharedSecretStorageService.KeyRef>, callback: MatrixCallback<Unit>) { - cryptoCoroutineScope.launch(coroutineDispatchers.main) { + override suspend fun storeSecret(name: String, secretBase64: String, keys: List<SharedSecretStorageService.KeyRef>) { + withContext(cryptoCoroutineScope.coroutineContext + coroutineDispatchers.main) { val encryptedContents = HashMap<String, EncryptedSecretContent>() - try { - keys.forEach { - val keyId = it.keyId - // encrypt the content - when (val key = keyId?.let { getKey(keyId) } ?: getDefaultKey()) { - is KeyInfoResult.Success -> { - if (key.keyInfo.content.algorithm == SSSS_ALGORITHM_AES_HMAC_SHA2) { - encryptAesHmacSha2(it.keySpec!!, name, secretBase64).let { - encryptedContents[key.keyInfo.id] = it - } - } else { - // Unknown algorithm - callback.onFailure(SharedSecretStorageError.UnknownAlgorithm(key.keyInfo.content.algorithm ?: "")) - return@launch + keys.forEach { + val keyId = it.keyId + // encrypt the content + when (val key = keyId?.let { getKey(keyId) } ?: getDefaultKey()) { + is KeyInfoResult.Success -> { + if (key.keyInfo.content.algorithm == SSSS_ALGORITHM_AES_HMAC_SHA2) { + encryptAesHmacSha2(it.keySpec!!, name, secretBase64).let { + encryptedContents[key.keyInfo.id] = it } - } - is KeyInfoResult.Error -> { - callback.onFailure(key.error) - return@launch + } else { + // Unknown algorithm + throw SharedSecretStorageError.UnknownAlgorithm(key.keyInfo.content.algorithm ?: "") } } + is KeyInfoResult.Error -> throw key.error } - - accountDataService.updateAccountData( - type = name, - content = mapOf( - "encrypted" to encryptedContents - ), - callback = callback - ) - } catch (failure: Throwable) { - callback.onFailure(failure) } + + accountDataService.updateAccountData( + type = name, + content = mapOf("encrypted" to encryptedContents) + ) } } @@ -344,57 +302,40 @@ internal class DefaultSharedSecretStorageService @Inject constructor( return results } - override fun getSecret(name: String, keyId: String?, secretKey: SsssKeySpec, callback: MatrixCallback<String>) { - val accountData = accountDataService.getAccountDataEvent(name) ?: return Unit.also { - callback.onFailure(SharedSecretStorageError.UnknownSecret(name)) - } - val encryptedContent = accountData.content[ENCRYPTED] as? Map<*, *> ?: return Unit.also { - callback.onFailure(SharedSecretStorageError.SecretNotEncrypted(name)) - } - val key = keyId?.let { getKey(it) } as? KeyInfoResult.Success ?: getDefaultKey() as? KeyInfoResult.Success ?: return Unit.also { - callback.onFailure(SharedSecretStorageError.UnknownKey(name)) - } + override suspend fun getSecret(name: String, keyId: String?, secretKey: SsssKeySpec): String { + val accountData = accountDataService.getAccountDataEvent(name) ?: throw SharedSecretStorageError.UnknownSecret(name) + val encryptedContent = accountData.content[ENCRYPTED] as? Map<*, *> ?: throw SharedSecretStorageError.SecretNotEncrypted(name) + val key = keyId?.let { getKey(it) } as? KeyInfoResult.Success ?: getDefaultKey() as? KeyInfoResult.Success + ?: throw SharedSecretStorageError.UnknownKey(name) - val encryptedForKey = encryptedContent[key.keyInfo.id] ?: return Unit.also { - callback.onFailure(SharedSecretStorageError.SecretNotEncryptedWithKey(name, key.keyInfo.id)) - } + val encryptedForKey = encryptedContent[key.keyInfo.id] ?: throw SharedSecretStorageError.SecretNotEncryptedWithKey(name, key.keyInfo.id) val secretContent = EncryptedSecretContent.fromJson(encryptedForKey) - ?: return Unit.also { - callback.onFailure(SharedSecretStorageError.ParsingError) - } + ?: throw SharedSecretStorageError.ParsingError val algorithm = key.keyInfo.content if (SSSS_ALGORITHM_CURVE25519_AES_SHA2 == algorithm.algorithm) { - val keySpec = secretKey as? RawBytesKeySpec ?: return Unit.also { - callback.onFailure(SharedSecretStorageError.BadKeyFormat) - } - cryptoCoroutineScope.launch(coroutineDispatchers.main) { - runCatching { - // decrypt from recovery key - withOlmDecryption { olmPkDecryption -> - olmPkDecryption.setPrivateKey(keySpec.privateKey) - olmPkDecryption.decrypt(OlmPkMessage() - .apply { - mCipherText = secretContent.ciphertext - mEphemeralKey = secretContent.ephemeral - mMac = secretContent.mac - } - ) - } - }.foldToCallback(callback) + val keySpec = secretKey as? RawBytesKeySpec ?: throw SharedSecretStorageError.BadKeyFormat + return withContext(cryptoCoroutineScope.coroutineContext + coroutineDispatchers.main) { + // decrypt from recovery key + withOlmDecryption { olmPkDecryption -> + olmPkDecryption.setPrivateKey(keySpec.privateKey) + olmPkDecryption.decrypt(OlmPkMessage() + .apply { + mCipherText = secretContent.ciphertext + mEphemeralKey = secretContent.ephemeral + mMac = secretContent.mac + } + ) + } } } else if (SSSS_ALGORITHM_AES_HMAC_SHA2 == algorithm.algorithm) { - val keySpec = secretKey as? RawBytesKeySpec ?: return Unit.also { - callback.onFailure(SharedSecretStorageError.BadKeyFormat) - } - cryptoCoroutineScope.launch(coroutineDispatchers.main) { - runCatching { - decryptAesHmacSha2(keySpec, name, secretContent) - }.foldToCallback(callback) + val keySpec = secretKey as? RawBytesKeySpec ?: throw SharedSecretStorageError.BadKeyFormat + return withContext(cryptoCoroutineScope.coroutineContext + coroutineDispatchers.main) { + decryptAesHmacSha2(keySpec, name, secretContent) } } else { - callback.onFailure(SharedSecretStorageError.UnsupportedAlgorithm(algorithm.algorithm ?: "")) + throw SharedSecretStorageError.UnsupportedAlgorithm(algorithm.algorithm ?: "") } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/IMXCryptoStore.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/IMXCryptoStore.kt index 6f1487f80a46da8694bff3ea046b2dab37fe6709..181bd94cc7e832718a8aa3595108c03e092040cb 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/IMXCryptoStore.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/IMXCryptoStore.kt @@ -259,7 +259,7 @@ internal interface IMXCryptoStore { * @param deviceKey the public key of the other device. * @return A set of sessionId, or null if device is not known */ - fun getDeviceSessionIds(deviceKey: String): Set<String>? + fun getDeviceSessionIds(deviceKey: String): List<String>? /** * Retrieve an end-to-end session between the logged-in user and another diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/RealmCryptoStore.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/RealmCryptoStore.kt index b9213ba758297549950996c724cdfb279e5f87bb..9ae93d61eb88be0b71cd6aeedf42a058ff123ff3 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/RealmCryptoStore.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/RealmCryptoStore.kt @@ -692,7 +692,7 @@ internal class RealmCryptoStore @Inject constructor( } } - override fun getDeviceSessionIds(deviceKey: String): MutableSet<String> { + override fun getDeviceSessionIds(deviceKey: String): List<String> { return doWithRealm(realmConfiguration) { it.where<OlmSessionEntity>() .equalTo(OlmSessionEntityFields.DEVICE_KEY, deviceKey) @@ -701,7 +701,6 @@ internal class RealmCryptoStore @Inject constructor( sessionEntity.sessionId } } - .toMutableSet() } override fun storeInboundGroupSessions(sessions: List<OlmInboundGroupSessionWrapper2>) { @@ -801,7 +800,7 @@ internal class RealmCryptoStore @Inject constructor( * Note: the result will be only use to export all the keys and not to use the OlmInboundGroupSessionWrapper2, * so there is no need to use or update `inboundGroupSessionToRelease` for native memory management */ - override fun getInboundGroupSessions(): MutableList<OlmInboundGroupSessionWrapper2> { + override fun getInboundGroupSessions(): List<OlmInboundGroupSessionWrapper2> { return doWithRealm(realmConfiguration) { it.where<OlmInboundGroupSessionEntity>() .findAll() @@ -809,7 +808,6 @@ internal class RealmCryptoStore @Inject constructor( inboundGroupSessionEntity.getInboundGroupSession() } } - .toMutableList() } override fun removeInboundGroupSession(sessionId: String, senderKey: String) { @@ -964,7 +962,7 @@ internal class RealmCryptoStore @Inject constructor( } } - override fun getRoomsListBlacklistUnverifiedDevices(): MutableList<String> { + override fun getRoomsListBlacklistUnverifiedDevices(): List<String> { return doWithRealm(realmConfiguration) { it.where<CryptoRoomEntity>() .equalTo(CryptoRoomEntityFields.BLACKLIST_UNVERIFIED_DEVICES, true) @@ -973,10 +971,9 @@ internal class RealmCryptoStore @Inject constructor( cryptoRoom.roomId } } - .toMutableList() } - override fun getDeviceTrackingStatuses(): MutableMap<String, Int> { + override fun getDeviceTrackingStatuses(): Map<String, Int> { return doWithRealm(realmConfiguration) { it.where<UserEntity>() .findAll() @@ -987,7 +984,6 @@ internal class RealmCryptoStore @Inject constructor( entry.value.deviceTrackingStatus } } - .toMutableMap() } override fun saveDeviceTrackingStatuses(deviceTrackingStatuses: Map<String, Int>) { diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/mapper/CrossSigningKeysMapper.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/mapper/CrossSigningKeysMapper.kt index 8e6143f3010b00bc67811c774a0aa6bc3e291210..9e739855927de9baa03ddd3477d2634ddd2cdc1f 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/mapper/CrossSigningKeysMapper.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/mapper/CrossSigningKeysMapper.kt @@ -45,7 +45,7 @@ internal class CrossSigningKeysMapper @Inject constructor(moshi: Moshi) { return CryptoCrossSigningKey( userId = userId ?: "", keys = mapOf("ed25519:$pubKey" to pubKey), - usages = keyInfo.usages.map { it }, + usages = keyInfo.usages.toList(), signatures = deserializeSignatures(keyInfo.signatures), trustLevel = keyInfo.trustLevelEntity?.let { DeviceTrustLevel( diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/tasks/ClaimOneTimeKeysForUsersDeviceTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/tasks/ClaimOneTimeKeysForUsersDeviceTask.kt index 3df6312adb63663c9f12e12f5579d9aed0d71812..d5cf749db738376f4a486f753fdd1da0123a65f1 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/tasks/ClaimOneTimeKeysForUsersDeviceTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/tasks/ClaimOneTimeKeysForUsersDeviceTask.kt @@ -20,7 +20,6 @@ import org.matrix.android.sdk.internal.crypto.api.CryptoApi import org.matrix.android.sdk.internal.crypto.model.MXKey import org.matrix.android.sdk.internal.crypto.model.MXUsersDevicesMap import org.matrix.android.sdk.internal.crypto.model.rest.KeysClaimBody -import org.matrix.android.sdk.internal.crypto.model.rest.KeysClaimResponse import org.matrix.android.sdk.internal.network.GlobalErrorReceiver import org.matrix.android.sdk.internal.network.executeRequest import org.matrix.android.sdk.internal.task.Task @@ -42,8 +41,8 @@ internal class DefaultClaimOneTimeKeysForUsersDevice @Inject constructor( override suspend fun execute(params: ClaimOneTimeKeysForUsersDeviceTask.Params): MXUsersDevicesMap<MXKey> { val body = KeysClaimBody(oneTimeKeys = params.usersDevicesKeyTypesMap.map) - val keysClaimResponse = executeRequest<KeysClaimResponse>(globalErrorReceiver) { - apiCall = cryptoApi.claimOneTimeKeysForUsersDevices(body) + val keysClaimResponse = executeRequest(globalErrorReceiver) { + cryptoApi.claimOneTimeKeysForUsersDevices(body) } val map = MXUsersDevicesMap<MXKey>() keysClaimResponse.oneTimeKeys?.let { oneTimeKeys -> diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/tasks/DeleteDeviceTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/tasks/DeleteDeviceTask.kt index 61596bb5b6a58c30c32160d413dee4175ca55e3a..bdb8e8d137a99b0054d2a611999ab47fd994a536 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/tasks/DeleteDeviceTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/tasks/DeleteDeviceTask.kt @@ -42,8 +42,8 @@ internal class DefaultDeleteDeviceTask @Inject constructor( override suspend fun execute(params: DeleteDeviceTask.Params) { try { - executeRequest<Unit>(globalErrorReceiver) { - apiCall = cryptoApi.deleteDevice(params.deviceId, DeleteDeviceParams(params.userAuthParam?.asMap())) + executeRequest(globalErrorReceiver) { + cryptoApi.deleteDevice(params.deviceId, DeleteDeviceParams(params.userAuthParam?.asMap())) } } catch (throwable: Throwable) { if (params.userInteractiveAuthInterceptor == null diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/tasks/DownloadKeysForUsersTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/tasks/DownloadKeysForUsersTask.kt index 5eb24b116ad00b2073ff065d046ef92c915b34df..86f02866ae0850440b4906e5bbd8a4b982d0b652 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/tasks/DownloadKeysForUsersTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/tasks/DownloadKeysForUsersTask.kt @@ -16,17 +16,25 @@ package org.matrix.android.sdk.internal.crypto.tasks +import kotlinx.coroutines.async +import kotlinx.coroutines.coroutineScope +import kotlinx.coroutines.joinAll +import kotlinx.coroutines.sync.Mutex +import kotlinx.coroutines.sync.withLock import org.matrix.android.sdk.internal.crypto.api.CryptoApi +import org.matrix.android.sdk.internal.crypto.model.rest.DeviceKeysWithUnsigned import org.matrix.android.sdk.internal.crypto.model.rest.KeysQueryBody import org.matrix.android.sdk.internal.crypto.model.rest.KeysQueryResponse +import org.matrix.android.sdk.internal.crypto.model.rest.RestKeyInfo import org.matrix.android.sdk.internal.network.GlobalErrorReceiver import org.matrix.android.sdk.internal.network.executeRequest import org.matrix.android.sdk.internal.task.Task +import org.matrix.android.sdk.internal.util.computeBestChunkSize import javax.inject.Inject internal interface DownloadKeysForUsersTask : Task<DownloadKeysForUsersTask.Params, KeysQueryResponse> { data class Params( - // the list of users to get keys for. + // the list of users to get keys for. The list MUST NOT be empty val userIds: List<String>, // the up-to token val token: String? @@ -39,15 +47,68 @@ internal class DefaultDownloadKeysForUsers @Inject constructor( ) : DownloadKeysForUsersTask { override suspend fun execute(params: DownloadKeysForUsersTask.Params): KeysQueryResponse { - val downloadQuery = params.userIds.associateWith { emptyList<String>() } + val bestChunkSize = computeBestChunkSize(params.userIds.size, LIMIT) + val token = params.token?.takeIf { token -> token.isNotEmpty() } - val body = KeysQueryBody( - deviceKeys = downloadQuery, - token = params.token?.takeIf { it.isNotEmpty() } - ) + return if (bestChunkSize.shouldChunk()) { + // Store server results in these mutable maps + val deviceKeys = mutableMapOf<String, Map<String, DeviceKeysWithUnsigned>>() + val failures = mutableMapOf<String, Map<String, Any>>() + val masterKeys = mutableMapOf<String, RestKeyInfo?>() + val selfSigningKeys = mutableMapOf<String, RestKeyInfo?>() + val userSigningKeys = mutableMapOf<String, RestKeyInfo?>() - return executeRequest(globalErrorReceiver) { - apiCall = cryptoApi.downloadKeysForUsers(body) + val mutex = Mutex() + + // Split network request into smaller request (#2925) + coroutineScope { + params.userIds + .chunked(bestChunkSize.chunkSize) + .map { + KeysQueryBody( + deviceKeys = it.associateWith { emptyList() }, + token = token + ) + } + .map { body -> + async { + val result = executeRequest(globalErrorReceiver) { + cryptoApi.downloadKeysForUsers(body) + } + + mutex.withLock { + deviceKeys.putAll(result.deviceKeys.orEmpty()) + failures.putAll(result.failures.orEmpty()) + masterKeys.putAll(result.masterKeys.orEmpty()) + selfSigningKeys.putAll(result.selfSigningKeys.orEmpty()) + userSigningKeys.putAll(result.userSigningKeys.orEmpty()) + } + } + } + .joinAll() + } + + KeysQueryResponse( + deviceKeys = deviceKeys, + failures = failures, + masterKeys = masterKeys, + selfSigningKeys = selfSigningKeys, + userSigningKeys = userSigningKeys + ) + } else { + // No need to chunk, direct request + executeRequest(globalErrorReceiver) { + cryptoApi.downloadKeysForUsers( + KeysQueryBody( + deviceKeys = params.userIds.associateWith { emptyList() }, + token = token + ) + ) + } } } + + companion object { + const val LIMIT = 250 + } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/tasks/GetDeviceInfoTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/tasks/GetDeviceInfoTask.kt index 5f6d2e344fc30871cc4f89ec61b863e51d10509f..9f20ea598de6331d3d46fa2972d896232a413a3b 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/tasks/GetDeviceInfoTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/tasks/GetDeviceInfoTask.kt @@ -34,7 +34,7 @@ internal class DefaultGetDeviceInfoTask @Inject constructor( override suspend fun execute(params: GetDeviceInfoTask.Params): DeviceInfo { return executeRequest(globalErrorReceiver) { - apiCall = cryptoApi.getDeviceInfo(params.deviceId) + cryptoApi.getDeviceInfo(params.deviceId) } } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/tasks/GetDevicesTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/tasks/GetDevicesTask.kt index ea33a918bcf9ec91916a0335bdd6234ba0611b5f..52f9f732994ca52a96903bf6bca735f1a6a56216 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/tasks/GetDevicesTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/tasks/GetDevicesTask.kt @@ -32,7 +32,7 @@ internal class DefaultGetDevicesTask @Inject constructor( override suspend fun execute(params: Unit): DevicesListResponse { return executeRequest(globalErrorReceiver) { - apiCall = cryptoApi.getDevices() + cryptoApi.getDevices() } } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/tasks/GetKeyChangesTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/tasks/GetKeyChangesTask.kt index 4cc9ab2fcb3cec2b27f38297f33463654a86611d..6e524c7fbe487dac00fe52c266f3e0dab84fdc74 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/tasks/GetKeyChangesTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/tasks/GetKeyChangesTask.kt @@ -39,7 +39,7 @@ internal class DefaultGetKeyChangesTask @Inject constructor( override suspend fun execute(params: GetKeyChangesTask.Params): KeyChangesResponse { return executeRequest(globalErrorReceiver) { - apiCall = cryptoApi.getKeyChanges(params.from, params.to) + cryptoApi.getKeyChanges(params.from, params.to) } } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/tasks/RedactEventTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/tasks/RedactEventTask.kt index 5226e52b33f2daaf7fe6b0bce5e8e0b2927e43d1..d6a7f3c6a026c44d942911446a38fd2064341e72 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/tasks/RedactEventTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/tasks/RedactEventTask.kt @@ -18,7 +18,6 @@ package org.matrix.android.sdk.internal.crypto.tasks import org.matrix.android.sdk.internal.network.GlobalErrorReceiver import org.matrix.android.sdk.internal.network.executeRequest import org.matrix.android.sdk.internal.session.room.RoomAPI -import org.matrix.android.sdk.internal.session.room.send.SendResponse import org.matrix.android.sdk.internal.task.Task import javax.inject.Inject @@ -36,14 +35,14 @@ internal class DefaultRedactEventTask @Inject constructor( private val globalErrorReceiver: GlobalErrorReceiver) : RedactEventTask { override suspend fun execute(params: RedactEventTask.Params): String { - val executeRequest = executeRequest<SendResponse>(globalErrorReceiver) { - apiCall = roomAPI.redactEvent( + val response = executeRequest(globalErrorReceiver) { + roomAPI.redactEvent( txId = params.txID, roomId = params.roomId, eventId = params.eventId, reason = if (params.reason == null) emptyMap() else mapOf("reason" to params.reason) ) } - return executeRequest.eventId + return response.eventId } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/tasks/SendEventTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/tasks/SendEventTask.kt index 573f2c3a5495a414a8971045b4fe1630b167ec72..e1e297767b100902ad4105f9f26f8fe53878f7b2 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/tasks/SendEventTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/tasks/SendEventTask.kt @@ -22,7 +22,6 @@ import org.matrix.android.sdk.internal.network.executeRequest import org.matrix.android.sdk.internal.session.room.RoomAPI import org.matrix.android.sdk.internal.session.room.membership.LoadRoomMembersTask import org.matrix.android.sdk.internal.session.room.send.LocalEchoRepository -import org.matrix.android.sdk.internal.session.room.send.SendResponse import org.matrix.android.sdk.internal.task.Task import javax.inject.Inject @@ -52,8 +51,8 @@ internal class DefaultSendEventTask @Inject constructor( val event = handleEncryption(params) val localId = event.eventId!! localEchoRepository.updateSendState(localId, params.event.roomId, SendState.SENDING) - val executeRequest = executeRequest<SendResponse>(globalErrorReceiver) { - apiCall = roomAPI.send( + val response = executeRequest(globalErrorReceiver) { + roomAPI.send( localId, roomId = event.roomId ?: "", content = event.content, @@ -61,7 +60,7 @@ internal class DefaultSendEventTask @Inject constructor( ) } localEchoRepository.updateSendState(localId, params.event.roomId, SendState.SENT) - return executeRequest.eventId + return response.eventId } catch (e: Throwable) { // localEchoRepository.updateSendState(params.event.eventId!!, SendState.UNDELIVERED) throw e diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/tasks/SendToDeviceTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/tasks/SendToDeviceTask.kt index d2af91601b49a994ae4de8ed37a9e7480465ee18..41a5118be017b8f119dcd7a326b46eb0d1730fe9 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/tasks/SendToDeviceTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/tasks/SendToDeviceTask.kt @@ -46,14 +46,16 @@ internal class DefaultSendToDeviceTask @Inject constructor( messages = params.contentMap.map ) - return executeRequest(globalErrorReceiver) { - apiCall = cryptoApi.sendToDevice( + return executeRequest( + globalErrorReceiver, + canRetry = true, + maxRetriesCount = 3 + ) { + cryptoApi.sendToDevice( params.eventType, params.transactionId ?: Random.nextInt(Integer.MAX_VALUE).toString(), sendToDeviceBody ) - isRetryable = true - maxRetryCount = 3 } } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/tasks/SendVerificationMessageTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/tasks/SendVerificationMessageTask.kt index ab125135bb393392b4b8cf3d1329c76f34ff6c14..d8b9d3cd864a91050a139fa365d84fd32a65380a 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/tasks/SendVerificationMessageTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/tasks/SendVerificationMessageTask.kt @@ -22,7 +22,6 @@ import org.matrix.android.sdk.internal.network.GlobalErrorReceiver import org.matrix.android.sdk.internal.network.executeRequest import org.matrix.android.sdk.internal.session.room.RoomAPI import org.matrix.android.sdk.internal.session.room.send.LocalEchoRepository -import org.matrix.android.sdk.internal.session.room.send.SendResponse import org.matrix.android.sdk.internal.task.Task import javax.inject.Inject @@ -45,8 +44,8 @@ internal class DefaultSendVerificationMessageTask @Inject constructor( try { localEchoRepository.updateSendState(localId, event.roomId, SendState.SENDING) - val executeRequest = executeRequest<SendResponse>(globalErrorReceiver) { - apiCall = roomAPI.send( + val response = executeRequest(globalErrorReceiver) { + roomAPI.send( localId, roomId = event.roomId ?: "", content = event.content, @@ -54,7 +53,7 @@ internal class DefaultSendVerificationMessageTask @Inject constructor( ) } localEchoRepository.updateSendState(localId, event.roomId, SendState.SENT) - return executeRequest.eventId + return response.eventId } catch (e: Throwable) { localEchoRepository.updateSendState(localId, event.roomId, SendState.UNDELIVERED) throw e diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/tasks/SetDeviceNameTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/tasks/SetDeviceNameTask.kt index b835d46236f2a2000e99375f3702c17808255e9f..4bedb1f393e5ffd14be3a2e60496f13658502852 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/tasks/SetDeviceNameTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/tasks/SetDeviceNameTask.kt @@ -42,7 +42,7 @@ internal class DefaultSetDeviceNameTask @Inject constructor( displayName = params.deviceName ) return executeRequest(globalErrorReceiver) { - apiCall = cryptoApi.updateDeviceInfo(params.deviceId, body) + cryptoApi.updateDeviceInfo(params.deviceId, body) } } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/tasks/UploadKeysTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/tasks/UploadKeysTask.kt index eb53bbbf8d289375e782c51c77d86e5cefc9013e..cac4dadd935ec4b97136b3d1ed8b8b03209a1843 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/tasks/UploadKeysTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/tasks/UploadKeysTask.kt @@ -50,7 +50,7 @@ internal class DefaultUploadKeysTask @Inject constructor( Timber.i("## Uploading device keys -> $body") return executeRequest(globalErrorReceiver) { - apiCall = cryptoApi.uploadKeys(body) + cryptoApi.uploadKeys(body) } } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/tasks/UploadSignaturesTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/tasks/UploadSignaturesTask.kt index c50faf37b1fa7e084ce7a51bfea4d55625c1fec5..e03e353cb1caa3dff8ec5e64b88e4b434631a4fd 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/tasks/UploadSignaturesTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/tasks/UploadSignaturesTask.kt @@ -17,7 +17,6 @@ package org.matrix.android.sdk.internal.crypto.tasks import org.matrix.android.sdk.api.failure.Failure import org.matrix.android.sdk.internal.crypto.api.CryptoApi -import org.matrix.android.sdk.internal.crypto.model.rest.SignatureUploadResponse import org.matrix.android.sdk.internal.network.GlobalErrorReceiver import org.matrix.android.sdk.internal.network.executeRequest import org.matrix.android.sdk.internal.task.Task @@ -36,10 +35,12 @@ internal class DefaultUploadSignaturesTask @Inject constructor( override suspend fun execute(params: UploadSignaturesTask.Params) { try { - val response = executeRequest<SignatureUploadResponse>(globalErrorReceiver) { - this.isRetryable = true - this.maxRetryCount = 10 - this.apiCall = cryptoApi.uploadSignatures(params.signatures) + val response = executeRequest( + globalErrorReceiver, + canRetry = true, + maxRetriesCount = 10 + ) { + cryptoApi.uploadSignatures(params.signatures) } if (response.failures?.isNotEmpty() == true) { throw Throwable(response.failures.toString()) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/tasks/UploadSigningKeysTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/tasks/UploadSigningKeysTask.kt index 14fad2ea3899466d36e27507cf288eccdd50d0f0..08c767ba34819af3d712917258ccfc69e7db8e6c 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/tasks/UploadSigningKeysTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/tasks/UploadSigningKeysTask.kt @@ -19,7 +19,6 @@ package org.matrix.android.sdk.internal.crypto.tasks import org.matrix.android.sdk.api.failure.Failure import org.matrix.android.sdk.internal.crypto.api.CryptoApi import org.matrix.android.sdk.internal.crypto.model.CryptoCrossSigningKey -import org.matrix.android.sdk.internal.crypto.model.rest.KeysQueryResponse import org.matrix.android.sdk.api.auth.UIABaseAuth import org.matrix.android.sdk.internal.crypto.model.rest.UploadSigningKeysBody import org.matrix.android.sdk.internal.crypto.model.toRest @@ -61,8 +60,8 @@ internal class DefaultUploadSigningKeysTask @Inject constructor( } private suspend fun doRequest(uploadQuery: UploadSigningKeysBody) { - val keysQueryResponse = executeRequest<KeysQueryResponse>(globalErrorReceiver) { - apiCall = cryptoApi.uploadSigningKeys(uploadQuery) + val keysQueryResponse = executeRequest(globalErrorReceiver) { + cryptoApi.uploadSigningKeys(uploadQuery) } if (keysQueryResponse.failures?.isNotEmpty() == true) { throw UploadSigningKeys(keysQueryResponse.failures) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/verification/DefaultVerificationService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/verification/DefaultVerificationService.kt index a92f5c5bf1b09f4d48c7cb31c96209de8c715cc2..d9da88770cd57b505920be9db61cbbe1cbb2adf9 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/verification/DefaultVerificationService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/verification/DefaultVerificationService.kt @@ -19,7 +19,6 @@ package org.matrix.android.sdk.internal.crypto.verification import android.os.Handler import android.os.Looper import dagger.Lazy -import org.matrix.android.sdk.api.MatrixCallback import org.matrix.android.sdk.api.session.crypto.crosssigning.CrossSigningService import org.matrix.android.sdk.api.session.crypto.crosssigning.KEYBACKUP_SECRET_SSSS_NAME import org.matrix.android.sdk.api.session.crypto.crosssigning.MASTER_KEY_SSSS_NAME @@ -1293,8 +1292,7 @@ internal class DefaultVerificationService @Inject constructor( transactionId: String, roomId: String, otherUserId: String, - otherDeviceId: String, - callback: MatrixCallback<String>?): String? { + otherDeviceId: String): String { if (method == VerificationMethod.SAS) { val tx = DefaultOutgoingSASDefaultVerificationTransaction( setDeviceVerificationAction, diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/verification/VerificationTransportRoomMessage.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/verification/VerificationTransportRoomMessage.kt index ad490bcd277febb624a2feb19ae189a4339efcac..bcf3250ed2b9898ecedcc9665e2b10a90abc78dd 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/verification/VerificationTransportRoomMessage.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/verification/VerificationTransportRoomMessage.kt @@ -183,7 +183,7 @@ internal class VerificationTransportRoomMessage( val workRequest = workManagerProvider.matrixOneTimeWorkRequestBuilder<SendVerificationMessageWorker>() .setConstraints(WorkManagerProvider.workConstraints) .setInputData(workerParams) - .setBackoffCriteria(BackoffPolicy.LINEAR, 2_000L, TimeUnit.MILLISECONDS) + .setBackoffCriteria(BackoffPolicy.LINEAR, WorkManagerProvider.BACKOFF_DELAY_MILLIS, TimeUnit.MILLISECONDS) .build() workManagerProvider.workManager @@ -280,7 +280,7 @@ internal class VerificationTransportRoomMessage( val workRequest = workManagerProvider.matrixOneTimeWorkRequestBuilder<SendVerificationMessageWorker>() .setConstraints(WorkManagerProvider.workConstraints) .setInputData(workerParams) - .setBackoffCriteria(BackoffPolicy.LINEAR, 2_000L, TimeUnit.MILLISECONDS) + .setBackoffCriteria(BackoffPolicy.LINEAR, WorkManagerProvider.BACKOFF_DELAY_MILLIS, TimeUnit.MILLISECONDS) .build() return workManagerProvider.workManager .beginUniqueWork(uniqueQueueName(), ExistingWorkPolicy.APPEND_OR_REPLACE, workRequest) 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 c7fe7ab447fb55f1242ca3d0a3db3a288a2e62cc..1daae906f2e902de3d302c776a46a29e4734861d 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/RealmSessionStoreMigration.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/RealmSessionStoreMigration.kt @@ -17,22 +17,27 @@ package org.matrix.android.sdk.internal.database import io.realm.DynamicRealm +import io.realm.FieldAttribute import io.realm.RealmMigration +import org.matrix.android.sdk.api.session.room.model.tag.RoomTag import org.matrix.android.sdk.internal.database.model.EditAggregatedSummaryEntityFields import org.matrix.android.sdk.internal.database.model.EditionOfEventFields +import org.matrix.android.sdk.internal.database.model.EventEntityFields import org.matrix.android.sdk.internal.database.model.HomeServerCapabilitiesEntityFields import org.matrix.android.sdk.internal.database.model.PendingThreePidEntityFields import org.matrix.android.sdk.internal.database.model.PreviewUrlCacheEntityFields import org.matrix.android.sdk.internal.database.model.RoomEntityFields import org.matrix.android.sdk.internal.database.model.RoomMembersLoadStatusType import org.matrix.android.sdk.internal.database.model.RoomSummaryEntityFields +import org.matrix.android.sdk.internal.database.model.RoomTagEntityFields +import org.matrix.android.sdk.internal.database.model.TimelineEventEntityFields import timber.log.Timber import javax.inject.Inject class RealmSessionStoreMigration @Inject constructor() : RealmMigration { companion object { - const val SESSION_STORE_SCHEMA_VERSION = 8L + const val SESSION_STORE_SCHEMA_VERSION = 9L } override fun migrate(realm: DynamicRealm, oldVersion: Long, newVersion: Long) { @@ -46,6 +51,7 @@ class RealmSessionStoreMigration @Inject constructor() : RealmMigration { if (oldVersion <= 5) migrateTo6(realm) if (oldVersion <= 6) migrateTo7(realm) if (oldVersion <= 7) migrateTo8(realm) + if (oldVersion <= 8) migrateTo9(realm) } private fun migrateTo1(realm: DynamicRealm) { @@ -149,4 +155,43 @@ class RealmSessionStoreMigration @Inject constructor() : RealmMigration { ?.removeField("sourceLocalEchoEvents") ?.addRealmListField(EditAggregatedSummaryEntityFields.EDITIONS.`$`, editionOfEventSchema) } + + fun migrateTo9(realm: DynamicRealm) { + Timber.d("Step 8 -> 9") + + realm.schema.get("RoomSummaryEntity") + ?.addField(RoomSummaryEntityFields.LAST_ACTIVITY_TIME, Long::class.java, FieldAttribute.INDEXED) + ?.setNullable(RoomSummaryEntityFields.LAST_ACTIVITY_TIME, true) + ?.addIndex(RoomSummaryEntityFields.MEMBERSHIP_STR) + ?.addIndex(RoomSummaryEntityFields.IS_DIRECT) + ?.addIndex(RoomSummaryEntityFields.VERSIONING_STATE_STR) + + ?.addField(RoomSummaryEntityFields.IS_FAVOURITE, Boolean::class.java) + ?.addIndex(RoomSummaryEntityFields.IS_FAVOURITE) + ?.addField(RoomSummaryEntityFields.IS_LOW_PRIORITY, Boolean::class.java) + ?.addIndex(RoomSummaryEntityFields.IS_LOW_PRIORITY) + ?.addField(RoomSummaryEntityFields.IS_SERVER_NOTICE, Boolean::class.java) + ?.addIndex(RoomSummaryEntityFields.IS_SERVER_NOTICE) + + ?.transform { obj -> + + val isFavorite = obj.getList(RoomSummaryEntityFields.TAGS.`$`).any { + it.getString(RoomTagEntityFields.TAG_NAME) == RoomTag.ROOM_TAG_FAVOURITE + } + obj.setBoolean(RoomSummaryEntityFields.IS_FAVOURITE, isFavorite) + + val isLowPriority = obj.getList(RoomSummaryEntityFields.TAGS.`$`).any { + it.getString(RoomTagEntityFields.TAG_NAME) == RoomTag.ROOM_TAG_LOW_PRIORITY + } + + obj.setBoolean(RoomSummaryEntityFields.IS_LOW_PRIORITY, isLowPriority) + +// XXX migrate last message origin server ts + obj.getObject(RoomSummaryEntityFields.LATEST_PREVIEWABLE_EVENT.`$`) + ?.getObject(TimelineEventEntityFields.ROOT.`$`) + ?.getLong(EventEntityFields.ORIGIN_SERVER_TS)?.let { + obj.setLong(RoomSummaryEntityFields.LAST_ACTIVITY_TIME, it) + } + } + } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/mapper/RoomSummaryMapper.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/mapper/RoomSummaryMapper.kt index 2e54a4cd5258127d4124085fd56a9bb98d583943..6dc70b60fc71937647211b6d58b8a9019096e395 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/mapper/RoomSummaryMapper.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/mapper/RoomSummaryMapper.kt @@ -26,7 +26,7 @@ internal class RoomSummaryMapper @Inject constructor(private val timelineEventMa private val typingUsersTracker: DefaultTypingUsersTracker) { fun map(roomSummaryEntity: RoomSummaryEntity): RoomSummary { - val tags = roomSummaryEntity.tags.map { + val tags = roomSummaryEntity.tags().map { RoomTag(it.tagName, it.tagOrder) } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/mapper/TimelineEventMapper.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/mapper/TimelineEventMapper.kt index a2b36ce59004d9b960b4eae48f640e4a2f17e837..f3bea68c26e6bb1fc83cc7d6e56b1bda03b1a6b1 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/mapper/TimelineEventMapper.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/mapper/TimelineEventMapper.kt @@ -17,7 +17,6 @@ package org.matrix.android.sdk.internal.database.mapper import org.matrix.android.sdk.api.session.events.model.Event -import org.matrix.android.sdk.api.session.room.model.ReadReceipt import org.matrix.android.sdk.api.session.room.sender.SenderInfo import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent import org.matrix.android.sdk.internal.database.model.TimelineEventEntity @@ -25,9 +24,9 @@ import javax.inject.Inject internal class TimelineEventMapper @Inject constructor(private val readReceiptsSummaryMapper: ReadReceiptsSummaryMapper) { - fun map(timelineEventEntity: TimelineEventEntity, buildReadReceipts: Boolean = true, correctedReadReceipts: List<ReadReceipt>? = null): TimelineEvent { + fun map(timelineEventEntity: TimelineEventEntity, buildReadReceipts: Boolean = true): TimelineEvent { val readReceipts = if (buildReadReceipts) { - correctedReadReceipts ?: timelineEventEntity.readReceipts + timelineEventEntity.readReceipts ?.let { readReceiptsSummaryMapper.map(it) } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/RoomMemberSummaryEntity.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/RoomMemberSummaryEntity.kt index a48b081f0224740f78cce80568fd8d2b7875e76c..e970fab397990bb84a53cc41f184efea26ad91ec 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/RoomMemberSummaryEntity.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/RoomMemberSummaryEntity.kt @@ -39,5 +39,7 @@ internal open class RoomMemberSummaryEntity(@PrimaryKey var primaryKey: String = membershipStr = value.name } + fun getBestName() = displayName?.takeIf { it.isNotBlank() } ?: userId + companion object } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/RoomSummaryEntity.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/RoomSummaryEntity.kt index 37696c90824afcb14ef4ebdb09e4bc8d54591c7e..c87ac15a78337778b9be15fe13f98740349d7ea7 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/RoomSummaryEntity.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/RoomSummaryEntity.kt @@ -16,61 +16,217 @@ package org.matrix.android.sdk.internal.database.model +import io.realm.RealmList +import io.realm.RealmObject +import io.realm.annotations.Index +import io.realm.annotations.PrimaryKey import org.matrix.android.sdk.api.crypto.RoomEncryptionTrustLevel import org.matrix.android.sdk.api.session.room.model.Membership import org.matrix.android.sdk.api.session.room.model.RoomSummary import org.matrix.android.sdk.api.session.room.model.VersioningState -import io.realm.RealmList -import io.realm.RealmObject -import io.realm.annotations.PrimaryKey +import org.matrix.android.sdk.api.session.room.model.tag.RoomTag internal open class RoomSummaryEntity( - @PrimaryKey var roomId: String = "", - var displayName: String? = "", - var avatarUrl: String? = "", - var name: String? = "", - var topic: String? = "", - var latestPreviewableEvent: TimelineEventEntity? = null, - var heroes: RealmList<String> = RealmList(), - var joinedMembersCount: Int? = 0, - var invitedMembersCount: Int? = 0, - var isDirect: Boolean = false, - var directUserId: String? = null, - var otherMemberIds: RealmList<String> = RealmList(), - var notificationCount: Int = 0, - var highlightCount: Int = 0, - var readMarkerId: String? = null, - var hasUnreadMessages: Boolean = false, - var tags: RealmList<RoomTagEntity> = RealmList(), - var userDrafts: UserDraftsEntity? = null, - var breadcrumbsIndex: Int = RoomSummary.NOT_IN_BREADCRUMBS, - var canonicalAlias: String? = null, - var aliases: RealmList<String> = RealmList(), - // this is required for querying - var flatAliases: String = "", - var isEncrypted: Boolean = false, - var encryptionEventTs: Long? = 0, - var roomEncryptionTrustLevelStr: String? = null, - var inviterId: String? = null, - var hasFailedSending: Boolean = false + @PrimaryKey var roomId: String = "" ) : RealmObject() { + var displayName: String? = "" + set(value) { + if (value != field) field = value + } + var avatarUrl: String? = "" + set(value) { + if (value != field) field = value + } + var name: String? = "" + set(value) { + if (value != field) field = value + } + var topic: String? = "" + set(value) { + if (value != field) field = value + } + + var latestPreviewableEvent: TimelineEventEntity? = null + set(value) { + if (value != field) field = value + } + + @Index + var lastActivityTime: Long? = null + set(value) { + if (value != field) field = value + } + + var heroes: RealmList<String> = RealmList() + + var joinedMembersCount: Int? = 0 + set(value) { + if (value != field) field = value + } + + var invitedMembersCount: Int? = 0 + set(value) { + if (value != field) field = value + } + + @Index + var isDirect: Boolean = false + set(value) { + if (value != field) field = value + } + + var directUserId: String? = null + set(value) { + if (value != field) field = value + } + + var otherMemberIds: RealmList<String> = RealmList() + + var notificationCount: Int = 0 + set(value) { + if (value != field) field = value + } + + var highlightCount: Int = 0 + set(value) { + if (value != field) field = value + } + + var readMarkerId: String? = null + set(value) { + if (value != field) field = value + } + + var hasUnreadMessages: Boolean = false + set(value) { + if (value != field) field = value + } + + private var tags: RealmList<RoomTagEntity> = RealmList() + + fun tags(): List<RoomTagEntity> = tags + + fun updateTags(newTags: List<Pair<String, Double?>>) { + val toDelete = mutableListOf<RoomTagEntity>() + tags.forEach { existingTag -> + val updatedTag = newTags.firstOrNull { it.first == existingTag.tagName } + if (updatedTag == null) { + toDelete.add(existingTag) + } else { + existingTag.tagOrder = updatedTag.second + } + } + toDelete.forEach { it.deleteFromRealm() } + newTags.forEach { newTag -> + if (tags.all { it.tagName != newTag.first }) { + // we must add it + tags.add( + RoomTagEntity(newTag.first, newTag.second) + ) + } + } + + isFavourite = newTags.any { it.first == RoomTag.ROOM_TAG_FAVOURITE } + isLowPriority = newTags.any { it.first == RoomTag.ROOM_TAG_LOW_PRIORITY } + isServerNotice = newTags.any { it.first == RoomTag.ROOM_TAG_SERVER_NOTICE } + } + + @Index + var isFavourite: Boolean = false + set(value) { + if (value != field) field = value + } + + @Index + var isLowPriority: Boolean = false + set(value) { + if (value != field) field = value + } + + @Index + var isServerNotice: Boolean = false + set(value) { + if (value != field) field = value + } + + var userDrafts: UserDraftsEntity? = null + set(value) { + if (value != field) field = value + } + + var breadcrumbsIndex: Int = RoomSummary.NOT_IN_BREADCRUMBS + set(value) { + if (value != field) field = value + } + + var canonicalAlias: String? = null + set(value) { + if (value != field) field = value + } + + var aliases: RealmList<String> = RealmList() + + fun updateAliases(newAliases: List<String>) { + // only update underlying field if there is a diff + if (newAliases.distinct().sorted() != aliases.distinct().sorted()) { + aliases.clear() + aliases.addAll(newAliases) + flatAliases = newAliases.joinToString(separator = "|", prefix = "|") + } + } + + // this is required for querying + var flatAliases: String = "" + + var isEncrypted: Boolean = false + set(value) { + if (value != field) field = value + } + + var encryptionEventTs: Long? = 0 + set(value) { + if (value != field) field = value + } + + var roomEncryptionTrustLevelStr: String? = null + set(value) { + if (value != field) field = value + } + + var inviterId: String? = null + set(value) { + if (value != field) field = value + } + + var hasFailedSending: Boolean = false + set(value) { + if (value != field) field = value + } + + @Index private var membershipStr: String = Membership.NONE.name + var membership: Membership get() { return Membership.valueOf(membershipStr) } set(value) { - membershipStr = value.name + if (value.name != membershipStr) { + membershipStr = value.name + } } + @Index private var versioningStateStr: String = VersioningState.NONE.name var versioningState: VersioningState get() { return VersioningState.valueOf(versioningStateStr) } set(value) { - versioningStateStr = value.name + if (value.name != versioningStateStr) { + versioningStateStr = value.name + } } var roomEncryptionTrustLevel: RoomEncryptionTrustLevel? @@ -84,7 +240,9 @@ internal open class RoomSummaryEntity( } } set(value) { - roomEncryptionTrustLevelStr = value?.name + if (value?.name != roomEncryptionTrustLevelStr) { + roomEncryptionTrustLevelStr = value?.name + } } companion object diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/query/ReadQueries.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/query/ReadQueries.kt index a3c741ad55a441c6a04c685f9ae5f85605b91520..5423025823dfc38206fe9fd3d49c3ff51f334c50 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/query/ReadQueries.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/query/ReadQueries.kt @@ -38,16 +38,21 @@ internal fun isEventRead(realmConfiguration: RealmConfiguration, Realm.getInstance(realmConfiguration).use { realm -> val liveChunk = ChunkEntity.findLastForwardChunkOfRoom(realm, roomId) ?: return@use val eventToCheck = liveChunk.timelineEvents.find(eventId) - isEventRead = if (eventToCheck == null || eventToCheck.root?.sender == userId) { - true - } else { - val readReceipt = ReadReceiptEntity.where(realm, roomId, userId).findFirst() - ?: return@use - val readReceiptIndex = liveChunk.timelineEvents.find(readReceipt.eventId)?.displayIndex - ?: Int.MIN_VALUE - val eventToCheckIndex = eventToCheck.displayIndex + isEventRead = when { + eventToCheck == null -> { + // This can happen in case of fast lane Event + false + } + eventToCheck.root?.sender == userId -> true + else -> { + val readReceipt = ReadReceiptEntity.where(realm, roomId, userId).findFirst() + ?: return@use + val readReceiptIndex = liveChunk.timelineEvents.find(readReceipt.eventId)?.displayIndex + ?: Int.MIN_VALUE + val eventToCheckIndex = eventToCheck.displayIndex - eventToCheckIndex <= readReceiptIndex + eventToCheckIndex <= readReceiptIndex + } } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/query/RoomSummaryEntityQueries.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/query/RoomSummaryEntityQueries.kt index 7430b7822f591658ca4d911e5bcf0a48de7af18d..2af5dcf0ae500b448c15262ca8935aa2cd137381 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/query/RoomSummaryEntityQueries.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/query/RoomSummaryEntityQueries.kt @@ -48,9 +48,15 @@ internal fun RoomSummaryEntity.Companion.getOrCreate(realm: Realm, roomId: Strin return where(realm, roomId).findFirst() ?: realm.createObject(roomId) } -internal fun RoomSummaryEntity.Companion.getDirectRooms(realm: Realm): RealmResults<RoomSummaryEntity> { +internal fun RoomSummaryEntity.Companion.getDirectRooms(realm: Realm, + excludeRoomIds: Set<String>? = null): RealmResults<RoomSummaryEntity> { return RoomSummaryEntity.where(realm) .equalTo(RoomSummaryEntityFields.IS_DIRECT, true) + .apply { + if (!excludeRoomIds.isNullOrEmpty()) { + not().`in`(RoomSummaryEntityFields.ROOM_ID, excludeRoomIds.toTypedArray()) + } + } .findAll() } 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 f4688411ffe0bdacf32bb8455912db481075cd36..0d0892b608d91b68b909ca26c7ccbd3c3e147972 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 @@ -28,6 +28,7 @@ import org.matrix.android.sdk.internal.network.interceptors.CurlLoggingIntercept import org.matrix.android.sdk.internal.network.interceptors.FormattedJsonHttpLogger import okhttp3.OkHttpClient import okhttp3.logging.HttpLoggingInterceptor +import org.matrix.android.sdk.internal.network.ApiInterceptor import java.util.concurrent.TimeUnit @Module @@ -63,7 +64,8 @@ internal object NetworkModule { timeoutInterceptor: TimeOutInterceptor, userAgentInterceptor: UserAgentInterceptor, httpLoggingInterceptor: HttpLoggingInterceptor, - curlLoggingInterceptor: CurlLoggingInterceptor): OkHttpClient { + curlLoggingInterceptor: CurlLoggingInterceptor, + apiInterceptor: ApiInterceptor): OkHttpClient { return OkHttpClient.Builder() .connectTimeout(30, TimeUnit.SECONDS) .readTimeout(60, TimeUnit.SECONDS) @@ -76,6 +78,7 @@ internal object NetworkModule { .addInterceptor(timeoutInterceptor) .addInterceptor(userAgentInterceptor) .addInterceptor(httpLoggingInterceptor) + .addInterceptor(apiInterceptor) .apply { if (BuildConfig.LOG_PRIVATE_DATA) { addInterceptor(curlLoggingInterceptor) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/di/WorkManagerProvider.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/di/WorkManagerProvider.kt index 4c9d06f7a2c727114d46f4fc4b5179b50a8b0376..bafffdf852006ea9666862210926a333990cc6a7 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/di/WorkManagerProvider.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/di/WorkManagerProvider.kt @@ -23,6 +23,7 @@ import androidx.work.NetworkType import androidx.work.OneTimeWorkRequestBuilder import androidx.work.PeriodicWorkRequestBuilder import androidx.work.WorkManager +import androidx.work.WorkRequest import java.util.concurrent.TimeUnit import javax.inject.Inject @@ -69,6 +70,7 @@ internal class WorkManagerProvider @Inject constructor( .setRequiredNetworkType(NetworkType.CONNECTED) .build() - const val BACKOFF_DELAY = 10_000L + // Use min value, smaller value will be ignored + const val BACKOFF_DELAY_MILLIS = WorkRequest.MIN_BACKOFF_MILLIS } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/extensions/Try.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/extensions/Try.kt index 8786321464959b625d4df9cb00eda6a2e5feb480..2ce0534b49bc436111dc1a2440823b2e9916ec70 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/extensions/Try.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/extensions/Try.kt @@ -21,7 +21,6 @@ import arrow.core.Success import arrow.core.Try import arrow.core.TryOf import arrow.core.fix -import org.matrix.android.sdk.api.MatrixCallback inline fun <A> TryOf<A>.onError(f: (Throwable) -> Unit): Try<A> = fix() .fold( @@ -32,10 +31,6 @@ inline fun <A> TryOf<A>.onError(f: (Throwable) -> Unit): Try<A> = fix() { Success(it) } ) -fun <A> Try<A>.foldToCallback(callback: MatrixCallback<A>): Unit = fold( - { callback.onFailure(it) }, - { callback.onSuccess(it) }) - /** * Same as doOnNext for Observables */ diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/federation/FederationAPI.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/federation/FederationAPI.kt index 1816616336c9a683a0605b91daf9da666989a625..c37392494f66f50fa1cee4d5d940fc56205db49a 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/federation/FederationAPI.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/federation/FederationAPI.kt @@ -18,10 +18,9 @@ package org.matrix.android.sdk.internal.federation import org.matrix.android.sdk.internal.network.NetworkConstants -import retrofit2.Call import retrofit2.http.GET internal interface FederationAPI { @GET(NetworkConstants.URI_FEDERATION_PATH + "version") - fun getVersion(): Call<FederationGetVersionResult> + suspend fun getVersion(): FederationGetVersionResult } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/federation/GetFederationVersionTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/federation/GetFederationVersionTask.kt index ce35e48f6b53f13e6d9c37f4f59a2df515a7fc98..b7f73a606cf0c76c215473121ca8822404eef174 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/federation/GetFederationVersionTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/federation/GetFederationVersionTask.kt @@ -28,8 +28,8 @@ internal class DefaultGetFederationVersionTask @Inject constructor( ) : GetFederationVersionTask { override suspend fun execute(params: Unit): FederationVersion { - val result = executeRequest<FederationGetVersionResult>(null) { - apiCall = federationAPI.getVersion() + val result = executeRequest(null) { + federationAPI.getVersion() } return FederationVersion( diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/network/ApiInterceptor.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/network/ApiInterceptor.kt new file mode 100644 index 0000000000000000000000000000000000000000..1dd6e75ea56a941410ec4a7556404dfb29761101 --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/network/ApiInterceptor.kt @@ -0,0 +1,102 @@ +/* + * Copyright (c) 2021 The Matrix.org Foundation C.I.C. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.matrix.android.sdk.internal.network + +import okhttp3.Interceptor +import okhttp3.Response +import org.matrix.android.sdk.api.extensions.tryOrNull +import org.matrix.android.sdk.api.network.ApiInterceptorListener +import org.matrix.android.sdk.api.network.ApiPath +import org.matrix.android.sdk.internal.di.MatrixScope +import timber.log.Timber +import javax.inject.Inject + +/** + * Interceptor class for provided api paths. + */ +@MatrixScope +internal class ApiInterceptor @Inject constructor() : Interceptor { + + init { + Timber.d("ApiInterceptor.init") + } + + private val apiResponseListenersMap = mutableMapOf<ApiPath, MutableList<ApiInterceptorListener>>() + + override fun intercept(chain: Interceptor.Chain): Response { + val request = chain.request() + val path = request.url.encodedPath.replaceFirst("/", "") + val method = request.method + + val response = chain.proceed(request) + + synchronized(apiResponseListenersMap) { + findApiPath(path, method)?.let { apiPath -> + response.peekBody(Long.MAX_VALUE).string().let { networkResponse -> + apiResponseListenersMap[apiPath]?.forEach { listener -> + tryOrNull("Error in the implementation") { + listener.onApiResponse(apiPath, networkResponse) + } + } + } + } + } + + return response + } + + private fun findApiPath(path: String, method: String): ApiPath? { + return apiResponseListenersMap + .keys + .find { apiPath -> + apiPath.method === method && isTheSamePath(apiPath.path, path) + } + } + + private fun isTheSamePath(pattern: String, path: String): Boolean { + val patternSegments = pattern.split("/") + val pathSegments = path.split("/") + + if (patternSegments.size != pathSegments.size) return false + + return patternSegments.indices.all { i -> + patternSegments[i] == pathSegments[i] || patternSegments[i].startsWith("{") + } + } + + /** + * Adds listener to send intercepted api responses through. + */ + fun addListener(path: ApiPath, listener: ApiInterceptorListener) { + synchronized(apiResponseListenersMap) { + apiResponseListenersMap.getOrPut(path) { mutableListOf() } + .add(listener) + } + } + + /** + * Remove listener to send intercepted api responses through. + */ + fun removeListener(path: ApiPath, listener: ApiInterceptorListener) { + synchronized(apiResponseListenersMap) { + apiResponseListenersMap[path]?.remove(listener) + if (apiResponseListenersMap[path]?.isEmpty() == true) { + apiResponseListenersMap.remove(path) + } + } + } +} 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 442029127d3202fcc5e35761ba1e234b00668f3b..0246bae024eed8933e5f2224939ad9655d652a1e 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,38 +19,49 @@ 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.getRetryDelay import org.matrix.android.sdk.api.failure.shouldBeRetried import org.matrix.android.sdk.internal.network.ssl.CertUtil -import retrofit2.Call -import retrofit2.awaitResponse +import retrofit2.HttpException import timber.log.Timber import java.io.IOException -internal suspend inline fun <DATA : Any> executeRequest(globalErrorReceiver: GlobalErrorReceiver?, - block: Request<DATA>.() -> Unit) = Request<DATA>(globalErrorReceiver).apply(block).execute() - -internal class Request<DATA : Any>(private val globalErrorReceiver: GlobalErrorReceiver?) { +/** + * Execute a request from the requestBlock and handle some of the Exception it could generate + * Ref: https://github.com/matrix-org/matrix-js-sdk/blob/develop/src/scheduler.js#L138-L175 + * + * @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 maxRetriesCount the max number of retries + * @param requestBlock a suspend lambda to perform the network request + */ +internal suspend inline fun <DATA> executeRequest(globalErrorReceiver: GlobalErrorReceiver?, + canRetry: Boolean = false, + maxDelayBeforeRetry: Long = 32_000L, + maxRetriesCount: Int = 4, + noinline requestBlock: suspend () -> DATA): DATA { + var currentRetryCount = 0 + var currentDelay = 1_000L - var isRetryable = false - var initialDelay: Long = 100L - var maxDelay: Long = 10_000L - var maxRetryCount = Int.MAX_VALUE - private var currentRetryCount = 0 - private var currentDelay = initialDelay - lateinit var apiCall: Call<DATA> + while (true) { + try { + return requestBlock() + } catch (throwable: Throwable) { + val exception = when (throwable) { + is KotlinNullPointerException -> IllegalStateException("The request returned a null body") + is HttpException -> throwable.toFailure(globalErrorReceiver) + else -> throwable + } - suspend fun execute(): DATA { - return try { - val response = apiCall.clone().awaitResponse() - if (response.isSuccessful) { - response.body() - ?: throw IllegalStateException("The request returned a null body") + // Log some details about the request which has failed. + val request = (throwable as? HttpException)?.response()?.raw()?.request + if (request == null) { + Timber.e("Exception when executing request") } else { - throw response.toFailure(globalErrorReceiver) + Timber.e("Exception when executing request ${request.method} ${request.url.toString().substringBefore("?")}") } - } catch (exception: Throwable) { - // Log some details about the request which has failed - Timber.e("Exception when executing request ${apiCall.request().method} ${apiCall.request().url.toString().substringBefore("?")}") // Check if this is a certificateException CertUtil.getCertificateException(exception) @@ -61,10 +72,18 @@ internal class Request<DATA : Any>(private val globalErrorReceiver: GlobalErrorR // } ?.also { unrecognizedCertificateException -> throw unrecognizedCertificateException } - if (isRetryable && currentRetryCount++ < maxRetryCount && exception.shouldBeRetried()) { + currentRetryCount++ + + if (exception is Failure.ServerError + && exception.httpCode == 429 + && exception.error.code == MatrixError.M_LIMIT_EXCEEDED + && currentRetryCount < maxRetriesCount) { + // 429, we can retry + delay(exception.getRetryDelay(1_000)) + } else if (canRetry && currentRetryCount < maxRetriesCount && exception.shouldBeRetried()) { delay(currentDelay) - currentDelay = (currentDelay * 2L).coerceAtMost(maxDelay) - return execute() + currentDelay = currentDelay.times(2L).coerceAtMost(maxDelayBeforeRetry) + // Try again (loop) } else { throw when (exception) { is IOException -> Failure.NetworkConnection(exception) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/network/RetrofitExtensions.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/network/RetrofitExtensions.kt index dd5a69dd3c99aa7232ff256fd6ca470204639be9..7132b4ff7a8518fb034d543f646953629f2bc203 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/network/RetrofitExtensions.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/network/RetrofitExtensions.kt @@ -25,6 +25,7 @@ import org.matrix.android.sdk.api.failure.MatrixError import org.matrix.android.sdk.internal.di.MoshiProvider import kotlinx.coroutines.suspendCancellableCoroutine import okhttp3.ResponseBody +import retrofit2.HttpException import retrofit2.Response import timber.log.Timber import java.io.IOException @@ -57,6 +58,13 @@ internal fun <T> Response<T>.toFailure(globalErrorReceiver: GlobalErrorReceiver? return toFailure(errorBody(), code(), globalErrorReceiver) } +/** + * Convert a HttpException to a Failure, and eventually parse errorBody to convert it to a MatrixError + */ +internal fun HttpException.toFailure(globalErrorReceiver: GlobalErrorReceiver?): Failure { + return toFailure(response()?.errorBody(), code(), globalErrorReceiver) +} + /** * Convert a okhttp3 Response to a Failure, and eventually parse errorBody to convert it to a MatrixError */ diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/raw/GetUrlTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/raw/GetUrlTask.kt index 16633d90ef27b9a56c6b8a9102f92f4de301d8b7..d0e2534e7a1bf4f4ea014c693f87c6b6cb95d0f0 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/raw/GetUrlTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/raw/GetUrlTask.kt @@ -17,7 +17,6 @@ package org.matrix.android.sdk.internal.raw import com.zhuinden.monarchy.Monarchy -import okhttp3.ResponseBody import org.matrix.android.sdk.api.cache.CacheStrategy import org.matrix.android.sdk.internal.database.model.RawCacheEntity import org.matrix.android.sdk.internal.database.query.get @@ -58,8 +57,8 @@ internal class DefaultGetUrlTask @Inject constructor( } private suspend fun doRequest(url: String): String { - return executeRequest<ResponseBody>(null) { - apiCall = rawAPI.getUrl(url) + return executeRequest(null) { + rawAPI.getUrl(url) } .string() } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/raw/RawAPI.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/raw/RawAPI.kt index 4b08afd711a673df4fba82da6ddab844d506bf41..338d94781bb9bb751930e5f448acd4b6796b05bf 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/raw/RawAPI.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/raw/RawAPI.kt @@ -18,11 +18,10 @@ package org.matrix.android.sdk.internal.raw import okhttp3.ResponseBody -import retrofit2.Call import retrofit2.http.GET import retrofit2.http.Url internal interface RawAPI { @GET - fun getUrl(@Url url: String): Call<ResponseBody> + suspend fun getUrl(@Url url: String): ResponseBody } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/DefaultFileService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/DefaultFileService.kt index 07cde3da609e0f3b11e68d4b90a8d89a0a5141a2..d05ee48c1bf41c33e520600100756af615e8ef3a 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/DefaultFileService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/DefaultFileService.kt @@ -20,26 +20,20 @@ import android.content.Context import android.net.Uri import android.webkit.MimeTypeMap import androidx.core.content.FileProvider -import arrow.core.Try -import kotlinx.coroutines.launch +import kotlinx.coroutines.CompletableDeferred +import kotlinx.coroutines.completeWith import kotlinx.coroutines.withContext import okhttp3.OkHttpClient import okhttp3.Request -import org.matrix.android.sdk.api.MatrixCallback -import org.matrix.android.sdk.api.extensions.tryOrNull import org.matrix.android.sdk.api.session.content.ContentUrlResolver import org.matrix.android.sdk.api.session.file.FileService -import org.matrix.android.sdk.api.util.Cancelable -import org.matrix.android.sdk.api.util.NoOpCancellable import org.matrix.android.sdk.internal.crypto.attachments.ElementToDecrypt import org.matrix.android.sdk.internal.crypto.attachments.MXEncryptedAttachments import org.matrix.android.sdk.internal.di.SessionDownloadsDirectory import org.matrix.android.sdk.internal.di.UnauthenticatedWithCertificateWithProgress import org.matrix.android.sdk.internal.session.download.DownloadProgressInterceptor.Companion.DOWNLOAD_PROGRESS_INTERCEPTOR_HEADER -import org.matrix.android.sdk.internal.task.TaskExecutor import org.matrix.android.sdk.internal.util.MatrixCoroutineDispatchers import org.matrix.android.sdk.internal.util.md5 -import org.matrix.android.sdk.internal.util.toCancelable import org.matrix.android.sdk.internal.util.writeToFile import timber.log.Timber import java.io.File @@ -53,14 +47,15 @@ internal class DefaultFileService @Inject constructor( private val contentUrlResolver: ContentUrlResolver, @UnauthenticatedWithCertificateWithProgress private val okHttpClient: OkHttpClient, - private val coroutineDispatchers: MatrixCoroutineDispatchers, - private val taskExecutor: TaskExecutor + private val coroutineDispatchers: MatrixCoroutineDispatchers ) : FileService { // Legacy folder, will be deleted private val legacyFolder = File(sessionCacheDirectory, "MF") + // Folder to store downloaded files (not decrypted) private val downloadFolder = File(sessionCacheDirectory, "F") + // Folder to store decrypted files private val decryptedFolder = File(downloadFolder, "D") @@ -73,134 +68,113 @@ internal class DefaultFileService @Inject constructor( * Retain ongoing downloads to avoid re-downloading and already downloading file * map of mxCurl to callbacks */ - private val ongoing = mutableMapOf<String, ArrayList<MatrixCallback<File>>>() + private val ongoing = mutableMapOf<String, CompletableDeferred<File>>() /** * Download file in the cache folder, and eventually decrypt it * TODO looks like files are copied 3 times */ - override fun downloadFile(fileName: String, - mimeType: String?, - url: String?, - elementToDecrypt: ElementToDecrypt?, - callback: MatrixCallback<File>): Cancelable { - url ?: return NoOpCancellable.also { - callback.onFailure(IllegalArgumentException("url is null")) - } + override suspend fun downloadFile(fileName: String, + mimeType: String?, + url: String?, + elementToDecrypt: ElementToDecrypt?): File { + url ?: throw IllegalArgumentException("url is null") Timber.v("## FileService downloadFile $url") - synchronized(ongoing) { + // TODO: Remove use of `synchronized` in suspend function. + val existingDownload = synchronized(ongoing) { val existing = ongoing[url] if (existing != null) { Timber.v("## FileService downloadFile is already downloading.. ") - existing.add(callback) - return NoOpCancellable + existing } else { // mark as tracked - ongoing[url] = ArrayList() + ongoing[url] = CompletableDeferred() // and proceed to download + null } } - return taskExecutor.executorScope.launch(coroutineDispatchers.main) { - withContext(coroutineDispatchers.io) { - Try { - if (!decryptedFolder.exists()) { - decryptedFolder.mkdirs() - } - // ensure we use unique file name by using URL (mapped to suitable file name) - // Also we need to add extension for the FileProvider, if not it lot's of app that it's - // shared with will not function well (even if mime type is passed in the intent) - getFiles(url, fileName, mimeType, elementToDecrypt != null) - }.flatMap { cachedFiles -> - if (!cachedFiles.file.exists()) { - val resolvedUrl = contentUrlResolver.resolveFullSize(url) ?: return@flatMap Try.Failure(IllegalArgumentException("url is null")) - - val request = Request.Builder() - .url(resolvedUrl) - .header(DOWNLOAD_PROGRESS_INTERCEPTOR_HEADER, url) - .build() - - val response = try { - okHttpClient.newCall(request).execute() - } catch (e: Throwable) { - return@flatMap Try.Failure(e) - } + if (existingDownload != null) { + // FIXME If the first downloader cancels then we'll unfortunately be cancelled too. + return existingDownload.await() + } - if (!response.isSuccessful) { - return@flatMap Try.Failure(IOException()) - } + val result = runCatching { + val cachedFiles = withContext(coroutineDispatchers.io) { + if (!decryptedFolder.exists()) { + decryptedFolder.mkdirs() + } - val source = response.body?.source() - ?: return@flatMap Try.Failure(IOException()) + // ensure we use unique file name by using URL (mapped to suitable file name) + // Also we need to add extension for the FileProvider, if not it lot's of app that it's + // shared with will not function well (even if mime type is passed in the intent) + val cachedFiles = getFiles(url, fileName, mimeType, elementToDecrypt != null) - Timber.v("Response size ${response.body?.contentLength()} - Stream available: ${!source.exhausted()}") + if (!cachedFiles.file.exists()) { + val resolvedUrl = contentUrlResolver.resolveFullSize(url) ?: throw IllegalArgumentException("url is null") - // Write the file to cache (encrypted version if the file is encrypted) - writeToFile(source.inputStream(), cachedFiles.file) - response.close() - } else { - Timber.v("## FileService: cache hit for $url") + val request = Request.Builder() + .url(resolvedUrl) + .header(DOWNLOAD_PROGRESS_INTERCEPTOR_HEADER, url) + .build() + + val response = okHttpClient.newCall(request).execute() + + if (!response.isSuccessful) { + throw IOException() } - Try.just(cachedFiles) + val source = response.body?.source() ?: throw IOException() + + Timber.v("Response size ${response.body?.contentLength()} - Stream available: ${!source.exhausted()}") + + // Write the file to cache (encrypted version if the file is encrypted) + writeToFile(source.inputStream(), cachedFiles.file) + response.close() + } else { + Timber.v("## FileService: cache hit for $url") } - }.flatMap { cachedFiles -> - // Decrypt if necessary - if (cachedFiles.decryptedFile != null) { - if (!cachedFiles.decryptedFile.exists()) { - Timber.v("## FileService: decrypt file") - // Ensure the parent folder exists - cachedFiles.decryptedFile.parentFile?.mkdirs() - val decryptSuccess = cachedFiles.file.inputStream().use { inputStream -> - cachedFiles.decryptedFile.outputStream().buffered().use { outputStream -> - MXEncryptedAttachments.decryptAttachment( - inputStream, - elementToDecrypt, - outputStream - ) - } - } - if (!decryptSuccess) { - return@flatMap Try.Failure(IllegalStateException("Decryption error")) + cachedFiles + } + + // Decrypt if necessary + if (cachedFiles.decryptedFile != null) { + if (!cachedFiles.decryptedFile.exists()) { + Timber.v("## FileService: decrypt file") + // Ensure the parent folder exists + cachedFiles.decryptedFile.parentFile?.mkdirs() + val decryptSuccess = cachedFiles.file.inputStream().use { inputStream -> + cachedFiles.decryptedFile.outputStream().buffered().use { outputStream -> + MXEncryptedAttachments.decryptAttachment( + inputStream, + elementToDecrypt, + outputStream + ) } - } else { - Timber.v("## FileService: cache hit for decrypted file") } - Try.just(cachedFiles.decryptedFile) + if (!decryptSuccess) { + throw IllegalStateException("Decryption error") + } } else { - // Clear file - Try.just(cachedFiles.file) + Timber.v("## FileService: cache hit for decrypted file") } - }.fold( - { throwable -> - callback.onFailure(throwable) - // notify concurrent requests - val toNotify = synchronized(ongoing) { - ongoing[url]?.also { - ongoing.remove(url) - } - } - toNotify?.forEach { otherCallbacks -> - tryOrNull { otherCallbacks.onFailure(throwable) } - } - }, - { file -> - callback.onSuccess(file) - // notify concurrent requests - val toNotify = synchronized(ongoing) { - ongoing[url]?.also { - ongoing.remove(url) - } - } - Timber.v("## FileService additional to notify ${toNotify?.size ?: 0} ") - toNotify?.forEach { otherCallbacks -> - tryOrNull { otherCallbacks.onSuccess(file) } - } - } - ) - }.toCancelable() + cachedFiles.decryptedFile + } else { + // Clear file + cachedFiles.file + } + } + + // notify concurrent requests + val toNotify = synchronized(ongoing) { ongoing.remove(url) } + result.onSuccess { + Timber.v("## FileService additional to notify is > 0 ") + } + toNotify?.completeWith(result) + + return result.getOrThrow() } fun storeDataFor(mxcUrl: String, @@ -325,6 +299,7 @@ internal class DefaultFileService @Inject constructor( companion object { private const val ENCRYPTED_FILENAME = "encrypted.bin" + // The extension would be added from the mimetype private const val DEFAULT_FILENAME = "file" } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/DefaultSession.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/DefaultSession.kt index 45fcc5af2d90042a7e3fd21f1924f1bfc99f00b0..821a9cba8c58f8b24493de43c5a10232b6094e53 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/DefaultSession.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/DefaultSession.kt @@ -33,6 +33,7 @@ import org.matrix.android.sdk.api.session.call.CallSignalingService import org.matrix.android.sdk.api.session.content.ContentUploadStateTracker import org.matrix.android.sdk.api.session.content.ContentUrlResolver import org.matrix.android.sdk.api.session.crypto.CryptoService +import org.matrix.android.sdk.api.session.events.EventService import org.matrix.android.sdk.api.session.file.ContentDownloadStateTracker import org.matrix.android.sdk.api.session.file.FileService import org.matrix.android.sdk.api.session.group.GroupService @@ -114,6 +115,7 @@ internal class DefaultSession @Inject constructor( private val accountDataService: Lazy<AccountDataService>, private val _sharedSecretStorageService: Lazy<SharedSecretStorageService>, private val accountService: Lazy<AccountService>, + private val eventService: Lazy<EventService>, private val defaultIdentityService: DefaultIdentityService, private val integrationManagerService: IntegrationManagerService, private val thirdPartyService: Lazy<ThirdPartyService>, @@ -129,6 +131,7 @@ internal class DefaultSession @Inject constructor( FilterService by filterService.get(), PushRuleService by pushRuleService.get(), PushersService by pushersService.get(), + EventService by eventService.get(), TermsService by termsService.get(), InitialSyncProgressService by initialSyncProgressService.get(), SecureStorageService by secureStorageService.get(), diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/SessionModule.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/SessionModule.kt index f10eb67921d03d1c4398ed5f5bb250518d8955ab..e61e4ecd893270b597253975efa76401423d1c26 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/SessionModule.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/SessionModule.kt @@ -32,10 +32,11 @@ import org.matrix.android.sdk.api.auth.data.HomeServerConnectionConfig import org.matrix.android.sdk.api.auth.data.SessionParams import org.matrix.android.sdk.api.auth.data.sessionId import org.matrix.android.sdk.api.crypto.MXCryptoConfig -import org.matrix.android.sdk.api.session.initsync.InitialSyncProgressService import org.matrix.android.sdk.api.session.Session import org.matrix.android.sdk.api.session.accountdata.AccountDataService +import org.matrix.android.sdk.api.session.events.EventService import org.matrix.android.sdk.api.session.homeserver.HomeServerCapabilitiesService +import org.matrix.android.sdk.api.session.initsync.InitialSyncProgressService import org.matrix.android.sdk.api.session.permalinks.PermalinkService import org.matrix.android.sdk.api.session.securestorage.SecureStorageService import org.matrix.android.sdk.api.session.securestorage.SharedSecretStorageService @@ -75,6 +76,7 @@ import org.matrix.android.sdk.internal.network.token.AccessTokenProvider import org.matrix.android.sdk.internal.network.token.HomeserverAccessTokenProvider import org.matrix.android.sdk.internal.session.call.CallEventProcessor import org.matrix.android.sdk.internal.session.download.DownloadProgressInterceptor +import org.matrix.android.sdk.internal.session.events.DefaultEventService import org.matrix.android.sdk.internal.session.homeserver.DefaultHomeServerCapabilitiesService import org.matrix.android.sdk.internal.session.identity.DefaultIdentityService import org.matrix.android.sdk.internal.session.initsync.DefaultInitialSyncProgressService @@ -357,6 +359,9 @@ internal abstract class SessionModule { @Binds abstract fun bindAccountDataService(service: DefaultAccountDataService): AccountDataService + @Binds + abstract fun bindEventService(service: DefaultEventService): EventService + @Binds abstract fun bindSharedSecretStorageService(service: DefaultSharedSecretStorageService): SharedSecretStorageService diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/account/AccountAPI.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/account/AccountAPI.kt index 1db9d121a6077a8634cf70b597f9fe31a10d06f7..a04d0f2686f935de33c0f4f95ded495bbecc98e3 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/account/AccountAPI.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/account/AccountAPI.kt @@ -17,7 +17,6 @@ package org.matrix.android.sdk.internal.session.account import org.matrix.android.sdk.internal.network.NetworkConstants -import retrofit2.Call import retrofit2.http.Body import retrofit2.http.POST @@ -28,7 +27,7 @@ internal interface AccountAPI { * @param params parameters to change password. */ @POST(NetworkConstants.URI_API_PREFIX_PATH_R0 + "account/password") - fun changePassword(@Body params: ChangePasswordParams): Call<Unit> + suspend fun changePassword(@Body params: ChangePasswordParams) /** * Deactivate the user account @@ -36,5 +35,5 @@ internal interface AccountAPI { * @param params the deactivate account params */ @POST(NetworkConstants.URI_API_PREFIX_PATH_R0 + "account/deactivate") - fun deactivate(@Body params: DeactivateAccountParams): Call<Unit> + suspend fun deactivate(@Body params: DeactivateAccountParams) } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/account/ChangePasswordTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/account/ChangePasswordTask.kt index 1f043b0a9de61e1c9505a05c6f7804428fbb8116..02c3735998815b3e4c24015874fad8b47ad3c391 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/account/ChangePasswordTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/account/ChangePasswordTask.kt @@ -39,8 +39,8 @@ internal class DefaultChangePasswordTask @Inject constructor( override suspend fun execute(params: ChangePasswordTask.Params) { val changePasswordParams = ChangePasswordParams.create(userId, params.password, params.newPassword) try { - executeRequest<Unit>(globalErrorReceiver) { - apiCall = accountAPI.changePassword(changePasswordParams) + executeRequest(globalErrorReceiver) { + accountAPI.changePassword(changePasswordParams) } } catch (throwable: Throwable) { val registrationFlowResponse = throwable.toRegistrationFlowResponse() @@ -49,8 +49,8 @@ internal class DefaultChangePasswordTask @Inject constructor( /* Avoid infinite loop */ && changePasswordParams.auth?.session == null) { // Retry with authentication - executeRequest<Unit>(globalErrorReceiver) { - apiCall = accountAPI.changePassword( + executeRequest(globalErrorReceiver) { + accountAPI.changePassword( changePasswordParams.copy(auth = changePasswordParams.auth?.copy(session = registrationFlowResponse.session)) ) } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/account/DeactivateAccountTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/account/DeactivateAccountTask.kt index ca6b0554a94c2d7d10329030d71f315c2341ea59..1a8e80ab683dcb875cabc216f76548bcdca21475 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/account/DeactivateAccountTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/account/DeactivateAccountTask.kt @@ -46,8 +46,8 @@ internal class DefaultDeactivateAccountTask @Inject constructor( val deactivateAccountParams = DeactivateAccountParams.create(params.userAuthParam, params.eraseAllData) val canCleanup = try { - executeRequest<Unit>(globalErrorReceiver) { - apiCall = accountAPI.deactivate(deactivateAccountParams) + executeRequest(globalErrorReceiver) { + accountAPI.deactivate(deactivateAccountParams) } true } catch (throwable: Throwable) { diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/call/CallEventProcessor.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/call/CallEventProcessor.kt index 4887351709ffde85aa2ccdb66e03cca66de8ce1a..a190ff62acebd4687b8e2b47dd5bff3d57215d5c 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/call/CallEventProcessor.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/call/CallEventProcessor.kt @@ -21,9 +21,11 @@ import org.matrix.android.sdk.api.session.events.model.Event import org.matrix.android.sdk.api.session.events.model.EventType import org.matrix.android.sdk.internal.database.model.EventInsertType import org.matrix.android.sdk.internal.session.EventInsertLiveProcessor +import org.matrix.android.sdk.internal.session.SessionScope import timber.log.Timber import javax.inject.Inject +@SessionScope internal class CallEventProcessor @Inject constructor(private val callSignalingHandler: CallSignalingHandler) : EventInsertLiveProcessor { @@ -51,6 +53,15 @@ internal class CallEventProcessor @Inject constructor(private val callSignalingH eventsToPostProcess.add(event) } + fun shouldProcessFastLane(eventType: String): Boolean { + return eventType == EventType.CALL_INVITE + } + + suspend fun processFastLane(event: Event) { + eventsToPostProcess.add(event) + onPostProcess() + } + override suspend fun onPostProcess() { eventsToPostProcess.forEach { dispatchToCallSignalingHandlerIfNeeded(it) @@ -60,7 +71,7 @@ internal class CallEventProcessor @Inject constructor(private val callSignalingH private fun dispatchToCallSignalingHandlerIfNeeded(event: Event) { val now = System.currentTimeMillis() - // TODO might check if an invite is not closed (hangup/answsered) in the same event batch? + // TODO might check if an invite is not closed (hangup/answered) in the same event batch? event.roomId ?: return Unit.also { Timber.w("Event with no room id ${event.eventId}") } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/call/CallSignalingHandler.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/call/CallSignalingHandler.kt index 7e54301f63e7d27522e52bd1532bfc7a99d0b8d0..8d7e9e819a7bc7a95590f85622108c8797e3fc84 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/call/CallSignalingHandler.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/call/CallSignalingHandler.kt @@ -56,25 +56,25 @@ internal class CallSignalingHandler @Inject constructor(private val activeCallHa fun onCallEvent(event: Event) { when (event.getClearType()) { - EventType.CALL_ANSWER -> { + EventType.CALL_ANSWER -> { handleCallAnswerEvent(event) } - EventType.CALL_INVITE -> { + EventType.CALL_INVITE -> { handleCallInviteEvent(event) } - EventType.CALL_HANGUP -> { + EventType.CALL_HANGUP -> { handleCallHangupEvent(event) } - EventType.CALL_REJECT -> { + EventType.CALL_REJECT -> { handleCallRejectEvent(event) } - EventType.CALL_CANDIDATES -> { + EventType.CALL_CANDIDATES -> { handleCallCandidatesEvent(event) } EventType.CALL_SELECT_ANSWER -> { handleCallSelectAnswerEvent(event) } - EventType.CALL_NEGOTIATE -> { + EventType.CALL_NEGOTIATE -> { handleCallNegotiateEvent(event) } } @@ -168,6 +168,14 @@ internal class CallSignalingHandler @Inject constructor(private val activeCallHa return } val content = event.getClearContent().toModel<CallInviteContent>() ?: return + + content.callId ?: return + if (activeCallHandler.getCallWithId(content.callId) != null) { + // Call is already known, maybe due to fast lane. Ignore + Timber.d("Ignoring already known call invite") + return + } + val incomingCall = mxCallFactory.createIncomingCall( roomId = event.roomId, opponentUserId = event.senderId, diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/call/GetTurnServerTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/call/GetTurnServerTask.kt index b21ec1113aafd71299806b60983629b4a2f0437f..d53ddb737116385dde615ad9a342bfc5345ad8a0 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/call/GetTurnServerTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/call/GetTurnServerTask.kt @@ -31,7 +31,7 @@ internal class DefaultGetTurnServerTask @Inject constructor(private val voipAPI: override suspend fun execute(params: Params): TurnServerResponse { return executeRequest(globalErrorReceiver) { - apiCall = voipAPI.getTurnServer() + voipAPI.getTurnServer() } } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/call/VoipApi.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/call/VoipApi.kt index 72c6c58f2774147722098446d97eb3438d6991b7..469faaae745770b56037cb32fd947bfbf66a8a72 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/call/VoipApi.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/call/VoipApi.kt @@ -18,11 +18,10 @@ package org.matrix.android.sdk.internal.session.call import org.matrix.android.sdk.api.session.call.TurnServerResponse import org.matrix.android.sdk.internal.network.NetworkConstants -import retrofit2.Call import retrofit2.http.GET internal interface VoipApi { @GET(NetworkConstants.URI_API_PREFIX_PATH_R0 + "voip/turnServer") - fun getTurnServer(): Call<TurnServerResponse> + suspend fun getTurnServer(): TurnServerResponse } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/directory/DirectoryAPI.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/directory/DirectoryAPI.kt index 6a50f3ee37fb5b132295f41674d3578e1dd6f631..19bc7e1908bb3ddb2974d3957cc0fdabd09605e3 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/directory/DirectoryAPI.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/directory/DirectoryAPI.kt @@ -19,7 +19,6 @@ package org.matrix.android.sdk.internal.session.directory import org.matrix.android.sdk.internal.network.NetworkConstants import org.matrix.android.sdk.internal.session.room.alias.AddRoomAliasBody import org.matrix.android.sdk.internal.session.room.alias.RoomAliasDescription -import retrofit2.Call import retrofit2.http.Body import retrofit2.http.DELETE import retrofit2.http.GET @@ -33,7 +32,7 @@ internal interface DirectoryAPI { * @param roomAlias the room alias. */ @GET(NetworkConstants.URI_API_PREFIX_PATH_R0 + "directory/room/{roomAlias}") - fun getRoomIdByAlias(@Path("roomAlias") roomAlias: String): Call<RoomAliasDescription> + suspend fun getRoomIdByAlias(@Path("roomAlias") roomAlias: String): RoomAliasDescription /** * Get the room directory visibility. @@ -41,7 +40,7 @@ internal interface DirectoryAPI { * @param roomId the room id. */ @GET(NetworkConstants.URI_API_PREFIX_PATH_R0 + "directory/list/room/{roomId}") - fun getRoomDirectoryVisibility(@Path("roomId") roomId: String): Call<RoomDirectoryVisibilityJson> + suspend fun getRoomDirectoryVisibility(@Path("roomId") roomId: String): RoomDirectoryVisibilityJson /** * Set the room directory visibility. @@ -50,21 +49,21 @@ internal interface DirectoryAPI { * @param body the body containing the new directory visibility */ @PUT(NetworkConstants.URI_API_PREFIX_PATH_R0 + "directory/list/room/{roomId}") - fun setRoomDirectoryVisibility(@Path("roomId") roomId: String, - @Body body: RoomDirectoryVisibilityJson): Call<Unit> + suspend fun setRoomDirectoryVisibility(@Path("roomId") roomId: String, + @Body body: RoomDirectoryVisibilityJson) /** * Add alias to the room. * @param roomAlias the room alias. */ @PUT(NetworkConstants.URI_API_PREFIX_PATH_R0 + "directory/room/{roomAlias}") - fun addRoomAlias(@Path("roomAlias") roomAlias: String, - @Body body: AddRoomAliasBody): Call<Unit> + suspend fun addRoomAlias(@Path("roomAlias") roomAlias: String, + @Body body: AddRoomAliasBody) /** * Delete a room alias * @param roomAlias the room alias. */ @DELETE(NetworkConstants.URI_API_PREFIX_PATH_R0 + "directory/room/{roomAlias}") - fun deleteRoomAlias(@Path("roomAlias") roomAlias: String): Call<Unit> + suspend fun deleteRoomAlias(@Path("roomAlias") roomAlias: String) } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/events/DefaultEventService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/events/DefaultEventService.kt new file mode 100644 index 0000000000000000000000000000000000000000..d7e9ef2ee04b1801ee883e3b987a055a2484e988 --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/events/DefaultEventService.kt @@ -0,0 +1,40 @@ +/* + * Copyright (c) 2021 The Matrix.org Foundation C.I.C. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.matrix.android.sdk.internal.session.events + +import org.matrix.android.sdk.api.session.events.EventService +import org.matrix.android.sdk.api.session.events.model.Event +import org.matrix.android.sdk.internal.session.call.CallEventProcessor +import org.matrix.android.sdk.internal.session.room.timeline.GetEventTask +import javax.inject.Inject + +internal class DefaultEventService @Inject constructor( + private val getEventTask: GetEventTask, + private val callEventProcessor: CallEventProcessor +) : EventService { + + override suspend fun getEvent(roomId: String, eventId: String): Event { + val event = getEventTask.execute(GetEventTask.Params(roomId, eventId)) + + // Fast lane to the call event processors: try to make the incoming call ring faster + if (callEventProcessor.shouldProcessFastLane(event.getClearType())) { + callEventProcessor.processFastLane(event) + } + + return event + } +} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/events/EventExt.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/events/EventExt.kt new file mode 100644 index 0000000000000000000000000000000000000000..91e709e464eb8dbb08e2a573850940c54944b663 --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/events/EventExt.kt @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2021 The Matrix.org Foundation C.I.C. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.matrix.android.sdk.internal.session.events + +import org.matrix.android.sdk.api.session.events.model.Event +import org.matrix.android.sdk.api.session.events.model.toModel +import org.matrix.android.sdk.api.session.room.model.RoomMemberContent + +internal fun Event.getFixedRoomMemberContent(): RoomMemberContent? { + val content = content.toModel<RoomMemberContent>() + // if user is leaving, we should grab his last name and avatar from prevContent + return if (content?.membership?.isLeft() == true) { + val prevContent = resolvedPrevContent().toModel<RoomMemberContent>() + content.copy( + displayName = prevContent?.displayName, + avatarUrl = prevContent?.avatarUrl + ) + } else { + content + } +} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/filter/FilterApi.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/filter/FilterApi.kt index 285bd51d38766709ab0db4629a15417a2698ba2c..2809dea23b71b83dd2b584c9359cdc636450b068 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/filter/FilterApi.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/filter/FilterApi.kt @@ -17,7 +17,6 @@ package org.matrix.android.sdk.internal.session.filter import org.matrix.android.sdk.internal.network.NetworkConstants -import retrofit2.Call import retrofit2.http.Body import retrofit2.http.GET import retrofit2.http.POST @@ -32,8 +31,8 @@ internal interface FilterApi { * @param body the Json representation of a FilterBody object */ @POST(NetworkConstants.URI_API_PREFIX_PATH_R0 + "user/{userId}/filter") - fun uploadFilter(@Path("userId") userId: String, - @Body body: Filter): Call<FilterResponse> + suspend fun uploadFilter(@Path("userId") userId: String, + @Body body: Filter): FilterResponse /** * Gets a filter with a given filterId from the homeserver @@ -43,6 +42,6 @@ internal interface FilterApi { * @return Filter */ @GET(NetworkConstants.URI_API_PREFIX_PATH_R0 + "user/{userId}/filter/{filterId}") - fun getFilterById(@Path("userId") userId: String, - @Path("filterId") filterId: String): Call<Filter> + suspend fun getFilterById(@Path("userId") userId: String, + @Path("filterId") filterId: String): Filter } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/filter/SaveFilterTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/filter/SaveFilterTask.kt index d42962d54ace83a4874b7cc3707fec818b3eb7c5..3cac89ce285551228e8d7d8998a2e08449f8814b 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/filter/SaveFilterTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/filter/SaveFilterTask.kt @@ -59,9 +59,9 @@ internal class DefaultSaveFilterTask @Inject constructor( } val updated = filterRepository.storeFilter(filterBody, roomFilter) if (updated) { - val filterResponse = executeRequest<FilterResponse>(globalErrorReceiver) { + val filterResponse = executeRequest(globalErrorReceiver) { // TODO auto retry - apiCall = filterAPI.uploadFilter(userId, filterBody) + filterAPI.uploadFilter(userId, filterBody) } filterRepository.storeFilterId(filterBody, filterResponse.filterId) } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/group/GetGroupDataTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/group/GetGroupDataTask.kt index 9836164aecaefd84d9e5bba9c18e883846abaa25..4e0ee3422b5e44721de045f68b62fad69c8f5266 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/group/GetGroupDataTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/group/GetGroupDataTask.kt @@ -64,14 +64,14 @@ internal class DefaultGetGroupDataTask @Inject constructor( } Timber.v("Fetch data for group with ids: ${groupIds.joinToString(";")}") val data = groupIds.map { groupId -> - val groupSummary = executeRequest<GroupSummaryResponse>(globalErrorReceiver) { - apiCall = groupAPI.getSummary(groupId) + val groupSummary = executeRequest(globalErrorReceiver) { + groupAPI.getSummary(groupId) } - val groupRooms = executeRequest<GroupRooms>(globalErrorReceiver) { - apiCall = groupAPI.getRooms(groupId) + val groupRooms = executeRequest(globalErrorReceiver) { + groupAPI.getRooms(groupId) } - val groupUsers = executeRequest<GroupUsers>(globalErrorReceiver) { - apiCall = groupAPI.getUsers(groupId) + val groupUsers = executeRequest(globalErrorReceiver) { + groupAPI.getUsers(groupId) } GroupData(groupId, groupSummary, groupRooms, groupUsers) } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/group/GroupAPI.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/group/GroupAPI.kt index 004112578c9a4591b2017a4db994a5756e646b41..58dcc57dd6ab24e901e58097773890d56ff52467 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/group/GroupAPI.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/group/GroupAPI.kt @@ -20,7 +20,6 @@ import org.matrix.android.sdk.internal.network.NetworkConstants import org.matrix.android.sdk.internal.session.group.model.GroupRooms import org.matrix.android.sdk.internal.session.group.model.GroupSummaryResponse import org.matrix.android.sdk.internal.session.group.model.GroupUsers -import retrofit2.Call import retrofit2.http.GET import retrofit2.http.Path @@ -32,7 +31,7 @@ internal interface GroupAPI { * @param groupId the group id */ @GET(NetworkConstants.URI_API_PREFIX_PATH_R0 + "groups/{groupId}/summary") - fun getSummary(@Path("groupId") groupId: String): Call<GroupSummaryResponse> + suspend fun getSummary(@Path("groupId") groupId: String): GroupSummaryResponse /** * Request the rooms list. @@ -40,7 +39,7 @@ internal interface GroupAPI { * @param groupId the group id */ @GET(NetworkConstants.URI_API_PREFIX_PATH_R0 + "groups/{groupId}/rooms") - fun getRooms(@Path("groupId") groupId: String): Call<GroupRooms> + suspend fun getRooms(@Path("groupId") groupId: String): GroupRooms /** * Request the users list. @@ -48,5 +47,5 @@ internal interface GroupAPI { * @param groupId the group id */ @GET(NetworkConstants.URI_API_PREFIX_PATH_R0 + "groups/{groupId}/users") - fun getUsers(@Path("groupId") groupId: String): Call<GroupUsers> + suspend fun getUsers(@Path("groupId") groupId: String): GroupUsers } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/homeserver/CapabilitiesAPI.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/homeserver/CapabilitiesAPI.kt index 8242edac84af7c84fb6193d2b99c6191c52ff0c3..7de0cc95928c784b4466b7a0fc371b425486b569 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/homeserver/CapabilitiesAPI.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/homeserver/CapabilitiesAPI.kt @@ -18,7 +18,6 @@ package org.matrix.android.sdk.internal.session.homeserver import org.matrix.android.sdk.internal.auth.version.Versions import org.matrix.android.sdk.internal.network.NetworkConstants -import retrofit2.Call import retrofit2.http.GET internal interface CapabilitiesAPI { @@ -26,17 +25,17 @@ internal interface CapabilitiesAPI { * Request the homeserver capabilities */ @GET(NetworkConstants.URI_API_PREFIX_PATH_R0 + "capabilities") - fun getCapabilities(): Call<GetCapabilitiesResult> + suspend fun getCapabilities(): GetCapabilitiesResult /** * Request the versions */ @GET(NetworkConstants.URI_API_PREFIX_PATH_ + "versions") - fun getVersions(): Call<Versions> + suspend fun getVersions(): Versions /** * Ping the homeserver. We do not care about the returned data, so there is no use to parse them */ @GET(NetworkConstants.URI_API_PREFIX_PATH_ + "versions") - fun ping(): Call<Unit> + suspend fun ping() } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/homeserver/GetHomeServerCapabilitiesTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/homeserver/GetHomeServerCapabilitiesTask.kt index 84c9132d6136d7051b8ede3ce2904a9cd94d1cb1..740370123f877d92340e322b6a50c83eb4dd0803 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/homeserver/GetHomeServerCapabilitiesTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/homeserver/GetHomeServerCapabilitiesTask.kt @@ -71,20 +71,20 @@ internal class DefaultGetHomeServerCapabilitiesTask @Inject constructor( } val capabilities = runCatching { - executeRequest<GetCapabilitiesResult>(globalErrorReceiver) { - apiCall = capabilitiesAPI.getCapabilities() + executeRequest(globalErrorReceiver) { + capabilitiesAPI.getCapabilities() } }.getOrNull() val mediaConfig = runCatching { - executeRequest<GetMediaConfigResult>(globalErrorReceiver) { - apiCall = mediaAPI.getMediaConfig() + executeRequest(globalErrorReceiver) { + mediaAPI.getMediaConfig() } }.getOrNull() val versions = runCatching { - executeRequest<Versions>(null) { - apiCall = capabilitiesAPI.getVersions() + executeRequest(null) { + capabilitiesAPI.getVersions() } }.getOrNull() diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/homeserver/HomeServerPinger.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/homeserver/HomeServerPinger.kt index 522097acbffa4ed4c1a3132e4f5370d2a40bce72..bb526adf4a88172e2320b5fa2498005db8c1e02b 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/homeserver/HomeServerPinger.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/homeserver/HomeServerPinger.kt @@ -34,8 +34,8 @@ internal class HomeServerPinger @Inject constructor(private val taskExecutor: Ta suspend fun canReachHomeServer(): Boolean { return try { - executeRequest<Unit>(null) { - apiCall = capabilitiesAPI.ping() + executeRequest(null) { + capabilitiesAPI.ping() } true } catch (throwable: Throwable) { diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/identity/DefaultIdentityService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/identity/DefaultIdentityService.kt index 948e387cb18c47a607e2a11a77470a4e60dda6bb..f5391d6cdb113bcc3b3d1a244aabfca9d2eff3f4 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/identity/DefaultIdentityService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/identity/DefaultIdentityService.kt @@ -20,7 +20,6 @@ import androidx.lifecycle.Lifecycle import androidx.lifecycle.LifecycleOwner import androidx.lifecycle.LifecycleRegistry import dagger.Lazy -import org.matrix.android.sdk.api.MatrixCallback import org.matrix.android.sdk.api.auth.data.SessionParams import org.matrix.android.sdk.api.extensions.tryOrNull import org.matrix.android.sdk.api.failure.Failure @@ -33,8 +32,6 @@ import org.matrix.android.sdk.api.session.identity.IdentityServiceError import org.matrix.android.sdk.api.session.identity.IdentityServiceListener import org.matrix.android.sdk.api.session.identity.SharedState import org.matrix.android.sdk.api.session.identity.ThreePid -import org.matrix.android.sdk.api.util.Cancelable -import org.matrix.android.sdk.api.util.NoOpCancellable import org.matrix.android.sdk.internal.di.AuthenticatedIdentity import org.matrix.android.sdk.internal.di.UnauthenticatedWithCertificate import org.matrix.android.sdk.internal.extensions.observeNotNull @@ -49,8 +46,6 @@ import org.matrix.android.sdk.internal.session.sync.model.accountdata.IdentitySe import org.matrix.android.sdk.api.session.accountdata.UserAccountDataTypes import org.matrix.android.sdk.internal.session.user.accountdata.AccountDataDataSource import org.matrix.android.sdk.internal.session.user.accountdata.UpdateUserAccountDataTask -import org.matrix.android.sdk.internal.task.TaskExecutor -import org.matrix.android.sdk.internal.task.launchToCallback import org.matrix.android.sdk.internal.util.MatrixCoroutineDispatchers import org.matrix.android.sdk.internal.util.ensureProtocol import kotlinx.coroutines.withContext @@ -83,8 +78,7 @@ internal class DefaultIdentityService @Inject constructor( private val identityApiProvider: IdentityApiProvider, private val accountDataDataSource: AccountDataDataSource, private val homeServerCapabilitiesService: HomeServerCapabilitiesService, - private val sessionParams: SessionParams, - private val taskExecutor: TaskExecutor + private val sessionParams: SessionParams ) : IdentityService, SessionLifecycleObserver { private val lifecycleOwner: LifecycleOwner = LifecycleOwner { lifecycleRegistry } @@ -136,101 +130,81 @@ internal class DefaultIdentityService @Inject constructor( return identityStore.getIdentityData()?.identityServerUrl } - override fun startBindThreePid(threePid: ThreePid, callback: MatrixCallback<Unit>): Cancelable { + override suspend fun startBindThreePid(threePid: ThreePid) { if (homeServerCapabilitiesService.getHomeServerCapabilities().lastVersionIdentityServerSupported.not()) { - callback.onFailure(IdentityServiceError.OutdatedHomeServer) - return NoOpCancellable + throw IdentityServiceError.OutdatedHomeServer } - return taskExecutor.executorScope.launchToCallback(coroutineDispatchers.main, callback) { - identityRequestTokenForBindingTask.execute(IdentityRequestTokenForBindingTask.Params(threePid, false)) - } + identityRequestTokenForBindingTask.execute(IdentityRequestTokenForBindingTask.Params(threePid, false)) } - override fun cancelBindThreePid(threePid: ThreePid, callback: MatrixCallback<Unit>): Cancelable { - return taskExecutor.executorScope.launchToCallback(coroutineDispatchers.main, callback) { - identityStore.deletePendingBinding(threePid) - } + override suspend fun cancelBindThreePid(threePid: ThreePid) { + identityStore.deletePendingBinding(threePid) } - override fun sendAgainValidationCode(threePid: ThreePid, callback: MatrixCallback<Unit>): Cancelable { - return taskExecutor.executorScope.launchToCallback(coroutineDispatchers.main, callback) { - identityRequestTokenForBindingTask.execute(IdentityRequestTokenForBindingTask.Params(threePid, true)) - } + override suspend fun sendAgainValidationCode(threePid: ThreePid) { + identityRequestTokenForBindingTask.execute(IdentityRequestTokenForBindingTask.Params(threePid, true)) } - override fun finalizeBindThreePid(threePid: ThreePid, callback: MatrixCallback<Unit>): Cancelable { + override suspend fun finalizeBindThreePid(threePid: ThreePid) { if (homeServerCapabilitiesService.getHomeServerCapabilities().lastVersionIdentityServerSupported.not()) { - callback.onFailure(IdentityServiceError.OutdatedHomeServer) - return NoOpCancellable + throw IdentityServiceError.OutdatedHomeServer } - return taskExecutor.executorScope.launchToCallback(coroutineDispatchers.main, callback) { - bindThreePidsTask.execute(BindThreePidsTask.Params(threePid)) - } + bindThreePidsTask.execute(BindThreePidsTask.Params(threePid)) } - override fun submitValidationToken(threePid: ThreePid, code: String, callback: MatrixCallback<Unit>): Cancelable { - return taskExecutor.executorScope.launchToCallback(coroutineDispatchers.main, callback) { - submitTokenForBindingTask.execute(IdentitySubmitTokenForBindingTask.Params(threePid, code)) - } + override suspend fun submitValidationToken(threePid: ThreePid, code: String) { + submitTokenForBindingTask.execute(IdentitySubmitTokenForBindingTask.Params(threePid, code)) } - override fun unbindThreePid(threePid: ThreePid, callback: MatrixCallback<Unit>): Cancelable { + override suspend fun unbindThreePid(threePid: ThreePid) { if (homeServerCapabilitiesService.getHomeServerCapabilities().lastVersionIdentityServerSupported.not()) { - callback.onFailure(IdentityServiceError.OutdatedHomeServer) - return NoOpCancellable + throw IdentityServiceError.OutdatedHomeServer } - return taskExecutor.executorScope.launchToCallback(coroutineDispatchers.main, callback) { - unbindThreePidsTask.execute(UnbindThreePidsTask.Params(threePid)) - } + unbindThreePidsTask.execute(UnbindThreePidsTask.Params(threePid)) } - override fun isValidIdentityServer(url: String, callback: MatrixCallback<Unit>): Cancelable { - return taskExecutor.executorScope.launchToCallback(coroutineDispatchers.main, callback) { - val api = retrofitFactory.create(unauthenticatedOkHttpClient, url).create(IdentityAuthAPI::class.java) + override suspend fun isValidIdentityServer(url: String) { + val api = retrofitFactory.create(unauthenticatedOkHttpClient, url).create(IdentityAuthAPI::class.java) - identityPingTask.execute(IdentityPingTask.Params(api)) - } + identityPingTask.execute(IdentityPingTask.Params(api)) } - override fun disconnect(callback: MatrixCallback<Unit>): Cancelable { - return taskExecutor.executorScope.launchToCallback(coroutineDispatchers.main, callback) { - identityDisconnectTask.execute(Unit) + override suspend fun disconnect() { + identityDisconnectTask.execute(Unit) - identityStore.setUrl(null) - updateIdentityAPI(null) - updateAccountData(null) - } + identityStore.setUrl(null) + updateIdentityAPI(null) + updateAccountData(null) } - override fun setNewIdentityServer(url: String, callback: MatrixCallback<String>): Cancelable { + override suspend fun setNewIdentityServer(url: String): String { val urlCandidate = url.ensureProtocol() - return taskExecutor.executorScope.launchToCallback(coroutineDispatchers.main, callback) { - val current = getCurrentIdentityServerUrl() - if (urlCandidate == current) { - // Nothing to do - Timber.d("Same URL, nothing to do") - } else { - // Disconnect previous one if any, first, because the token will change. - // In case of error when configuring the new identity server, this is not a big deal, - // we will ask for a new token on the previous Identity server - runCatching { identityDisconnectTask.execute(Unit) } - .onFailure { Timber.w(it, "Unable to disconnect identity server") } + val current = getCurrentIdentityServerUrl() + if (urlCandidate == current) { + // Nothing to do + Timber.d("Same URL, nothing to do") + } else { + // Disconnect previous one if any, first, because the token will change. + // In case of error when configuring the new identity server, this is not a big deal, + // we will ask for a new token on the previous Identity server + runCatching { identityDisconnectTask.execute(Unit) } + .onFailure { Timber.w(it, "Unable to disconnect identity server") } - // Try to get a token - val token = getNewIdentityServerToken(urlCandidate) + // Try to get a token + val token = getNewIdentityServerToken(urlCandidate) - identityStore.setUrl(urlCandidate) - identityStore.setToken(token) - updateIdentityAPI(urlCandidate) + identityStore.setUrl(urlCandidate) + identityStore.setToken(token) + updateIdentityAPI(urlCandidate) - updateAccountData(urlCandidate) - } - urlCandidate + updateAccountData(urlCandidate) } + + return urlCandidate } private suspend fun updateAccountData(url: String?) { @@ -252,45 +226,38 @@ internal class DefaultIdentityService @Inject constructor( identityStore.setUserConsent(newValue) } - override fun lookUp(threePids: List<ThreePid>, callback: MatrixCallback<List<FoundThreePid>>): Cancelable { + override suspend fun lookUp(threePids: List<ThreePid>): List<FoundThreePid> { if (!getUserConsent()) { - callback.onFailure(IdentityServiceError.UserConsentNotProvided) - return NoOpCancellable + throw IdentityServiceError.UserConsentNotProvided } if (threePids.isEmpty()) { - callback.onSuccess(emptyList()) - return NoOpCancellable + return emptyList() } - return taskExecutor.executorScope.launchToCallback(coroutineDispatchers.main, callback) { - lookUpInternal(true, threePids) - } + return lookUpInternal(true, threePids) } - override fun getShareStatus(threePids: List<ThreePid>, callback: MatrixCallback<Map<ThreePid, SharedState>>): Cancelable { + override suspend fun getShareStatus(threePids: List<ThreePid>): Map<ThreePid, SharedState> { // Note: we do not require user consent here, because it is used for emails and phone numbers that the user has already sent // to the home server, and not emails and phone numbers from the contact book of the user if (threePids.isEmpty()) { - callback.onSuccess(emptyMap()) - return NoOpCancellable + return emptyMap() } - return taskExecutor.executorScope.launchToCallback(coroutineDispatchers.main, callback) { - val lookupResult = lookUpInternal(true, threePids) - - threePids.associateWith { threePid -> - // If not in lookup result, check if there is a pending binding - if (lookupResult.firstOrNull { it.threePid == threePid } == null) { - if (identityStore.getPendingBinding(threePid) == null) { - SharedState.NOT_SHARED - } else { - SharedState.BINDING_IN_PROGRESS - } + val lookupResult = lookUpInternal(true, threePids) + + return threePids.associateWith { threePid -> + // If not in lookup result, check if there is a pending binding + if (lookupResult.firstOrNull { it.threePid == threePid } == null) { + if (identityStore.getPendingBinding(threePid) == null) { + SharedState.NOT_SHARED } else { - SharedState.SHARED + SharedState.BINDING_IN_PROGRESS } + } else { + SharedState.SHARED } } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/identity/IdentityAPI.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/identity/IdentityAPI.kt index 7e2702e70dfd1f54258d07672015eccbe6812ee1..e9e4d17e3a72727af7a3fda3ca8861dee0be11f8 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/identity/IdentityAPI.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/identity/IdentityAPI.kt @@ -26,7 +26,6 @@ import org.matrix.android.sdk.internal.session.identity.model.IdentityRequestOwn import org.matrix.android.sdk.internal.session.identity.model.IdentityRequestTokenForEmailBody import org.matrix.android.sdk.internal.session.identity.model.IdentityRequestTokenForMsisdnBody import org.matrix.android.sdk.internal.session.identity.model.IdentityRequestTokenResponse -import retrofit2.Call import retrofit2.http.Body import retrofit2.http.GET import retrofit2.http.POST @@ -43,20 +42,20 @@ internal interface IdentityAPI { * Ref: https://matrix.org/docs/spec/identity_service/latest#get-matrix-identity-v2-account */ @GET(NetworkConstants.URI_IDENTITY_PATH_V2 + "account") - fun getAccount(): Call<IdentityAccountResponse> + suspend fun getAccount(): IdentityAccountResponse /** * Logs out the access token, preventing it from being used to authenticate future requests to the server. */ @POST(NetworkConstants.URI_IDENTITY_PATH_V2 + "account/logout") - fun logout(): Call<Unit> + suspend fun logout() /** * Request the hash detail to request a bunch of 3PIDs * Ref: https://matrix.org/docs/spec/identity_service/latest#get-matrix-identity-v2-hash-details */ @GET(NetworkConstants.URI_IDENTITY_PATH_V2 + "hash_details") - fun hashDetails(): Call<IdentityHashDetailResponse> + suspend fun hashDetails(): IdentityHashDetailResponse /** * Request a bunch of 3PIDs @@ -65,7 +64,7 @@ internal interface IdentityAPI { * @param body the body request */ @POST(NetworkConstants.URI_IDENTITY_PATH_V2 + "lookup") - fun lookup(@Body body: IdentityLookUpParams): Call<IdentityLookUpResponse> + suspend fun lookup(@Body body: IdentityLookUpParams): IdentityLookUpResponse /** * Create a session to change the bind status of an email to an identity server @@ -75,7 +74,7 @@ internal interface IdentityAPI { * @return the sid */ @POST(NetworkConstants.URI_IDENTITY_PATH_V2 + "validate/email/requestToken") - fun requestTokenToBindEmail(@Body body: IdentityRequestTokenForEmailBody): Call<IdentityRequestTokenResponse> + suspend fun requestTokenToBindEmail(@Body body: IdentityRequestTokenForEmailBody): IdentityRequestTokenResponse /** * Create a session to change the bind status of an phone number to an identity server @@ -85,7 +84,7 @@ internal interface IdentityAPI { * @return the sid */ @POST(NetworkConstants.URI_IDENTITY_PATH_V2 + "validate/msisdn/requestToken") - fun requestTokenToBindMsisdn(@Body body: IdentityRequestTokenForMsisdnBody): Call<IdentityRequestTokenResponse> + suspend fun requestTokenToBindMsisdn(@Body body: IdentityRequestTokenForMsisdnBody): IdentityRequestTokenResponse /** * Validate ownership of an email address, or a phone number. @@ -94,6 +93,6 @@ internal interface IdentityAPI { * - https://matrix.org/docs/spec/identity_service/latest#post-matrix-identity-v2-validate-email-submittoken */ @POST(NetworkConstants.URI_IDENTITY_PATH_V2 + "validate/{medium}/submitToken") - fun submitToken(@Path("medium") medium: String, - @Body body: IdentityRequestOwnershipParams): Call<SuccessResult> + suspend fun submitToken(@Path("medium") medium: String, + @Body body: IdentityRequestOwnershipParams): SuccessResult } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/identity/IdentityAuthAPI.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/identity/IdentityAuthAPI.kt index fd6e1163ef79fbd6fc208951139b10bd473dbbb6..1671859585f1d35cff1f218d1df1d69dae8ff8ae 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/identity/IdentityAuthAPI.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/identity/IdentityAuthAPI.kt @@ -19,7 +19,6 @@ package org.matrix.android.sdk.internal.session.identity import org.matrix.android.sdk.internal.network.NetworkConstants import org.matrix.android.sdk.internal.session.identity.model.IdentityRegisterResponse import org.matrix.android.sdk.internal.session.openid.RequestOpenIdTokenResponse -import retrofit2.Call import retrofit2.http.Body import retrofit2.http.GET import retrofit2.http.POST @@ -40,18 +39,18 @@ internal interface IdentityAuthAPI { * @return 200 in case of success */ @GET(NetworkConstants.URI_IDENTITY_PREFIX_PATH) - fun ping(): Call<Unit> + suspend fun ping() /** * Ping v1 will be used to check outdated Identity server */ @GET("_matrix/identity/api/v1") - fun pingV1(): Call<Unit> + suspend fun pingV1() /** * Exchanges an OpenID token from the homeserver for an access token to access the identity server. * The request body is the same as the values returned by /openid/request_token in the Client-Server API. */ @POST(NetworkConstants.URI_IDENTITY_PATH_V2 + "account/register") - fun register(@Body openIdToken: RequestOpenIdTokenResponse): Call<IdentityRegisterResponse> + suspend fun register(@Body openIdToken: RequestOpenIdTokenResponse): IdentityRegisterResponse } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/identity/IdentityBulkLookupTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/identity/IdentityBulkLookupTask.kt index 67f3b2aa56952c02e80e9d5d17556a0653fa4fad..4f6e906766801806c67a0d6cc154b1d25fff4828 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/identity/IdentityBulkLookupTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/identity/IdentityBulkLookupTask.kt @@ -83,7 +83,7 @@ internal class DefaultIdentityBulkLookupTask @Inject constructor( return try { LookUpData(hashedAddresses, executeRequest(null) { - apiCall = identityAPI.lookup(IdentityLookUpParams( + identityAPI.lookup(IdentityLookUpParams( hashedAddresses, IdentityHashDetailResponse.ALGORITHM_SHA256, hashDetailResponse.pepper @@ -126,7 +126,7 @@ internal class DefaultIdentityBulkLookupTask @Inject constructor( private suspend fun fetchHashDetails(identityAPI: IdentityAPI): IdentityHashDetailResponse { return executeRequest(null) { - apiCall = identityAPI.hashDetails() + identityAPI.hashDetails() } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/identity/IdentityDisconnectTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/identity/IdentityDisconnectTask.kt index 50e24f1245ab44bf02fb35f3725fa7b31a13f359..fc84a144fed99807439c8bccfe0651662a55aa40 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/identity/IdentityDisconnectTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/identity/IdentityDisconnectTask.kt @@ -42,8 +42,8 @@ internal class DefaultIdentityDisconnectTask @Inject constructor( return } - executeRequest<Unit>(null) { - apiCall = identityAPI.logout() + executeRequest(null) { + identityAPI.logout() } } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/identity/IdentityPingTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/identity/IdentityPingTask.kt index b0d33811bdcb9ef78b467bcf6b9a2e8c775b0ee4..fca9408d9cf8af498b89606448f07c8de4e7e45d 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/identity/IdentityPingTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/identity/IdentityPingTask.kt @@ -33,14 +33,14 @@ internal class DefaultIdentityPingTask @Inject constructor() : IdentityPingTask override suspend fun execute(params: IdentityPingTask.Params) { try { - executeRequest<Unit>(null) { - apiCall = params.identityAuthAPI.ping() + executeRequest(null) { + params.identityAuthAPI.ping() } } catch (throwable: Throwable) { if (throwable is Failure.ServerError && throwable.httpCode == HttpsURLConnection.HTTP_NOT_FOUND /* 404 */) { // Check if API v1 is available - executeRequest<Unit>(null) { - apiCall = params.identityAuthAPI.pingV1() + executeRequest(null) { + params.identityAuthAPI.pingV1() } // API V1 is responding, but not V2 -> Outdated throw IdentityServiceError.OutdatedIdentityServer diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/identity/IdentityRegisterTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/identity/IdentityRegisterTask.kt index 19215f353ac9919e430d2a5e77add335690f878d..8cc854bd940daa821d0f785db5fb31375aafa547 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/identity/IdentityRegisterTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/identity/IdentityRegisterTask.kt @@ -33,7 +33,7 @@ internal class DefaultIdentityRegisterTask @Inject constructor() : IdentityRegis override suspend fun execute(params: IdentityRegisterTask.Params): IdentityRegisterResponse { return executeRequest(null) { - apiCall = params.identityAuthAPI.register(params.openIdTokenResponse) + params.identityAuthAPI.register(params.openIdTokenResponse) } } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/identity/IdentityRequestTokenForBindingTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/identity/IdentityRequestTokenForBindingTask.kt index bd4cd763f00233d295f66e816958dd69f901f93b..9c89048176f6d8d918f324e6bf34845d882524dc 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/identity/IdentityRequestTokenForBindingTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/identity/IdentityRequestTokenForBindingTask.kt @@ -25,7 +25,6 @@ import org.matrix.android.sdk.internal.session.identity.data.IdentityPendingBind import org.matrix.android.sdk.internal.session.identity.data.IdentityStore import org.matrix.android.sdk.internal.session.identity.model.IdentityRequestTokenForEmailBody import org.matrix.android.sdk.internal.session.identity.model.IdentityRequestTokenForMsisdnBody -import org.matrix.android.sdk.internal.session.identity.model.IdentityRequestTokenResponse import org.matrix.android.sdk.internal.task.Task import java.util.UUID import javax.inject.Inject @@ -56,8 +55,8 @@ internal class DefaultIdentityRequestTokenForBindingTask @Inject constructor( val clientSecret = identityPendingBinding?.clientSecret ?: UUID.randomUUID().toString() val sendAttempt = identityPendingBinding?.sendAttempt?.inc() ?: 1 - val tokenResponse = executeRequest<IdentityRequestTokenResponse>(null) { - apiCall = when (params.threePid) { + val tokenResponse = executeRequest(null) { + when (params.threePid) { is ThreePid.Email -> identityAPI.requestTokenToBindEmail(IdentityRequestTokenForEmailBody( clientSecret = clientSecret, sendAttempt = sendAttempt, diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/identity/IdentitySubmitTokenForBindingTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/identity/IdentitySubmitTokenForBindingTask.kt index ebc71c715df27f6922ae43e409cdd5104656750d..f884e2816dccfb9e3e63b572e1976b3a83f995cb 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/identity/IdentitySubmitTokenForBindingTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/identity/IdentitySubmitTokenForBindingTask.kt @@ -19,7 +19,6 @@ package org.matrix.android.sdk.internal.session.identity import org.matrix.android.sdk.api.session.identity.IdentityServiceError import org.matrix.android.sdk.api.session.identity.ThreePid import org.matrix.android.sdk.api.session.identity.toMedium -import org.matrix.android.sdk.internal.auth.registration.SuccessResult import org.matrix.android.sdk.internal.di.UserId import org.matrix.android.sdk.internal.network.executeRequest import org.matrix.android.sdk.internal.session.identity.data.IdentityStore @@ -44,8 +43,8 @@ internal class DefaultIdentitySubmitTokenForBindingTask @Inject constructor( val identityAPI = getIdentityApiAndEnsureTerms(identityApiProvider, userId) val identityPendingBinding = identityStore.getPendingBinding(params.threePid) ?: throw IdentityServiceError.NoCurrentBindingError - val tokenResponse = executeRequest<SuccessResult>(null) { - apiCall = identityAPI.submitToken( + val tokenResponse = executeRequest(null) { + identityAPI.submitToken( params.threePid.toMedium(), IdentityRequestOwnershipParams( clientSecret = identityPendingBinding.clientSecret, diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/identity/IdentityTaskHelper.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/identity/IdentityTaskHelper.kt index d3aecce38162f76ff7535f0629ff04719f8f9327..d06b157f226884416ad771c5f7f7de985a5c7dc9 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/identity/IdentityTaskHelper.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/identity/IdentityTaskHelper.kt @@ -18,14 +18,13 @@ package org.matrix.android.sdk.internal.session.identity import org.matrix.android.sdk.api.session.identity.IdentityServiceError import org.matrix.android.sdk.internal.network.executeRequest -import org.matrix.android.sdk.internal.session.identity.model.IdentityAccountResponse internal suspend fun getIdentityApiAndEnsureTerms(identityApiProvider: IdentityApiProvider, userId: String): IdentityAPI { val identityAPI = identityApiProvider.identityApi ?: throw IdentityServiceError.NoIdentityServerConfigured // Always check that we have access to the service (regarding terms) - val identityAccountResponse = executeRequest<IdentityAccountResponse>(null) { - apiCall = identityAPI.getAccount() + val identityAccountResponse = executeRequest(null) { + identityAPI.getAccount() } assert(userId == identityAccountResponse.userId) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/media/GetPreviewUrlTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/media/GetPreviewUrlTask.kt index d85e471f1d1945ef42a87793c928df6f488a76c8..e707c2351c1edc7d055d1dd5eb88c428e0cc246d 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/media/GetPreviewUrlTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/media/GetPreviewUrlTask.kt @@ -65,8 +65,8 @@ internal class DefaultGetPreviewUrlTask @Inject constructor( } private suspend fun doRequest(url: String, timestamp: Long?): PreviewUrlData { - return executeRequest<JsonDict>(globalErrorReceiver) { - apiCall = mediaAPI.getPreviewUrlData(url, timestamp) + return executeRequest(globalErrorReceiver) { + mediaAPI.getPreviewUrlData(url, timestamp) } .toPreviewUrlData(url) } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/media/GetRawPreviewUrlTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/media/GetRawPreviewUrlTask.kt index 32305cd4e405419470c8e1a4d9a392c849d008e9..fd906f0dc8a6d8aa708dbe9f72b416bb0e67ee14 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/media/GetRawPreviewUrlTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/media/GetRawPreviewUrlTask.kt @@ -36,7 +36,7 @@ internal class DefaultGetRawPreviewUrlTask @Inject constructor( override suspend fun execute(params: GetRawPreviewUrlTask.Params): JsonDict { return executeRequest(globalErrorReceiver) { - apiCall = mediaAPI.getPreviewUrlData(params.url, params.timestamp) + mediaAPI.getPreviewUrlData(params.url, params.timestamp) } } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/media/MediaAPI.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/media/MediaAPI.kt index bbb4f1e06a17c9068fd2e44b443ddd11a517538f..9ee1d26cdcac6a09150356b20b512b6879814e0b 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/media/MediaAPI.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/media/MediaAPI.kt @@ -18,7 +18,6 @@ package org.matrix.android.sdk.internal.session.media import org.matrix.android.sdk.api.util.JsonDict import org.matrix.android.sdk.internal.network.NetworkConstants -import retrofit2.Call import retrofit2.http.GET import retrofit2.http.Query @@ -28,7 +27,7 @@ internal interface MediaAPI { * Ref: https://matrix.org/docs/spec/client_server/r0.6.1#get-matrix-media-r0-config */ @GET(NetworkConstants.URI_API_MEDIA_PREFIX_PATH_R0 + "config") - fun getMediaConfig(): Call<GetMediaConfigResult> + suspend fun getMediaConfig(): GetMediaConfigResult /** * Get information about a URL for the client. Typically this is called when a client @@ -39,5 +38,5 @@ internal interface MediaAPI { * if it does not have the requested version available. */ @GET(NetworkConstants.URI_API_MEDIA_PREFIX_PATH_R0 + "preview_url") - fun getPreviewUrlData(@Query("url") url: String, @Query("ts") ts: Long?): Call<JsonDict> + suspend fun getPreviewUrlData(@Query("url") url: String, @Query("ts") ts: Long?): JsonDict } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/notification/DefaultPushRuleService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/notification/DefaultPushRuleService.kt index e00d2ff26cf1fbdfc01f423314052dc7c1735807..38f6b08b43b39d9eba976a7820f0faaeb0f6652a 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/notification/DefaultPushRuleService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/notification/DefaultPushRuleService.kt @@ -16,8 +16,10 @@ package org.matrix.android.sdk.internal.session.notification import com.zhuinden.monarchy.Monarchy +import org.matrix.android.sdk.api.pushrules.Action import org.matrix.android.sdk.api.pushrules.PushRuleService import org.matrix.android.sdk.api.pushrules.RuleKind +import org.matrix.android.sdk.api.pushrules.RuleScope import org.matrix.android.sdk.api.pushrules.RuleSetKey import org.matrix.android.sdk.api.pushrules.getActions import org.matrix.android.sdk.api.pushrules.rest.PushRule @@ -45,6 +47,7 @@ internal class DefaultPushRuleService @Inject constructor( private val addPushRuleTask: AddPushRuleTask, private val updatePushRuleActionsTask: UpdatePushRuleActionsTask, private val removePushRuleTask: RemovePushRuleTask, + private val pushRuleFinder: PushRuleFinder, private val taskExecutor: TaskExecutor, @SessionDatabase private val monarchy: Monarchy ) : PushRuleService { @@ -130,6 +133,12 @@ internal class DefaultPushRuleService @Inject constructor( } } + override fun getActions(event: Event): List<Action> { + val rules = getPushRules(RuleScope.GLOBAL).getAllRules() + + return pushRuleFinder.fulfilledBingRule(event, rules)?.getActions().orEmpty() + } + // fun processEvents(events: List<Event>) { // var hasDoneSomething = false // events.forEach { event -> 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 54883b51e663a0672d3500d00050780d90e1675f..0ece07fc1574eb72b2390989c25a91c3d0e742fe 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 @@ -16,9 +16,7 @@ package org.matrix.android.sdk.internal.session.notification -import org.matrix.android.sdk.api.pushrules.ConditionResolver import org.matrix.android.sdk.api.pushrules.rest.PushRule -import org.matrix.android.sdk.api.session.events.model.Event import org.matrix.android.sdk.api.session.events.model.EventType import org.matrix.android.sdk.internal.di.UserId import org.matrix.android.sdk.internal.session.sync.model.RoomsSyncResponse @@ -35,7 +33,7 @@ internal interface ProcessEventForPushTask : Task<ProcessEventForPushTask.Params internal class DefaultProcessEventForPushTask @Inject constructor( private val defaultPushRuleService: DefaultPushRuleService, - private val conditionResolver: ConditionResolver, + private val pushRuleFinder: PushRuleFinder, @UserId private val userId: String ) : ProcessEventForPushTask { @@ -72,7 +70,7 @@ internal class DefaultProcessEventForPushTask @Inject constructor( Timber.v("[PushRules] Found ${allEvents.size} out of ${(newJoinEvents + inviteEvents).size}" + " to check for push rules with ${params.rules.size} rules") allEvents.forEach { event -> - fulfilledBingRule(event, params.rules)?.let { + pushRuleFinder.fulfilledBingRule(event, params.rules)?.let { Timber.v("[PushRules] Rule $it match for event ${event.eventId}") defaultPushRuleService.dispatchBing(event, it) } @@ -94,13 +92,4 @@ internal class DefaultProcessEventForPushTask @Inject constructor( defaultPushRuleService.dispatchFinish() } - - private fun fulfilledBingRule(event: Event, rules: List<PushRule>): PushRule? { - return rules.firstOrNull { rule -> - // All conditions must hold true for an event in order to apply the action for the event. - rule.enabled && rule.conditions?.all { - it.asExecutableCondition(rule)?.isSatisfied(event, conditionResolver) ?: false - } ?: false - } - } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/notification/PushRuleFinder.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/notification/PushRuleFinder.kt new file mode 100644 index 0000000000000000000000000000000000000000..6e302d373d819dfe5944685165bd39c06200c729 --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/notification/PushRuleFinder.kt @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2021 The Matrix.org Foundation C.I.C. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.matrix.android.sdk.internal.session.notification + +import org.matrix.android.sdk.api.pushrules.ConditionResolver +import org.matrix.android.sdk.api.pushrules.rest.PushRule +import org.matrix.android.sdk.api.session.events.model.Event +import javax.inject.Inject + +internal class PushRuleFinder @Inject constructor( + private val conditionResolver: ConditionResolver +) { + fun fulfilledBingRule(event: Event, rules: List<PushRule>): PushRule? { + return rules.firstOrNull { rule -> + // All conditions must hold true for an event in order to apply the action for the event. + rule.enabled && rule.conditions?.all { + it.asExecutableCondition(rule)?.isSatisfied(event, conditionResolver) ?: false + } ?: false + } + } +} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/openid/GetOpenIdTokenTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/openid/GetOpenIdTokenTask.kt index f83c6b770af5fa3883d84f73c6163bfebf8e6cb9..8481a6ab93d6eaec9e1af4b454c8f86746b6d11d 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/openid/GetOpenIdTokenTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/openid/GetOpenIdTokenTask.kt @@ -31,7 +31,7 @@ internal class DefaultGetOpenIdTokenTask @Inject constructor( override suspend fun execute(params: Unit): RequestOpenIdTokenResponse { return executeRequest(globalErrorReceiver) { - apiCall = openIdAPI.openIdToken(userId) + openIdAPI.openIdToken(userId) } } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/openid/OpenIdAPI.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/openid/OpenIdAPI.kt index 4614d824536e1260806eac913f61452b5e989fcf..ed090b845d02888f912ede1657b20bab8ce540c9 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/openid/OpenIdAPI.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/openid/OpenIdAPI.kt @@ -18,7 +18,6 @@ package org.matrix.android.sdk.internal.session.openid import org.matrix.android.sdk.api.util.JsonDict import org.matrix.android.sdk.internal.network.NetworkConstants -import retrofit2.Call import retrofit2.http.Body import retrofit2.http.POST import retrofit2.http.Path @@ -34,6 +33,6 @@ internal interface OpenIdAPI { * @param userId the user id */ @POST(NetworkConstants.URI_API_PREFIX_PATH_R0 + "user/{userId}/openid/request_token") - fun openIdToken(@Path("userId") userId: String, - @Body body: JsonDict = emptyMap()): Call<RequestOpenIdTokenResponse> + suspend fun openIdToken(@Path("userId") userId: String, + @Body body: JsonDict = emptyMap()): RequestOpenIdTokenResponse } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/profile/AddThreePidTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/profile/AddThreePidTask.kt index 6d6d70bb0d5d51a84200bfee1d6f78bb99cdc57f..678d3994284c65dccdc033e9aeebeab2a6900447 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/profile/AddThreePidTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/profile/AddThreePidTask.kt @@ -50,13 +50,14 @@ internal class DefaultAddThreePidTask @Inject constructor( val clientSecret = UUID.randomUUID().toString() val sendAttempt = 1 - val result = executeRequest<AddEmailResponse>(globalErrorReceiver) { - val body = AddEmailBody( - clientSecret = clientSecret, - email = threePid.email, - sendAttempt = sendAttempt - ) - apiCall = profileAPI.addEmail(body) + val body = AddEmailBody( + clientSecret = clientSecret, + email = threePid.email, + sendAttempt = sendAttempt + ) + + val result = executeRequest(globalErrorReceiver) { + profileAPI.addEmail(body) } // Store as a pending three pid @@ -84,14 +85,15 @@ internal class DefaultAddThreePidTask @Inject constructor( val countryCode = parsedNumber.countryCode val country = phoneNumberUtil.getRegionCodeForCountryCode(countryCode) - val result = executeRequest<AddMsisdnResponse>(globalErrorReceiver) { - val body = AddMsisdnBody( - clientSecret = clientSecret, - country = country, - phoneNumber = parsedNumber.nationalNumber.toString(), - sendAttempt = sendAttempt - ) - apiCall = profileAPI.addMsisdn(body) + val body = AddMsisdnBody( + clientSecret = clientSecret, + country = country, + phoneNumber = parsedNumber.nationalNumber.toString(), + sendAttempt = sendAttempt + ) + + val result = executeRequest(globalErrorReceiver) { + profileAPI.addMsisdn(body) } // Store as a pending three pid diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/profile/BindThreePidsTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/profile/BindThreePidsTask.kt index a37e5380bcc9a0cdeb6b8d5159dafd98f18b3401..87e51181e636952d757bb71c92607f06ef3b5447 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/profile/BindThreePidsTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/profile/BindThreePidsTask.kt @@ -43,8 +43,8 @@ internal class DefaultBindThreePidsTask @Inject constructor(private val profileA val identityServerAccessToken = accessTokenProvider.getToken() ?: throw IdentityServiceError.NoIdentityServerConfigured val identityPendingBinding = identityStore.getPendingBinding(params.threePid) ?: throw IdentityServiceError.NoCurrentBindingError - executeRequest<Unit>(globalErrorReceiver) { - apiCall = profileAPI.bindThreePid( + executeRequest(globalErrorReceiver) { + profileAPI.bindThreePid( BindThreePidBody( clientSecret = identityPendingBinding.clientSecret, identityServerUrlWithoutProtocol = identityServerUrlWithoutProtocol, diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/profile/DefaultProfileService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/profile/DefaultProfileService.kt index b3216d744d4bbf006cbbd1dc2a4905f9713715b5..386fec8256f62d8b13fbe71c693c2f1fccf9d9da 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/profile/DefaultProfileService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/profile/DefaultProfileService.kt @@ -21,11 +21,10 @@ import android.net.Uri import androidx.lifecycle.LiveData import com.zhuinden.monarchy.Monarchy import io.realm.kotlin.where -import org.matrix.android.sdk.api.MatrixCallback +import kotlinx.coroutines.withContext import org.matrix.android.sdk.api.auth.UserInteractiveAuthInterceptor import org.matrix.android.sdk.api.session.identity.ThreePid import org.matrix.android.sdk.api.session.profile.ProfileService -import org.matrix.android.sdk.api.util.Cancelable import org.matrix.android.sdk.api.util.JsonDict import org.matrix.android.sdk.api.util.MimeTypes import org.matrix.android.sdk.api.util.Optional @@ -36,7 +35,6 @@ import org.matrix.android.sdk.internal.session.content.FileUploader import org.matrix.android.sdk.internal.session.user.UserStore import org.matrix.android.sdk.internal.task.TaskExecutor import org.matrix.android.sdk.internal.task.configureWith -import org.matrix.android.sdk.internal.task.launchToCallback import org.matrix.android.sdk.internal.util.MatrixCoroutineDispatchers import javax.inject.Inject @@ -55,64 +53,38 @@ internal class DefaultProfileService @Inject constructor(private val taskExecuto private val userStore: UserStore, private val fileUploader: FileUploader) : ProfileService { - override fun getDisplayName(userId: String, matrixCallback: MatrixCallback<Optional<String>>): Cancelable { + override suspend fun getDisplayName(userId: String): Optional<String> { val params = GetProfileInfoTask.Params(userId) - return getProfileInfoTask - .configureWith(params) { - this.callback = object : MatrixCallback<JsonDict> { - override fun onSuccess(data: JsonDict) { - val displayName = data[ProfileService.DISPLAY_NAME_KEY] as? String - matrixCallback.onSuccess(Optional.from(displayName)) - } - - override fun onFailure(failure: Throwable) { - matrixCallback.onFailure(failure) - } - } - } - .executeBy(taskExecutor) + val data = getProfileInfoTask.execute(params) + val displayName = data[ProfileService.DISPLAY_NAME_KEY] as? String + return Optional.from(displayName) } - override fun setDisplayName(userId: String, newDisplayName: String, matrixCallback: MatrixCallback<Unit>): Cancelable { - return taskExecutor.executorScope.launchToCallback(coroutineDispatchers.io, matrixCallback) { + override suspend fun setDisplayName(userId: String, newDisplayName: String) { + withContext(coroutineDispatchers.io) { setDisplayNameTask.execute(SetDisplayNameTask.Params(userId = userId, newDisplayName = newDisplayName)) userStore.updateDisplayName(userId, newDisplayName) } } - override fun updateAvatar(userId: String, newAvatarUri: Uri, fileName: String, matrixCallback: MatrixCallback<Unit>): Cancelable { - return taskExecutor.executorScope.launchToCallback(coroutineDispatchers.main, matrixCallback) { + override suspend fun updateAvatar(userId: String, newAvatarUri: Uri, fileName: String) { + withContext(coroutineDispatchers.main) { val response = fileUploader.uploadFromUri(newAvatarUri, fileName, MimeTypes.Jpeg) setAvatarUrlTask.execute(SetAvatarUrlTask.Params(userId = userId, newAvatarUrl = response.contentUri)) userStore.updateAvatar(userId, response.contentUri) } } - override fun getAvatarUrl(userId: String, matrixCallback: MatrixCallback<Optional<String>>): Cancelable { + override suspend fun getAvatarUrl(userId: String): Optional<String> { val params = GetProfileInfoTask.Params(userId) - return getProfileInfoTask - .configureWith(params) { - this.callback = object : MatrixCallback<JsonDict> { - override fun onSuccess(data: JsonDict) { - val avatarUrl = data[ProfileService.AVATAR_URL_KEY] as? String - matrixCallback.onSuccess(Optional.from(avatarUrl)) - } - - override fun onFailure(failure: Throwable) { - matrixCallback.onFailure(failure) - } - } - } - .executeBy(taskExecutor) + val data = getProfileInfoTask.execute(params) + val avatarUrl = data[ProfileService.AVATAR_URL_KEY] as? String + return Optional.from(avatarUrl) } - override fun getProfile(userId: String, matrixCallback: MatrixCallback<JsonDict>): Cancelable { + override suspend fun getProfile(userId: String): JsonDict { val params = GetProfileInfoTask.Params(userId) - return getProfileInfoTask - .configureWith(params) { - this.callback = matrixCallback - } - .executeBy(taskExecutor) + return getProfileInfoTask.execute(params) } override fun getThreePids(): List<ThreePid> { @@ -154,70 +126,38 @@ internal class DefaultProfileService @Inject constructor(private val taskExecuto ) } - override fun addThreePid(threePid: ThreePid, matrixCallback: MatrixCallback<Unit>): Cancelable { - return addThreePidTask - .configureWith(AddThreePidTask.Params(threePid)) { - callback = matrixCallback - } - .executeBy(taskExecutor) + override suspend fun addThreePid(threePid: ThreePid) { + addThreePidTask.execute(AddThreePidTask.Params(threePid)) } - override fun submitSmsCode(threePid: ThreePid.Msisdn, code: String, matrixCallback: MatrixCallback<Unit>): Cancelable { - return validateSmsCodeTask - .configureWith(ValidateSmsCodeTask.Params(threePid, code)) { - callback = matrixCallback - } - .executeBy(taskExecutor) + override suspend fun submitSmsCode(threePid: ThreePid.Msisdn, code: String) { + validateSmsCodeTask.execute(ValidateSmsCodeTask.Params(threePid, code)) } - override fun finalizeAddingThreePid(threePid: ThreePid, - userInteractiveAuthInterceptor: UserInteractiveAuthInterceptor, - matrixCallback: MatrixCallback<Unit>): Cancelable { - return finalizeAddingThreePidTask - .configureWith(FinalizeAddingThreePidTask.Params( + override suspend fun finalizeAddingThreePid(threePid: ThreePid, + userInteractiveAuthInterceptor: UserInteractiveAuthInterceptor) { + finalizeAddingThreePidTask + .execute(FinalizeAddingThreePidTask.Params( threePid = threePid, userInteractiveAuthInterceptor = userInteractiveAuthInterceptor, userWantsToCancel = false - )) { - callback = alsoRefresh(matrixCallback) - } - .executeBy(taskExecutor) + )) + refreshThreePids() } - override fun cancelAddingThreePid(threePid: ThreePid, matrixCallback: MatrixCallback<Unit>): Cancelable { - return finalizeAddingThreePidTask - .configureWith(FinalizeAddingThreePidTask.Params( + override suspend fun cancelAddingThreePid(threePid: ThreePid) { + finalizeAddingThreePidTask + .execute(FinalizeAddingThreePidTask.Params( threePid = threePid, userInteractiveAuthInterceptor = null, userWantsToCancel = true - )) { - callback = alsoRefresh(matrixCallback) - } - .executeBy(taskExecutor) - } - - /** - * Wrap the callback to fetch 3Pids from the server in case of success - */ - private fun alsoRefresh(callback: MatrixCallback<Unit>): MatrixCallback<Unit> { - return object : MatrixCallback<Unit> { - override fun onFailure(failure: Throwable) { - callback.onFailure(failure) - } - - override fun onSuccess(data: Unit) { - refreshThreePids() - callback.onSuccess(data) - } - } + )) + refreshThreePids() } - override fun deleteThreePid(threePid: ThreePid, matrixCallback: MatrixCallback<Unit>): Cancelable { - return deleteThreePidTask - .configureWith(DeleteThreePidTask.Params(threePid)) { - callback = alsoRefresh(matrixCallback) - } - .executeBy(taskExecutor) + override suspend fun deleteThreePid(threePid: ThreePid) { + deleteThreePidTask.execute(DeleteThreePidTask.Params(threePid)) + refreshThreePids() } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/profile/DeleteThreePidTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/profile/DeleteThreePidTask.kt index 3549f3613f62f35372304162e4d6a25b0714cb23..7b7617aa8094906b8f453aef4066082aea354d3e 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/profile/DeleteThreePidTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/profile/DeleteThreePidTask.kt @@ -34,12 +34,12 @@ internal class DefaultDeleteThreePidTask @Inject constructor( private val globalErrorReceiver: GlobalErrorReceiver) : DeleteThreePidTask() { override suspend fun execute(params: Params) { - executeRequest<DeleteThreePidResponse>(globalErrorReceiver) { - val body = DeleteThreePidBody( - medium = params.threePid.toMedium(), - address = params.threePid.value - ) - apiCall = profileAPI.deleteThreePid(body) + val body = DeleteThreePidBody( + medium = params.threePid.toMedium(), + address = params.threePid.value + ) + executeRequest(globalErrorReceiver) { + profileAPI.deleteThreePid(body) } // We do not really care about the result for the moment diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/profile/FinalizeAddingThreePidTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/profile/FinalizeAddingThreePidTask.kt index c2a38af0938a17ed1e64335191a375912c5de5d2..5f063365e004ad16d525aa14fc8b46053b07c201 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/profile/FinalizeAddingThreePidTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/profile/FinalizeAddingThreePidTask.kt @@ -61,13 +61,13 @@ internal class DefaultFinalizeAddingThreePidTask @Inject constructor( ?: throw IllegalArgumentException("unknown threepid") try { - executeRequest<Unit>(globalErrorReceiver) { + executeRequest(globalErrorReceiver) { val body = FinalizeAddThreePidBody( clientSecret = pendingThreePids.clientSecret, sid = pendingThreePids.sid, auth = params.userAuthParam?.asMap() ) - apiCall = profileAPI.finalizeAddThreePid(body) + profileAPI.finalizeAddThreePid(body) } true } catch (throwable: Throwable) { diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/profile/GetProfileInfoTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/profile/GetProfileInfoTask.kt index ed60c4a368d5f8acd7668b35adca3e54caf05e49..fed4288f849e0bae23f7da2f0a7e7d58d5ebabcd 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/profile/GetProfileInfoTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/profile/GetProfileInfoTask.kt @@ -34,7 +34,7 @@ internal class DefaultGetProfileInfoTask @Inject constructor(private val profile override suspend fun execute(params: Params): JsonDict { return executeRequest(globalErrorReceiver) { - apiCall = profileAPI.getProfile(params.userId) + profileAPI.getProfile(params.userId) } } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/profile/ProfileAPI.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/profile/ProfileAPI.kt index 7794f578b0058f2ec0c307d9b07d9b97da3df080..5113b821e8726764e8530b112f342b7c5edf0a24 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/profile/ProfileAPI.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/profile/ProfileAPI.kt @@ -21,7 +21,6 @@ import org.matrix.android.sdk.api.util.JsonDict import org.matrix.android.sdk.internal.auth.registration.SuccessResult import org.matrix.android.sdk.internal.auth.registration.ValidationCodeBody import org.matrix.android.sdk.internal.network.NetworkConstants -import retrofit2.Call import retrofit2.http.Body import retrofit2.http.GET import retrofit2.http.POST @@ -37,70 +36,70 @@ internal interface ProfileAPI { * @param userId the user id to fetch profile info */ @GET(NetworkConstants.URI_API_PREFIX_PATH_R0 + "profile/{userId}") - fun getProfile(@Path("userId") userId: String): Call<JsonDict> + suspend fun getProfile(@Path("userId") userId: String): JsonDict /** * List all 3PIDs linked to the Matrix user account. */ @GET(NetworkConstants.URI_API_PREFIX_PATH_R0 + "account/3pid") - fun getThreePIDs(): Call<AccountThreePidsResponse> + suspend fun getThreePIDs(): AccountThreePidsResponse /** * Change user display name */ @PUT(NetworkConstants.URI_API_PREFIX_PATH_R0 + "profile/{userId}/displayname") - fun setDisplayName(@Path("userId") userId: String, - @Body body: SetDisplayNameBody): Call<Unit> + suspend fun setDisplayName(@Path("userId") userId: String, + @Body body: SetDisplayNameBody) /** * Change user avatar url. */ @PUT(NetworkConstants.URI_API_PREFIX_PATH_R0 + "profile/{userId}/avatar_url") - fun setAvatarUrl(@Path("userId") userId: String, - @Body body: SetAvatarUrlBody): Call<Unit> + suspend fun setAvatarUrl(@Path("userId") userId: String, + @Body body: SetAvatarUrlBody) /** * Bind a threePid * Ref: https://matrix.org/docs/spec/client_server/latest#post-matrix-client-r0-account-3pid-bind */ @POST(NetworkConstants.URI_API_PREFIX_PATH_UNSTABLE + "account/3pid/bind") - fun bindThreePid(@Body body: BindThreePidBody): Call<Unit> + suspend fun bindThreePid(@Body body: BindThreePidBody) /** * Unbind a threePid * Ref: https://matrix.org/docs/spec/client_server/latest#post-matrix-client-r0-account-3pid-unbind */ @POST(NetworkConstants.URI_API_PREFIX_PATH_UNSTABLE + "account/3pid/unbind") - fun unbindThreePid(@Body body: UnbindThreePidBody): Call<UnbindThreePidResponse> + suspend fun unbindThreePid(@Body body: UnbindThreePidBody): UnbindThreePidResponse /** * Ref: https://matrix.org/docs/spec/client_server/r0.6.1#post-matrix-client-r0-account-3pid-email-requesttoken */ @POST(NetworkConstants.URI_API_PREFIX_PATH_R0 + "account/3pid/email/requestToken") - fun addEmail(@Body body: AddEmailBody): Call<AddEmailResponse> + suspend fun addEmail(@Body body: AddEmailBody): AddEmailResponse /** * Ref: https://matrix.org/docs/spec/client_server/r0.6.1#post-matrix-client-r0-account-3pid-msisdn-requesttoken */ @POST(NetworkConstants.URI_API_PREFIX_PATH_R0 + "account/3pid/msisdn/requestToken") - fun addMsisdn(@Body body: AddMsisdnBody): Call<AddMsisdnResponse> + suspend fun addMsisdn(@Body body: AddMsisdnBody): AddMsisdnResponse /** * Validate Msisdn code (same model than for Identity server API) */ @POST - fun validateMsisdn(@Url url: String, - @Body params: ValidationCodeBody): Call<SuccessResult> + suspend fun validateMsisdn(@Url url: String, + @Body params: ValidationCodeBody): SuccessResult /** * Ref: https://matrix.org/docs/spec/client_server/r0.6.1#post-matrix-client-r0-account-3pid-add */ @POST(NetworkConstants.URI_API_PREFIX_PATH_R0 + "account/3pid/add") - fun finalizeAddThreePid(@Body body: FinalizeAddThreePidBody): Call<Unit> + suspend fun finalizeAddThreePid(@Body body: FinalizeAddThreePidBody) /** * Ref: https://matrix.org/docs/spec/client_server/r0.6.1#post-matrix-client-r0-account-3pid-delete */ @POST(NetworkConstants.URI_API_PREFIX_PATH_R0 + "account/3pid/delete") - fun deleteThreePid(@Body body: DeleteThreePidBody): Call<DeleteThreePidResponse> + suspend fun deleteThreePid(@Body body: DeleteThreePidBody): DeleteThreePidResponse } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/profile/RefreshUserThreePidsTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/profile/RefreshUserThreePidsTask.kt index 552ad874eeeba0bea3e88e06589b3fecdb7ddc09..8a064b4fd12c93d98e70e0b7ffb8e19d3b03db76 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/profile/RefreshUserThreePidsTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/profile/RefreshUserThreePidsTask.kt @@ -33,8 +33,8 @@ internal class DefaultRefreshUserThreePidsTask @Inject constructor(private val p private val globalErrorReceiver: GlobalErrorReceiver) : RefreshUserThreePidsTask() { override suspend fun execute(params: Unit) { - val accountThreePidsResponse = executeRequest<AccountThreePidsResponse>(globalErrorReceiver) { - apiCall = profileAPI.getThreePIDs() + val accountThreePidsResponse = executeRequest(globalErrorReceiver) { + profileAPI.getThreePIDs() } Timber.d("Get ${accountThreePidsResponse.threePids?.size} threePids") diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/profile/SetAvatarUrlTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/profile/SetAvatarUrlTask.kt index b29153d665f634d7c529885622022dbd423989f9..a7d116d919df94c6a64fc71f31abba8011f5df17 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/profile/SetAvatarUrlTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/profile/SetAvatarUrlTask.kt @@ -33,11 +33,11 @@ internal class DefaultSetAvatarUrlTask @Inject constructor( private val globalErrorReceiver: GlobalErrorReceiver) : SetAvatarUrlTask() { override suspend fun execute(params: Params) { + val body = SetAvatarUrlBody( + avatarUrl = params.newAvatarUrl + ) return executeRequest(globalErrorReceiver) { - val body = SetAvatarUrlBody( - avatarUrl = params.newAvatarUrl - ) - apiCall = profileAPI.setAvatarUrl(params.userId, body) + profileAPI.setAvatarUrl(params.userId, body) } } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/profile/SetDisplayNameTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/profile/SetDisplayNameTask.kt index 3f236bc589ba22750b31a41d55d59acec236fc2e..61d304231038a889a6f8ce5422d0029712ef7ea0 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/profile/SetDisplayNameTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/profile/SetDisplayNameTask.kt @@ -33,11 +33,11 @@ internal class DefaultSetDisplayNameTask @Inject constructor( private val globalErrorReceiver: GlobalErrorReceiver) : SetDisplayNameTask() { override suspend fun execute(params: Params) { + val body = SetDisplayNameBody( + displayName = params.newDisplayName + ) return executeRequest(globalErrorReceiver) { - val body = SetDisplayNameBody( - displayName = params.newDisplayName - ) - apiCall = profileAPI.setDisplayName(params.userId, body) + profileAPI.setDisplayName(params.userId, body) } } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/profile/UnbindThreePidsTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/profile/UnbindThreePidsTask.kt index 3439f6f840a980af59d0324327a5dc3e2bc50362..df8a1c97ffb5a29854d4af2cf9ad229a45e18797 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/profile/UnbindThreePidsTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/profile/UnbindThreePidsTask.kt @@ -39,8 +39,8 @@ internal class DefaultUnbindThreePidsTask @Inject constructor(private val profil val identityServerUrlWithoutProtocol = identityStore.getIdentityServerUrlWithoutProtocol() ?: throw IdentityServiceError.NoIdentityServerConfigured - return executeRequest<UnbindThreePidResponse>(globalErrorReceiver) { - apiCall = profileAPI.unbindThreePid( + return executeRequest(globalErrorReceiver) { + profileAPI.unbindThreePid( UnbindThreePidBody( identityServerUrlWithoutProtocol, params.threePid.toMedium(), diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/profile/ValidateSmsCodeTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/profile/ValidateSmsCodeTask.kt index efb6c6e836ca4dabbb7b255e664c4483281d4c67..c898fc6c5cc74e1eb906d1288d35b76330ab739a 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/profile/ValidateSmsCodeTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/profile/ValidateSmsCodeTask.kt @@ -19,7 +19,6 @@ package org.matrix.android.sdk.internal.session.profile import com.zhuinden.monarchy.Monarchy import org.matrix.android.sdk.api.failure.Failure import org.matrix.android.sdk.api.session.identity.ThreePid -import org.matrix.android.sdk.internal.auth.registration.SuccessResult import org.matrix.android.sdk.internal.auth.registration.ValidationCodeBody import org.matrix.android.sdk.internal.database.model.PendingThreePidEntity import org.matrix.android.sdk.internal.di.SessionDatabase @@ -58,8 +57,8 @@ internal class DefaultValidateSmsCodeTask @Inject constructor( sid = pendingThreePids.sid, code = params.code ) - val result = executeRequest<SuccessResult>(globalErrorReceiver) { - apiCall = profileAPI.validateMsisdn(url, body) + val result = executeRequest(globalErrorReceiver) { + profileAPI.validateMsisdn(url, body) } if (!result.isSuccess()) { diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/AddHttpPusherWorker.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/AddHttpPusherWorker.kt index d0f7cbfca3d0d5b0ec03bead41e847c95932fb54..c9d7ad21938c960dee8debfb7e6efc8c1d15cdaa 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/AddHttpPusherWorker.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/AddHttpPusherWorker.kt @@ -81,8 +81,8 @@ internal class AddHttpPusherWorker(context: Context, params: WorkerParameters) } private suspend fun setPusher(pusher: JsonPusher) { - executeRequest<Unit>(globalErrorReceiver) { - apiCall = pushersAPI.setPusher(pusher) + executeRequest(globalErrorReceiver) { + pushersAPI.setPusher(pusher) } monarchy.awaitTransaction { realm -> val echo = PusherEntity.where(realm, pusher.pushKey).findFirst() diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/AddPushRuleTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/AddPushRuleTask.kt index 03748b15283517d9ee03f653a659ed3d73d62cc2..b2176871688bf631e5aa35f253169bc3ff0006d2 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/AddPushRuleTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/AddPushRuleTask.kt @@ -36,7 +36,7 @@ internal class DefaultAddPushRuleTask @Inject constructor( override suspend fun execute(params: AddPushRuleTask.Params) { return executeRequest(globalErrorReceiver) { - apiCall = pushRulesApi.addRule(params.kind.value, params.pushRule.ruleId, params.pushRule) + pushRulesApi.addRule(params.kind.value, params.pushRule.ruleId, params.pushRule) } } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/DefaultPushersService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/DefaultPushersService.kt index e239182b4a68f5090f6ecdade6b082e134c94b8f..a772cf5ebb17ceb97c86f85174f00d23864ebec1 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/DefaultPushersService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/DefaultPushersService.kt @@ -18,10 +18,8 @@ package org.matrix.android.sdk.internal.session.pushers import androidx.lifecycle.LiveData import androidx.work.BackoffPolicy import com.zhuinden.monarchy.Monarchy -import org.matrix.android.sdk.api.MatrixCallback import org.matrix.android.sdk.api.session.pushers.Pusher import org.matrix.android.sdk.api.session.pushers.PushersService -import org.matrix.android.sdk.api.util.Cancelable import org.matrix.android.sdk.internal.database.mapper.asDomain import org.matrix.android.sdk.internal.database.model.PusherEntity import org.matrix.android.sdk.internal.database.query.where @@ -47,16 +45,11 @@ internal class DefaultPushersService @Inject constructor( private val taskExecutor: TaskExecutor ) : PushersService { - override fun testPush(url: String, - appId: String, - pushkey: String, - eventId: String, - callback: MatrixCallback<Unit>): Cancelable { - return pushGatewayNotifyTask - .configureWith(PushGatewayNotifyTask.Params(url, appId, pushkey, eventId)) { - this.callback = callback - } - .executeBy(taskExecutor) + override suspend fun testPush(url: String, + appId: String, + pushkey: String, + eventId: String) { + pushGatewayNotifyTask.execute(PushGatewayNotifyTask.Params(url, appId, pushkey, eventId)) } override fun refreshPushers() { @@ -96,20 +89,15 @@ internal class DefaultPushersService @Inject constructor( val request = workManagerProvider.matrixOneTimeWorkRequestBuilder<AddHttpPusherWorker>() .setConstraints(WorkManagerProvider.workConstraints) .setInputData(WorkerParamsFactory.toData(params)) - .setBackoffCriteria(BackoffPolicy.LINEAR, 10_000L, TimeUnit.MILLISECONDS) + .setBackoffCriteria(BackoffPolicy.LINEAR, WorkManagerProvider.BACKOFF_DELAY_MILLIS, TimeUnit.MILLISECONDS) .build() workManagerProvider.workManager.enqueue(request) return request.id } - override fun removeHttpPusher(pushkey: String, appId: String, callback: MatrixCallback<Unit>): Cancelable { + override suspend fun removeHttpPusher(pushkey: String, appId: String) { val params = RemovePusherTask.Params(pushkey, appId) - return removePusherTask - .configureWith(params) { - this.callback = callback - } - // .enableRetry() ?? - .executeBy(taskExecutor) + removePusherTask.execute(params) } override fun getPushersLive(): LiveData<List<Pusher>> { diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/GetPushRulesTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/GetPushRulesTask.kt index 9fb2d51664290bb3fbba12c385efc63990b216cb..8cf861d2853c88dc769cc0d76b31916cb4a9463f 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/GetPushRulesTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/GetPushRulesTask.kt @@ -15,7 +15,6 @@ */ package org.matrix.android.sdk.internal.session.pushers -import org.matrix.android.sdk.api.pushrules.rest.GetPushRulesResponse import org.matrix.android.sdk.internal.network.GlobalErrorReceiver import org.matrix.android.sdk.internal.network.executeRequest import org.matrix.android.sdk.internal.task.Task @@ -35,8 +34,8 @@ internal class DefaultGetPushRulesTask @Inject constructor( ) : GetPushRulesTask { override suspend fun execute(params: GetPushRulesTask.Params) { - val response = executeRequest<GetPushRulesResponse>(globalErrorReceiver) { - apiCall = pushRulesApi.getAllRules() + val response = executeRequest(globalErrorReceiver) { + pushRulesApi.getAllRules() } savePushRulesTask.execute(SavePushRulesTask.Params(response)) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/GetPushersTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/GetPushersTask.kt index 125c8f0022d6dad1301fbfb9eb3049ca6baa1a89..ba413a34db2d2c1aeff3151bf97ed436e8c65bf4 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/GetPushersTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/GetPushersTask.kt @@ -36,8 +36,8 @@ internal class DefaultGetPushersTask @Inject constructor( ) : GetPushersTask { override suspend fun execute(params: Unit) { - val response = executeRequest<GetPushersResponse>(globalErrorReceiver) { - apiCall = pushersAPI.getPushers() + val response = executeRequest(globalErrorReceiver) { + pushersAPI.getPushers() } monarchy.awaitTransaction { realm -> // clear existings? diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/PushRulesApi.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/PushRulesApi.kt index cbcb7d2b37033650d09ed0f819a7c6e5323185e4..daf9397ce8de0cde3d91da79c965890987574bc6 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/PushRulesApi.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/PushRulesApi.kt @@ -18,7 +18,6 @@ package org.matrix.android.sdk.internal.session.pushers import org.matrix.android.sdk.api.pushrules.rest.GetPushRulesResponse import org.matrix.android.sdk.api.pushrules.rest.PushRule import org.matrix.android.sdk.internal.network.NetworkConstants -import retrofit2.Call import retrofit2.http.Body import retrofit2.http.DELETE import retrofit2.http.GET @@ -30,7 +29,7 @@ internal interface PushRulesApi { * Get all push rules */ @GET(NetworkConstants.URI_API_PREFIX_PATH_R0 + "pushrules/") - fun getAllRules(): Call<GetPushRulesResponse> + suspend fun getAllRules(): GetPushRulesResponse /** * Update the ruleID enable status @@ -40,10 +39,9 @@ internal interface PushRulesApi { * @param enable the new enable status */ @PUT(NetworkConstants.URI_API_PREFIX_PATH_R0 + "pushrules/global/{kind}/{ruleId}/enabled") - fun updateEnableRuleStatus(@Path("kind") kind: String, - @Path("ruleId") ruleId: String, - @Body enable: Boolean?) - : Call<Unit> + suspend fun updateEnableRuleStatus(@Path("kind") kind: String, + @Path("ruleId") ruleId: String, + @Body enable: Boolean?) /** * Update the ruleID action @@ -54,10 +52,9 @@ internal interface PushRulesApi { * @param actions the actions */ @PUT(NetworkConstants.URI_API_PREFIX_PATH_R0 + "pushrules/global/{kind}/{ruleId}/actions") - fun updateRuleActions(@Path("kind") kind: String, - @Path("ruleId") ruleId: String, - @Body actions: Any) - : Call<Unit> + suspend fun updateRuleActions(@Path("kind") kind: String, + @Path("ruleId") ruleId: String, + @Body actions: Any) /** * Delete a rule @@ -66,9 +63,8 @@ internal interface PushRulesApi { * @param ruleId the ruleId */ @DELETE(NetworkConstants.URI_API_PREFIX_PATH_R0 + "pushrules/global/{kind}/{ruleId}") - fun deleteRule(@Path("kind") kind: String, - @Path("ruleId") ruleId: String) - : Call<Unit> + suspend fun deleteRule(@Path("kind") kind: String, + @Path("ruleId") ruleId: String) /** * Add the ruleID enable status @@ -78,8 +74,7 @@ internal interface PushRulesApi { * @param rule the rule to add. */ @PUT(NetworkConstants.URI_API_PREFIX_PATH_R0 + "pushrules/global/{kind}/{ruleId}") - fun addRule(@Path("kind") kind: String, - @Path("ruleId") ruleId: String, - @Body rule: PushRule) - : Call<Unit> + suspend fun addRule(@Path("kind") kind: String, + @Path("ruleId") ruleId: String, + @Body rule: PushRule) } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/PushersAPI.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/PushersAPI.kt index ed4fb73e1b127e659e405481777871d5f18a504a..0afea6996ddc1f8f061e4a6fd11fff2f5119470f 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/PushersAPI.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/PushersAPI.kt @@ -16,7 +16,6 @@ package org.matrix.android.sdk.internal.session.pushers import org.matrix.android.sdk.internal.network.NetworkConstants -import retrofit2.Call import retrofit2.http.Body import retrofit2.http.GET import retrofit2.http.POST @@ -29,7 +28,7 @@ internal interface PushersAPI { * Ref: https://matrix.org/docs/spec/client_server/latest#get-matrix-client-r0-pushers */ @GET(NetworkConstants.URI_API_PREFIX_PATH_R0 + "pushers") - fun getPushers(): Call<GetPushersResponse> + suspend fun getPushers(): GetPushersResponse /** * This endpoint allows the creation, modification and deletion of pushers for this user ID. @@ -38,5 +37,5 @@ internal interface PushersAPI { * Ref: https://matrix.org/docs/spec/client_server/latest#post-matrix-client-r0-pushers-set */ @POST(NetworkConstants.URI_API_PREFIX_PATH_R0 + "pushers/set") - fun setPusher(@Body jsonPusher: JsonPusher): Call<Unit> + suspend fun setPusher(@Body jsonPusher: JsonPusher) } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/RemovePushRuleTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/RemovePushRuleTask.kt index ff3122f5665de05f80865c81284751cd44e21a95..23d0515f414c833245f884a13e5ec8786bd78157 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/RemovePushRuleTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/RemovePushRuleTask.kt @@ -36,7 +36,7 @@ internal class DefaultRemovePushRuleTask @Inject constructor( override suspend fun execute(params: RemovePushRuleTask.Params) { return executeRequest(globalErrorReceiver) { - apiCall = pushRulesApi.deleteRule(params.kind.value, params.pushRule.ruleId) + pushRulesApi.deleteRule(params.kind.value, params.pushRule.ruleId) } } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/RemovePusherTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/RemovePusherTask.kt index e3f4fdb789671ff7aed078be2c4cacf0c4a3056f..3a2ebf40c2f0ae89ade69cd562a6f72600768d1a 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/RemovePusherTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/RemovePusherTask.kt @@ -62,8 +62,8 @@ internal class DefaultRemovePusherTask @Inject constructor( data = JsonPusherData(existing.data.url, existing.data.format), append = false ) - executeRequest<Unit>(globalErrorReceiver) { - apiCall = pushersAPI.setPusher(deleteBody) + executeRequest(globalErrorReceiver) { + pushersAPI.setPusher(deleteBody) } monarchy.awaitTransaction { PusherEntity.where(it, params.pushKey).findFirst()?.deleteFromRealm() diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/UpdatePushRuleActionsTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/UpdatePushRuleActionsTask.kt index a5c220e662583512910b307d1d3b8cb37ece7bd9..2a24aee8929111559584efbf6fda0e3d97deeca3 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/UpdatePushRuleActionsTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/UpdatePushRuleActionsTask.kt @@ -38,8 +38,8 @@ internal class DefaultUpdatePushRuleActionsTask @Inject constructor( override suspend fun execute(params: UpdatePushRuleActionsTask.Params) { if (params.oldPushRule.enabled != params.newPushRule.enabled) { // First change enabled state - executeRequest<Unit>(globalErrorReceiver) { - apiCall = pushRulesApi.updateEnableRuleStatus(params.kind.value, params.newPushRule.ruleId, params.newPushRule.enabled) + executeRequest(globalErrorReceiver) { + pushRulesApi.updateEnableRuleStatus(params.kind.value, params.newPushRule.ruleId, params.newPushRule.enabled) } } @@ -47,8 +47,8 @@ internal class DefaultUpdatePushRuleActionsTask @Inject constructor( // Also ensure the actions are up to date val body = mapOf("actions" to params.newPushRule.actions) - executeRequest<Unit>(globalErrorReceiver) { - apiCall = pushRulesApi.updateRuleActions(params.kind.value, params.newPushRule.ruleId, body) + executeRequest(globalErrorReceiver) { + pushRulesApi.updateRuleActions(params.kind.value, params.newPushRule.ruleId, body) } } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/UpdatePushRuleEnableStatusTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/UpdatePushRuleEnableStatusTask.kt index f36b5c55fb1933a4cc4a400a4aadd822ce78d2a9..9d7a46bede1e80071a9dc269deaa5de8c4e6ebd2 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/UpdatePushRuleEnableStatusTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/UpdatePushRuleEnableStatusTask.kt @@ -35,7 +35,7 @@ internal class DefaultUpdatePushRuleEnableStatusTask @Inject constructor( override suspend fun execute(params: UpdatePushRuleEnableStatusTask.Params) { return executeRequest(globalErrorReceiver) { - apiCall = pushRulesApi.updateEnableRuleStatus(params.kind.value, params.pushRule.ruleId, params.enabled) + pushRulesApi.updateEnableRuleStatus(params.kind.value, params.pushRule.ruleId, params.enabled) } } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/gateway/PushGatewayAPI.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/gateway/PushGatewayAPI.kt index d95587fc222dc74ed1b3152eb13460c40a8aeee7..4333d6c7b8850c389f148bfb6cca482b5d470a75 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/gateway/PushGatewayAPI.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/gateway/PushGatewayAPI.kt @@ -16,7 +16,6 @@ package org.matrix.android.sdk.internal.session.pushers.gateway import org.matrix.android.sdk.internal.network.NetworkConstants -import retrofit2.Call import retrofit2.http.Body import retrofit2.http.POST @@ -27,5 +26,5 @@ internal interface PushGatewayAPI { * Ref: https://matrix.org/docs/spec/push_gateway/r0.1.1#post-matrix-push-v1-notify */ @POST(NetworkConstants.URI_PUSH_GATEWAY_PREFIX_PATH + "notify") - fun notify(@Body body: PushGatewayNotifyBody): Call<PushGatewayNotifyResponse> + suspend fun notify(@Body body: PushGatewayNotifyBody): PushGatewayNotifyResponse } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/gateway/PushGatewayNotifyTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/gateway/PushGatewayNotifyTask.kt index df6f46fa81cd44ecf471134f1f178b7bb91474c3..316e221b32ef1fa3c3a8b8d47555d4507ab29e2c 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/gateway/PushGatewayNotifyTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/gateway/PushGatewayNotifyTask.kt @@ -45,8 +45,8 @@ internal class DefaultPushGatewayNotifyTask @Inject constructor( ) .create(PushGatewayAPI::class.java) - val response = executeRequest<PushGatewayNotifyResponse>(null) { - apiCall = sygnalApi.notify( + val response = executeRequest(null) { + sygnalApi.notify( PushGatewayNotifyBody( PushGatewayNotification( eventId = params.eventId, 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 8e817ec31a0dcb4537bbf4a4422207bfaefd16fd..1d8eb6c95ea7d2628b15ed3ecb86e9f8517eb10b 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/DefaultRoom.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/DefaultRoom.kt @@ -17,7 +17,6 @@ package org.matrix.android.sdk.internal.session.room import androidx.lifecycle.LiveData -import org.matrix.android.sdk.api.MatrixCallback import org.matrix.android.sdk.api.session.crypto.CryptoService import org.matrix.android.sdk.api.session.events.model.EventType import org.matrix.android.sdk.api.session.room.Room @@ -37,14 +36,11 @@ import org.matrix.android.sdk.api.session.room.timeline.TimelineService import org.matrix.android.sdk.api.session.room.typing.TypingService import org.matrix.android.sdk.api.session.room.uploads.UploadsService import org.matrix.android.sdk.api.session.search.SearchResult -import org.matrix.android.sdk.api.util.Cancelable import org.matrix.android.sdk.api.util.Optional import org.matrix.android.sdk.internal.crypto.MXCRYPTO_ALGORITHM_MEGOLM import org.matrix.android.sdk.internal.session.room.state.SendStateTask import org.matrix.android.sdk.internal.session.room.summary.RoomSummaryDataSource import org.matrix.android.sdk.internal.session.search.SearchTask -import org.matrix.android.sdk.internal.task.TaskExecutor -import org.matrix.android.sdk.internal.task.configureWith import org.matrix.android.sdk.internal.util.awaitCallback import java.security.InvalidParameterException import javax.inject.Inject @@ -66,7 +62,6 @@ internal class DefaultRoom @Inject constructor(override val roomId: String, private val relationService: RelationService, private val roomMembersService: MembershipService, private val roomPushRuleService: RoomPushRuleService, - private val taskExecutor: TaskExecutor, private val sendStateTask: SendStateTask, private val searchTask: SearchTask) : Room, @@ -133,16 +128,15 @@ internal class DefaultRoom @Inject constructor(override val roomId: String, } } - override fun search(searchTerm: String, - nextBatch: String?, - orderByRecent: Boolean, - limit: Int, - beforeLimit: Int, - afterLimit: Int, - includeProfile: Boolean, - callback: MatrixCallback<SearchResult>): Cancelable { - return searchTask - .configureWith(SearchTask.Params( + override suspend fun search(searchTerm: String, + nextBatch: String?, + orderByRecent: Boolean, + limit: Int, + beforeLimit: Int, + afterLimit: Int, + includeProfile: Boolean): SearchResult { + return searchTask.execute( + SearchTask.Params( searchTerm = searchTerm, roomId = roomId, nextBatch = nextBatch, @@ -151,8 +145,7 @@ internal class DefaultRoom @Inject constructor(override val roomId: String, beforeLimit = beforeLimit, afterLimit = afterLimit, includeProfile = includeProfile - )) { - this.callback = callback - }.executeBy(taskExecutor) + ) + ) } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/DefaultRoomService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/DefaultRoomService.kt index 383dd876d3de84160f87f44ef966298b79181852..bd63ba480eb8e3c29f6491dda18ed54b29dff56a 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/DefaultRoomService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/DefaultRoomService.kt @@ -18,17 +18,20 @@ package org.matrix.android.sdk.internal.session.room import androidx.lifecycle.LiveData import androidx.lifecycle.Transformations +import androidx.paging.PagedList import com.zhuinden.monarchy.Monarchy 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.Room import org.matrix.android.sdk.api.session.room.RoomService import org.matrix.android.sdk.api.session.room.RoomSummaryQueryParams +import org.matrix.android.sdk.api.session.room.UpdatableFilterLivePageResult import org.matrix.android.sdk.api.session.room.members.ChangeMembershipState import org.matrix.android.sdk.api.session.room.model.RoomMemberSummary import org.matrix.android.sdk.api.session.room.model.RoomSummary import org.matrix.android.sdk.api.session.room.model.create.CreateRoomParams import org.matrix.android.sdk.api.session.room.peeking.PeekResult +import org.matrix.android.sdk.api.session.room.summary.RoomAggregateNotificationCount import org.matrix.android.sdk.api.util.Cancelable import org.matrix.android.sdk.api.util.Optional import org.matrix.android.sdk.api.util.toOptional @@ -96,6 +99,20 @@ internal class DefaultRoomService @Inject constructor( return roomSummaryDataSource.getRoomSummariesLive(queryParams) } + override fun getPagedRoomSummariesLive(queryParams: RoomSummaryQueryParams, pagedListConfig: PagedList.Config) + : LiveData<PagedList<RoomSummary>> { + return roomSummaryDataSource.getSortedPagedRoomSummariesLive(queryParams, pagedListConfig) + } + + override fun getFilteredPagedRoomSummariesLive(queryParams: RoomSummaryQueryParams, pagedListConfig: PagedList.Config) + : UpdatableFilterLivePageResult { + return roomSummaryDataSource.getFilteredPagedRoomSummariesLive(queryParams, pagedListConfig) + } + + override fun getNotificationCountForRooms(queryParams: RoomSummaryQueryParams): RoomAggregateNotificationCount { + return roomSummaryDataSource.getNotificationCountForRooms(queryParams) + } + override fun getBreadcrumbs(queryParams: RoomSummaryQueryParams): List<RoomSummary> { return roomSummaryDataSource.getBreadcrumbs(queryParams) } 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 60440c6359f54dffa16e99f743db07724f53c4ae..c7e09e595450ea0fcc5a312e80bdc0b3c13a4927 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 @@ -53,8 +53,9 @@ import org.matrix.android.sdk.internal.session.EventInsertLiveProcessor import timber.log.Timber import javax.inject.Inject -internal class EventRelationsAggregationProcessor @Inject constructor(@UserId private val userId: String) - : EventInsertLiveProcessor { +internal class EventRelationsAggregationProcessor @Inject constructor( + @UserId private val userId: String +) : EventInsertLiveProcessor { private val allowedTypes = listOf( EventType.MESSAGE, @@ -87,12 +88,12 @@ internal class EventRelationsAggregationProcessor @Inject constructor(@UserId pr EventType.REACTION -> { // we got a reaction!! Timber.v("###REACTION in room $roomId , reaction eventID ${event.eventId}") - handleReaction(event, roomId, realm, userId, isLocalEcho) + handleReaction(realm, event, roomId, isLocalEcho) } EventType.MESSAGE -> { if (event.unsignedData?.relations?.annotations != null) { - Timber.v("###REACTION Agreggation in room $roomId for event ${event.eventId}") - handleInitialAggregatedRelations(event, roomId, event.unsignedData.relations.annotations, realm) + Timber.v("###REACTION Aggregation in room $roomId for event ${event.eventId}") + handleInitialAggregatedRelations(realm, event, roomId, event.unsignedData.relations.annotations) EventAnnotationsSummaryEntity.where(realm, roomId, event.eventId ?: "").findFirst() ?.let { @@ -108,7 +109,7 @@ internal class EventRelationsAggregationProcessor @Inject constructor(@UserId pr handleReplace(realm, event, content, roomId, isLocalEcho) } else if (content?.relatesTo?.type == RelationType.RESPONSE) { Timber.v("###RESPONSE in room $roomId for event ${event.eventId}") - handleResponse(realm, userId, event, content, roomId, isLocalEcho) + handleResponse(realm, event, content, roomId, isLocalEcho) } } @@ -122,7 +123,7 @@ internal class EventRelationsAggregationProcessor @Inject constructor(@UserId pr Timber.v("## SAS REF in room $roomId for event ${event.eventId}") event.content.toModel<MessageRelationContent>()?.relatesTo?.let { if (it.type == RelationType.REFERENCE && it.eventId != null) { - handleVerification(realm, event, roomId, isLocalEcho, it.eventId, userId) + handleVerification(realm, event, roomId, isLocalEcho, it.eventId) } } } @@ -140,7 +141,7 @@ internal class EventRelationsAggregationProcessor @Inject constructor(@UserId pr handleReplace(realm, event, it, roomId, isLocalEcho, encryptedEventContent.relatesTo.eventId) } else if (encryptedEventContent.relatesTo.type == RelationType.RESPONSE) { Timber.v("###RESPONSE in room $roomId for event ${event.eventId}") - handleResponse(realm, userId, event, it, roomId, isLocalEcho, encryptedEventContent.relatesTo.eventId) + handleResponse(realm, event, it, roomId, isLocalEcho, encryptedEventContent.relatesTo.eventId) } } } else if (encryptedEventContent?.relatesTo?.type == RelationType.REFERENCE) { @@ -154,10 +155,17 @@ internal class EventRelationsAggregationProcessor @Inject constructor(@UserId pr EventType.KEY_VERIFICATION_KEY -> { Timber.v("## SAS REF in room $roomId for event ${event.eventId}") encryptedEventContent.relatesTo.eventId?.let { - handleVerification(realm, event, roomId, isLocalEcho, it, userId) + handleVerification(realm, event, roomId, isLocalEcho, it) } } } + } else if (encryptedEventContent?.relatesTo?.type == RelationType.ANNOTATION) { + // Reaction + if (event.getClearType() == EventType.REACTION) { + // we got a reaction!! + Timber.v("###REACTION e2e in room $roomId , reaction eventID ${event.eventId}") + handleReaction(realm, event, roomId, isLocalEcho) + } } } EventType.REDACTION -> { @@ -172,11 +180,11 @@ internal class EventRelationsAggregationProcessor @Inject constructor(@UserId pr // was this event a m.replace val contentModel = ContentMapper.map(eventToPrune.content)?.toModel<MessageContent>() if (RelationType.REPLACE == contentModel?.relatesTo?.type && contentModel.relatesTo?.eventId != null) { - handleRedactionOfReplace(eventToPrune, contentModel.relatesTo!!.eventId!!, realm) + handleRedactionOfReplace(realm, eventToPrune, contentModel.relatesTo!!.eventId!!) } } EventType.REACTION -> { - handleReactionRedact(eventToPrune, realm, userId) + handleReactionRedact(realm, eventToPrune) } } } @@ -267,7 +275,6 @@ internal class EventRelationsAggregationProcessor @Inject constructor(@UserId pr } private fun handleResponse(realm: Realm, - userId: String, event: Event, content: MessageContent, roomId: String, @@ -354,7 +361,10 @@ internal class EventRelationsAggregationProcessor @Inject constructor(@UserId pr existingPollSummary.aggregatedContent = ContentMapper.map(sumModel.toContent()) } - private fun handleInitialAggregatedRelations(event: Event, roomId: String, aggregation: AggregatedAnnotation, realm: Realm) { + private fun handleInitialAggregatedRelations(realm: Realm, + event: Event, + roomId: String, + aggregation: AggregatedAnnotation) { if (SHOULD_HANDLE_SERVER_AGREGGATION) { aggregation.chunk?.forEach { if (it.type == EventType.REACTION) { @@ -376,7 +386,10 @@ internal class EventRelationsAggregationProcessor @Inject constructor(@UserId pr } } - private fun handleReaction(event: Event, roomId: String, realm: Realm, userId: String, isLocalEcho: Boolean) { + private fun handleReaction(realm: Realm, + event: Event, + roomId: String, + isLocalEcho: Boolean) { val content = event.content.toModel<ReactionContent>() if (content == null) { Timber.e("Malformed reaction content ${event.content}") @@ -441,7 +454,9 @@ internal class EventRelationsAggregationProcessor @Inject constructor(@UserId pr /** * Called when an event is deleted */ - private fun handleRedactionOfReplace(redacted: EventEntity, relatedEventId: String, realm: Realm) { + private fun handleRedactionOfReplace(realm: Realm, + redacted: EventEntity, + relatedEventId: String) { Timber.d("Handle redaction of m.replace") val eventSummary = EventAnnotationsSummaryEntity.where(realm, redacted.roomId, relatedEventId).findFirst() if (eventSummary == null) { @@ -457,7 +472,8 @@ internal class EventRelationsAggregationProcessor @Inject constructor(@UserId pr sourceToDiscard.deleteFromRealm() } - private fun handleReactionRedact(eventToPrune: EventEntity, realm: Realm, userId: String) { + private fun handleReactionRedact(realm: Realm, + eventToPrune: EventEntity) { Timber.v("REDACTION of reaction ${eventToPrune.eventId}") // delete a reaction, need to update the annotation summary if any val reactionContent: ReactionContent = EventMapper.map(eventToPrune).content.toModel() ?: return @@ -494,7 +510,7 @@ internal class EventRelationsAggregationProcessor @Inject constructor(@UserId pr } } - private fun handleVerification(realm: Realm, event: Event, roomId: String, isLocalEcho: Boolean, relatedEventId: String, userId: String) { + private fun handleVerification(realm: Realm, event: Event, roomId: String, isLocalEcho: Boolean, relatedEventId: String) { val eventSummary = EventAnnotationsSummaryEntity.getOrCreate(realm, roomId, relatedEventId) val verifSummary = eventSummary.referencesSummaryEntity diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/RoomAPI.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/RoomAPI.kt index 20cb49ee8a8e771840b8ff4a5c3258bff5f62d35..6fee630510b01624b9904d9ec7d576af895438a6 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/RoomAPI.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/RoomAPI.kt @@ -18,6 +18,7 @@ package org.matrix.android.sdk.internal.session.room 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.Membership import org.matrix.android.sdk.api.session.room.model.roomdirectory.PublicRoomsParams import org.matrix.android.sdk.api.session.room.model.roomdirectory.PublicRoomsResponse import org.matrix.android.sdk.api.util.JsonDict @@ -37,7 +38,6 @@ import org.matrix.android.sdk.internal.session.room.tags.TagBody import org.matrix.android.sdk.internal.session.room.timeline.EventContextResponse import org.matrix.android.sdk.internal.session.room.timeline.PaginationResponse import org.matrix.android.sdk.internal.session.room.typing.TypingBody -import retrofit2.Call import retrofit2.http.Body import retrofit2.http.DELETE import retrofit2.http.GET @@ -56,9 +56,9 @@ internal interface RoomAPI { * Ref: https://matrix.org/docs/spec/client_server/r0.4.0.html#post-matrix-client-r0-publicrooms */ @POST(NetworkConstants.URI_API_PREFIX_PATH_R0 + "publicRooms") - fun publicRooms(@Query("server") server: String?, - @Body publicRoomsParams: PublicRoomsParams - ): Call<PublicRoomsResponse> + suspend fun publicRooms(@Query("server") server: String?, + @Body publicRoomsParams: PublicRoomsParams + ): PublicRoomsResponse /** * Create a room. @@ -70,7 +70,7 @@ internal interface RoomAPI { */ @Headers("CONNECT_TIMEOUT:60000", "READ_TIMEOUT:60000", "WRITE_TIMEOUT:60000") @POST(NetworkConstants.URI_API_PREFIX_PATH_R0 + "createRoom") - fun createRoom(@Body param: CreateRoomBody): Call<CreateRoomResponse> + suspend fun createRoom(@Body param: CreateRoomBody): CreateRoomResponse /** * Get a list of messages starting from a reference. @@ -82,12 +82,12 @@ internal interface RoomAPI { * @param filter A JSON RoomEventFilter to filter returned events with. Optional. */ @GET(NetworkConstants.URI_API_PREFIX_PATH_R0 + "rooms/{roomId}/messages") - fun getRoomMessagesFrom(@Path("roomId") roomId: String, - @Query("from") from: String, - @Query("dir") dir: String, - @Query("limit") limit: Int, - @Query("filter") filter: String? - ): Call<PaginationResponse> + suspend fun getRoomMessagesFrom(@Path("roomId") roomId: String, + @Query("from") from: String, + @Query("dir") dir: String, + @Query("limit") limit: Int, + @Query("filter") filter: String? + ): PaginationResponse /** * Get all members of a room @@ -98,11 +98,11 @@ internal interface RoomAPI { * @param notMembership to exclude one type of membership (optional) */ @GET(NetworkConstants.URI_API_PREFIX_PATH_R0 + "rooms/{roomId}/members") - fun getMembers(@Path("roomId") roomId: String, - @Query("at") syncToken: String?, - @Query("membership") membership: String?, - @Query("not_membership") notMembership: String? - ): Call<RoomMembersResponse> + suspend fun getMembers(@Path("roomId") roomId: String, + @Query("at") syncToken: String?, + @Query("membership") membership: Membership?, + @Query("not_membership") notMembership: Membership? + ): RoomMembersResponse /** * Send an event to a room. @@ -113,11 +113,11 @@ internal interface RoomAPI { * @param content the event content */ @PUT(NetworkConstants.URI_API_PREFIX_PATH_R0 + "rooms/{roomId}/send/{eventType}/{txId}") - fun send(@Path("txId") txId: String, - @Path("roomId") roomId: String, - @Path("eventType") eventType: String, - @Body content: Content? - ): Call<SendResponse> + suspend fun send(@Path("txId") txId: String, + @Path("roomId") roomId: String, + @Path("eventType") eventType: String, + @Body content: Content? + ): SendResponse /** * Get the context surrounding an event. @@ -128,10 +128,10 @@ internal interface RoomAPI { * @param filter A JSON RoomEventFilter to filter returned events with. Optional. */ @GET(NetworkConstants.URI_API_PREFIX_PATH_R0 + "rooms/{roomId}/context/{eventId}") - fun getContextOfEvent(@Path("roomId") roomId: String, - @Path("eventId") eventId: String, - @Query("limit") limit: Int, - @Query("filter") filter: String? = null): Call<EventContextResponse> + suspend fun getContextOfEvent(@Path("roomId") roomId: String, + @Path("eventId") eventId: String, + @Query("limit") limit: Int, + @Query("filter") filter: String? = null): EventContextResponse /** * Retrieve an event from its room id / events id @@ -140,8 +140,8 @@ internal interface RoomAPI { * @param eventId the event Id */ @GET(NetworkConstants.URI_API_PREFIX_PATH_R0 + "rooms/{roomId}/event/{eventId}") - fun getEvent(@Path("roomId") roomId: String, - @Path("eventId") eventId: String): Call<Event> + suspend fun getEvent(@Path("roomId") roomId: String, + @Path("eventId") eventId: String): Event /** * Send read markers. @@ -150,8 +150,16 @@ internal interface RoomAPI { * @param markers the read markers */ @POST(NetworkConstants.URI_API_PREFIX_PATH_R0 + "rooms/{roomId}/read_markers") - fun sendReadMarker(@Path("roomId") roomId: String, - @Body markers: Map<String, String>): Call<Unit> + suspend fun sendReadMarker(@Path("roomId") roomId: String, + @Body markers: Map<String, String>) + + /** + * Send receipt to a room + */ + @POST(NetworkConstants.URI_API_PREFIX_PATH_R0 + "rooms/{roomId}/receipt/{receiptType}/{eventId}") + suspend fun sendReceipt(@Path("roomId") roomId: String, + @Path("receiptType") receiptType: String, + @Path("eventId") eventId: String) /** * Invite a user to the given room. @@ -161,8 +169,8 @@ internal interface RoomAPI { * @param body a object that just contains a user id */ @POST(NetworkConstants.URI_API_PREFIX_PATH_R0 + "rooms/{roomId}/invite") - fun invite(@Path("roomId") roomId: String, - @Body body: InviteBody): Call<Unit> + suspend fun invite(@Path("roomId") roomId: String, + @Body body: InviteBody) /** * Invite a user to a room, using a ThreePid @@ -170,8 +178,8 @@ internal interface RoomAPI { * @param roomId Required. The room identifier (not alias) to which to invite the user. */ @POST(NetworkConstants.URI_API_PREFIX_PATH_R0 + "rooms/{roomId}/invite") - fun invite3pid(@Path("roomId") roomId: String, - @Body body: ThreePidInviteBody): Call<Unit> + suspend fun invite3pid(@Path("roomId") roomId: String, + @Body body: ThreePidInviteBody) /** * Send a generic state event @@ -181,9 +189,9 @@ internal interface RoomAPI { * @param params the request parameters */ @PUT(NetworkConstants.URI_API_PREFIX_PATH_R0 + "rooms/{roomId}/state/{state_event_type}") - fun sendStateEvent(@Path("roomId") roomId: String, - @Path("state_event_type") stateEventType: String, - @Body params: JsonDict): Call<Unit> + suspend fun sendStateEvent(@Path("roomId") roomId: String, + @Path("state_event_type") stateEventType: String, + @Body params: JsonDict) /** * Send a generic state event @@ -194,17 +202,17 @@ internal interface RoomAPI { * @param params the request parameters */ @PUT(NetworkConstants.URI_API_PREFIX_PATH_R0 + "rooms/{roomId}/state/{state_event_type}/{state_key}") - fun sendStateEvent(@Path("roomId") roomId: String, - @Path("state_event_type") stateEventType: String, - @Path("state_key") stateKey: String, - @Body params: JsonDict): Call<Unit> + suspend fun sendStateEvent(@Path("roomId") roomId: String, + @Path("state_event_type") stateEventType: String, + @Path("state_key") stateKey: String, + @Body params: JsonDict) /** * Get state events of a room * Ref: https://matrix.org/docs/spec/client_server/r0.6.1#get-matrix-client-r0-rooms-roomid-state */ @GET(NetworkConstants.URI_API_PREFIX_PATH_R0 + "rooms/{roomId}/state") - fun getRoomState(@Path("roomId") roomId: String) : Call<List<Event>> + suspend fun getRoomState(@Path("roomId") roomId: String): List<Event> /** * Send a relation event to a room. @@ -215,12 +223,12 @@ internal interface RoomAPI { * @param content the event content */ @POST(NetworkConstants.URI_API_PREFIX_PATH_R0 + "rooms/{roomId}/send_relation/{parent_id}/{relation_type}/{event_type}") - fun sendRelation(@Path("roomId") roomId: String, - @Path("parent_id") parentId: String, - @Path("relation_type") relationType: String, - @Path("event_type") eventType: String, - @Body content: Content? - ): Call<SendResponse> + suspend fun sendRelation(@Path("roomId") roomId: String, + @Path("parent_id") parentId: String, + @Path("relation_type") relationType: String, + @Path("event_type") eventType: String, + @Body content: Content? + ): SendResponse /** * Paginate relations for event based in normal topological order @@ -229,11 +237,11 @@ internal interface RoomAPI { * @param eventType filter for this event type */ @GET(NetworkConstants.URI_API_PREFIX_PATH_UNSTABLE + "rooms/{roomId}/relations/{eventId}/{relationType}/{eventType}") - fun getRelations(@Path("roomId") roomId: String, - @Path("eventId") eventId: String, - @Path("relationType") relationType: String, - @Path("eventType") eventType: String - ): Call<RelationsResponse> + suspend fun getRelations(@Path("roomId") roomId: String, + @Path("eventId") eventId: String, + @Path("relationType") relationType: String, + @Path("eventType") eventType: String + ): RelationsResponse /** * Join the given room. @@ -243,9 +251,9 @@ internal interface RoomAPI { * @param params the request body */ @POST(NetworkConstants.URI_API_PREFIX_PATH_R0 + "join/{roomIdOrAlias}") - fun join(@Path("roomIdOrAlias") roomIdOrAlias: String, - @Query("server_name") viaServers: List<String>, - @Body params: Map<String, String?>): Call<JoinRoomResponse> + suspend fun join(@Path("roomIdOrAlias") roomIdOrAlias: String, + @Query("server_name") viaServers: List<String>, + @Body params: Map<String, String?>): JoinRoomResponse /** * Leave the given room. @@ -254,8 +262,8 @@ internal interface RoomAPI { * @param params the request body */ @POST(NetworkConstants.URI_API_PREFIX_PATH_R0 + "rooms/{roomId}/leave") - fun leave(@Path("roomId") roomId: String, - @Body params: Map<String, String?>): Call<Unit> + suspend fun leave(@Path("roomId") roomId: String, + @Body params: Map<String, String?>) /** * Ban a user from the given room. @@ -264,8 +272,8 @@ internal interface RoomAPI { * @param userIdAndReason the banned user object (userId and reason for ban) */ @POST(NetworkConstants.URI_API_PREFIX_PATH_R0 + "rooms/{roomId}/ban") - fun ban(@Path("roomId") roomId: String, - @Body userIdAndReason: UserIdAndReason): Call<Unit> + suspend fun ban(@Path("roomId") roomId: String, + @Body userIdAndReason: UserIdAndReason) /** * unban a user from the given room. @@ -274,8 +282,8 @@ internal interface RoomAPI { * @param userIdAndReason the unbanned user object (userId and reason for unban) */ @POST(NetworkConstants.URI_API_PREFIX_PATH_R0 + "rooms/{roomId}/unban") - fun unban(@Path("roomId") roomId: String, - @Body userIdAndReason: UserIdAndReason): Call<Unit> + suspend fun unban(@Path("roomId") roomId: String, + @Body userIdAndReason: UserIdAndReason) /** * Kick a user from the given room. @@ -284,8 +292,8 @@ internal interface RoomAPI { * @param userIdAndReason the kicked user object (userId and reason for kicking) */ @POST(NetworkConstants.URI_API_PREFIX_PATH_R0 + "rooms/{roomId}/kick") - fun kick(@Path("roomId") roomId: String, - @Body userIdAndReason: UserIdAndReason): Call<Unit> + suspend fun kick(@Path("roomId") roomId: String, + @Body userIdAndReason: UserIdAndReason) /** * Strips all information out of an event which isn't critical to the integrity of the server-side representation of the room. @@ -298,12 +306,12 @@ internal interface RoomAPI { * @param reason json containing reason key {"reason": "Indecent material"} */ @PUT(NetworkConstants.URI_API_PREFIX_PATH_R0 + "rooms/{roomId}/redact/{eventId}/{txnId}") - fun redactEvent( + suspend fun redactEvent( @Path("txnId") txId: String, @Path("roomId") roomId: String, @Path("eventId") eventId: String, @Body reason: Map<String, String> - ): Call<SendResponse> + ): SendResponse /** * Reports an event as inappropriate to the server, which may then notify the appropriate people. @@ -313,24 +321,24 @@ internal interface RoomAPI { * @param body body containing score and reason */ @POST(NetworkConstants.URI_API_PREFIX_PATH_R0 + "rooms/{roomId}/report/{eventId}") - fun reportContent(@Path("roomId") roomId: String, - @Path("eventId") eventId: String, - @Body body: ReportContentBody): Call<Unit> + suspend fun reportContent(@Path("roomId") roomId: String, + @Path("eventId") eventId: String, + @Body body: ReportContentBody) /** * Get a list of aliases maintained by the local server for the given room. * Ref: https://matrix.org/docs/spec/client_server/r0.6.1#get-matrix-client-r0-rooms-roomid-aliases */ @GET(NetworkConstants.URI_API_PREFIX_PATH_UNSTABLE + "org.matrix.msc2432/rooms/{roomId}/aliases") - fun getAliases(@Path("roomId") roomId: String): Call<GetAliasesResponse> + suspend fun getAliases(@Path("roomId") roomId: String): GetAliasesResponse /** * Inform that the user is starting to type or has stopped typing */ @PUT(NetworkConstants.URI_API_PREFIX_PATH_R0 + "rooms/{roomId}/typing/{userId}") - fun sendTypingState(@Path("roomId") roomId: String, - @Path("userId") userId: String, - @Body body: TypingBody): Call<Unit> + suspend fun sendTypingState(@Path("roomId") roomId: String, + @Path("userId") userId: String, + @Body body: TypingBody) /** * Room tagging @@ -340,16 +348,16 @@ internal interface RoomAPI { * Add a tag to a room. */ @PUT(NetworkConstants.URI_API_PREFIX_PATH_R0 + "user/{userId}/rooms/{roomId}/tags/{tag}") - fun putTag(@Path("userId") userId: String, - @Path("roomId") roomId: String, - @Path("tag") tag: String, - @Body body: TagBody): Call<Unit> + suspend fun putTag(@Path("userId") userId: String, + @Path("roomId") roomId: String, + @Path("tag") tag: String, + @Body body: TagBody) /** * Delete a tag from a room. */ @DELETE(NetworkConstants.URI_API_PREFIX_PATH_R0 + "user/{userId}/rooms/{roomId}/tags/{tag}") - fun deleteTag(@Path("userId") userId: String, - @Path("roomId") roomId: String, - @Path("tag") tag: String): Call<Unit> + suspend fun deleteTag(@Path("userId") userId: String, + @Path("roomId") roomId: String, + @Path("tag") tag: String) } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/RoomAvatarResolver.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/RoomAvatarResolver.kt index 99f9d3644d58e867a97bb47d3e55df2a622838e9..60ad83ee055ebc668b1e7047138c33c8f4b08b36 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/RoomAvatarResolver.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/RoomAvatarResolver.kt @@ -16,18 +16,19 @@ package org.matrix.android.sdk.internal.session.room +import io.realm.Realm +import org.matrix.android.sdk.api.extensions.orFalse import org.matrix.android.sdk.api.session.events.model.EventType import org.matrix.android.sdk.api.session.events.model.toModel import org.matrix.android.sdk.api.session.room.model.RoomAvatarContent -import org.matrix.android.sdk.internal.database.mapper.ContentMapper +import org.matrix.android.sdk.internal.database.mapper.asDomain import org.matrix.android.sdk.internal.database.model.CurrentStateEventEntity import org.matrix.android.sdk.internal.database.model.RoomMemberSummaryEntityFields +import org.matrix.android.sdk.internal.database.model.RoomSummaryEntity import org.matrix.android.sdk.internal.database.query.getOrNull +import org.matrix.android.sdk.internal.database.query.where import org.matrix.android.sdk.internal.di.UserId import org.matrix.android.sdk.internal.session.room.membership.RoomMemberHelper -import io.realm.Realm -import org.matrix.android.sdk.internal.database.model.RoomSummaryEntity -import org.matrix.android.sdk.internal.database.query.where import javax.inject.Inject internal class RoomAvatarResolver @Inject constructor(@UserId private val userId: String) { @@ -39,24 +40,35 @@ internal class RoomAvatarResolver @Inject constructor(@UserId private val userId * @return the room avatar url, can be a fallback to a room member avatar or null */ fun resolve(realm: Realm, roomId: String): String? { - var res: String? - val roomName = CurrentStateEventEntity.getOrNull(realm, roomId, type = EventType.STATE_ROOM_AVATAR, stateKey = "")?.root - res = ContentMapper.map(roomName?.content).toModel<RoomAvatarContent>()?.avatarUrl - if (!res.isNullOrEmpty()) { - return res + val roomName = CurrentStateEventEntity.getOrNull(realm, roomId, type = EventType.STATE_ROOM_AVATAR, stateKey = "") + ?.root + ?.asDomain() + ?.content + ?.toModel<RoomAvatarContent>() + ?.avatarUrl + if (!roomName.isNullOrEmpty()) { + return roomName } val roomMembers = RoomMemberHelper(realm, roomId) val members = roomMembers.queryActiveRoomMembersEvent().findAll() // detect if it is a room with no more than 2 members (i.e. an alone or a 1:1 chat) - val isDirectRoom = RoomSummaryEntity.where(realm, roomId).findFirst()?.isDirect ?: false + val isDirectRoom = RoomSummaryEntity.where(realm, roomId).findFirst()?.isDirect.orFalse() + if (isDirectRoom) { if (members.size == 1) { - res = members.firstOrNull()?.avatarUrl + // Use avatar of a left user + val firstLeftAvatarUrl = roomMembers.queryLeftRoomMembersEvent() + .findAll() + .firstOrNull { !it.avatarUrl.isNullOrEmpty() } + ?.avatarUrl + + return firstLeftAvatarUrl ?: members.firstOrNull()?.avatarUrl } else if (members.size == 2) { val firstOtherMember = members.where().notEqualTo(RoomMemberSummaryEntityFields.USER_ID, userId).findFirst() - res = firstOtherMember?.avatarUrl + return firstOtherMember?.avatarUrl } } - return res + + return null } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/RoomFactory.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/RoomFactory.kt index 63370a1ad8f6dd884ec15dcf0181bf0a8a177578..90640b47000dbfa674b9a0d793c1ae6ed39a35af 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/RoomFactory.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/RoomFactory.kt @@ -36,7 +36,6 @@ import org.matrix.android.sdk.internal.session.room.timeline.DefaultTimelineServ import org.matrix.android.sdk.internal.session.room.typing.DefaultTypingService import org.matrix.android.sdk.internal.session.room.uploads.DefaultUploadsService import org.matrix.android.sdk.internal.session.search.SearchTask -import org.matrix.android.sdk.internal.task.TaskExecutor import javax.inject.Inject internal interface RoomFactory { @@ -60,7 +59,6 @@ internal class DefaultRoomFactory @Inject constructor(private val cryptoService: private val relationServiceFactory: DefaultRelationService.Factory, private val membershipServiceFactory: DefaultMembershipService.Factory, private val roomPushRuleServiceFactory: DefaultRoomPushRuleService.Factory, - private val taskExecutor: TaskExecutor, private val sendStateTask: SendStateTask, private val searchTask: SearchTask) : RoomFactory { @@ -84,7 +82,6 @@ internal class DefaultRoomFactory @Inject constructor(private val cryptoService: relationService = relationServiceFactory.create(roomId), roomMembersService = membershipServiceFactory.create(roomId), roomPushRuleService = roomPushRuleServiceFactory.create(roomId), - taskExecutor = taskExecutor, sendStateTask = sendStateTask, searchTask = searchTask ) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/RoomModule.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/RoomModule.kt index 66b727236077cb25f132323659fa9092891e711e..5133f72932b2fe3d0ffe05009bb0cc9a2eea7c7a 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/RoomModule.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/RoomModule.kt @@ -79,9 +79,11 @@ import org.matrix.android.sdk.internal.session.room.tags.DefaultDeleteTagFromRoo import org.matrix.android.sdk.internal.session.room.tags.DeleteTagFromRoomTask import org.matrix.android.sdk.internal.session.room.timeline.DefaultFetchTokenAndPaginateTask import org.matrix.android.sdk.internal.session.room.timeline.DefaultGetContextOfEventTask +import org.matrix.android.sdk.internal.session.room.timeline.DefaultGetEventTask import org.matrix.android.sdk.internal.session.room.timeline.DefaultPaginationTask import org.matrix.android.sdk.internal.session.room.timeline.FetchTokenAndPaginateTask import org.matrix.android.sdk.internal.session.room.timeline.GetContextOfEventTask +import org.matrix.android.sdk.internal.session.room.timeline.GetEventTask import org.matrix.android.sdk.internal.session.room.timeline.PaginationTask import org.matrix.android.sdk.internal.session.room.typing.DefaultSendTypingTask import org.matrix.android.sdk.internal.session.room.typing.SendTypingTask @@ -228,4 +230,7 @@ internal abstract class RoomModule { @Binds abstract fun bindPeekRoomTask(task: DefaultPeekRoomTask): PeekRoomTask + + @Binds + abstract fun bindGetEventTask(task: DefaultGetEventTask): GetEventTask } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/alias/AddRoomAliasTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/alias/AddRoomAliasTask.kt index 9e4ec6f77779258ba3c550ce2f3795af69f68c24..97ea1d6ad15aced5961edeadcff1bbcc5f2ef630 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/alias/AddRoomAliasTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/alias/AddRoomAliasTask.kt @@ -45,8 +45,8 @@ internal class DefaultAddRoomAliasTask @Inject constructor( override suspend fun execute(params: AddRoomAliasTask.Params) { aliasAvailabilityChecker.check(params.aliasLocalPart) - executeRequest<Unit>(globalErrorReceiver) { - apiCall = directoryAPI.addRoomAlias( + executeRequest(globalErrorReceiver) { + directoryAPI.addRoomAlias( roomAlias = params.aliasLocalPart.toFullLocalAlias(userId), body = AddRoomAliasBody( roomId = params.roomId diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/alias/DeleteRoomAliasTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/alias/DeleteRoomAliasTask.kt index 6ad3db90a92f42be824ee8c53235a022545bb93f..01ac3fcec84717b63c926fff7bd4b649d066f5cf 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/alias/DeleteRoomAliasTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/alias/DeleteRoomAliasTask.kt @@ -34,8 +34,8 @@ internal class DefaultDeleteRoomAliasTask @Inject constructor( ) : DeleteRoomAliasTask { override suspend fun execute(params: DeleteRoomAliasTask.Params) { - executeRequest<Unit>(globalErrorReceiver) { - apiCall = directoryAPI.deleteRoomAlias( + executeRequest(globalErrorReceiver) { + directoryAPI.deleteRoomAlias( roomAlias = params.roomAlias ) } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/alias/GetRoomIdByAliasTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/alias/GetRoomIdByAliasTask.kt index a53ffc4fcdaba71d05cf0ec0bfb0519bacbd63e0..71c8c9cd38cf0472c585b4b846c7fb6362c4327c 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/alias/GetRoomIdByAliasTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/alias/GetRoomIdByAliasTask.kt @@ -52,8 +52,8 @@ internal class DefaultGetRoomIdByAliasTask @Inject constructor( Optional.from(null) } else { val description = tryOrNull("## Failed to get roomId from alias") { - executeRequest<RoomAliasDescription>(globalErrorReceiver) { - apiCall = directoryAPI.getRoomIdByAlias(params.roomAlias) + executeRequest(globalErrorReceiver) { + directoryAPI.getRoomIdByAlias(params.roomAlias) } } Optional.from(description) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/alias/GetRoomLocalAliasesTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/alias/GetRoomLocalAliasesTask.kt index 202cb1f6de9ea47249e56d0b557d9441ba44dc91..1ff4156ed3e857b2439c007af34ca91018383587 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/alias/GetRoomLocalAliasesTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/alias/GetRoomLocalAliasesTask.kt @@ -35,8 +35,8 @@ internal class DefaultGetRoomLocalAliasesTask @Inject constructor( override suspend fun execute(params: GetRoomLocalAliasesTask.Params): List<String> { // We do not check for "org.matrix.msc2432", so the API may be missing - val response = executeRequest<GetAliasesResponse>(globalErrorReceiver) { - apiCall = roomAPI.getAliases(roomId = params.roomId) + val response = executeRequest(globalErrorReceiver) { + roomAPI.getAliases(roomId = params.roomId) } return response.aliases diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/alias/RoomAliasAvailabilityChecker.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/alias/RoomAliasAvailabilityChecker.kt index 51a849a35e9f9a190284e09a264cca16ac64da70..9faf50dd8b70b2a6fadab5387ef51644f578c853 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/alias/RoomAliasAvailabilityChecker.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/alias/RoomAliasAvailabilityChecker.kt @@ -41,8 +41,8 @@ internal class RoomAliasAvailabilityChecker @Inject constructor( // Check alias availability val fullAlias = aliasLocalPart.toFullLocalAlias(userId) try { - executeRequest<RoomAliasDescription>(globalErrorReceiver) { - apiCall = directoryAPI.getRoomIdByAlias(fullAlias) + executeRequest(globalErrorReceiver) { + directoryAPI.getRoomIdByAlias(fullAlias) } } catch (throwable: Throwable) { if (throwable is Failure.ServerError && throwable.httpCode == 404) { diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/create/CreateRoomTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/create/CreateRoomTask.kt index 9c16bd1b0f2214ec1d56a9f3eb448145fa6f3b78..bafe2b90ae0b197773436d386d29ff56b8083080 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/create/CreateRoomTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/create/CreateRoomTask.kt @@ -17,18 +17,19 @@ package org.matrix.android.sdk.internal.session.room.create import com.zhuinden.monarchy.Monarchy +import io.realm.Realm import io.realm.RealmConfiguration import kotlinx.coroutines.TimeoutCancellationException import org.matrix.android.sdk.api.failure.Failure import org.matrix.android.sdk.api.failure.MatrixError import org.matrix.android.sdk.api.session.room.alias.RoomAliasError import org.matrix.android.sdk.api.session.room.failure.CreateRoomFailure +import org.matrix.android.sdk.api.session.room.model.Membership import org.matrix.android.sdk.api.session.room.model.create.CreateRoomParams import org.matrix.android.sdk.api.session.room.model.create.CreateRoomPreset import org.matrix.android.sdk.internal.database.awaitNotEmptyResult -import org.matrix.android.sdk.internal.database.model.RoomEntity -import org.matrix.android.sdk.internal.database.model.RoomEntityFields import org.matrix.android.sdk.internal.database.model.RoomSummaryEntity +import org.matrix.android.sdk.internal.database.model.RoomSummaryEntityFields import org.matrix.android.sdk.internal.database.query.where import org.matrix.android.sdk.internal.di.SessionDatabase import org.matrix.android.sdk.internal.network.GlobalErrorReceiver @@ -75,8 +76,8 @@ internal class DefaultCreateRoomTask @Inject constructor( val createRoomBody = createRoomBodyBuilder.build(params) val createRoomResponse = try { - executeRequest<CreateRoomResponse>(globalErrorReceiver) { - apiCall = roomAPI.createRoom(createRoomBody) + executeRequest(globalErrorReceiver) { + roomAPI.createRoom(createRoomBody) } } catch (throwable: Throwable) { if (throwable is Failure.ServerError) { @@ -96,12 +97,18 @@ internal class DefaultCreateRoomTask @Inject constructor( // Wait for room to come back from the sync (but it can maybe be in the DB if the sync response is received before) try { awaitNotEmptyResult(realmConfiguration, TimeUnit.MINUTES.toMillis(1L)) { realm -> - realm.where(RoomEntity::class.java) - .equalTo(RoomEntityFields.ROOM_ID, roomId) + realm.where(RoomSummaryEntity::class.java) + .equalTo(RoomSummaryEntityFields.ROOM_ID, roomId) + .equalTo(RoomSummaryEntityFields.MEMBERSHIP_STR, Membership.JOIN.name) } } catch (exception: TimeoutCancellationException) { throw CreateRoomFailure.CreatedWithTimeout } + + Realm.getInstance(realmConfiguration).executeTransactionAsync { + RoomSummaryEntity.where(it, roomId).findFirst()?.lastActivityTime = System.currentTimeMillis() + } + if (otherUserId != null) { handleDirectChatCreation(roomId, otherUserId) } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/directory/GetPublicRoomTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/directory/GetPublicRoomTask.kt index edd8ae9b0dfe9a0531f5ee8179604502ffb0f1e0..4a6b0703c58067d6c85491a838f1e52fd6337003 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/directory/GetPublicRoomTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/directory/GetPublicRoomTask.kt @@ -38,7 +38,7 @@ internal class DefaultGetPublicRoomTask @Inject constructor( override suspend fun execute(params: GetPublicRoomTask.Params): PublicRoomsResponse { return executeRequest(globalErrorReceiver) { - apiCall = roomAPI.publicRooms(params.server, params.publicRoomsParams) + roomAPI.publicRooms(params.server, params.publicRoomsParams) } } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/directory/GetRoomDirectoryVisibilityTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/directory/GetRoomDirectoryVisibilityTask.kt index 8d71001ef958345e5e8fe97703a8536111a61bbc..77492e429f3bee1f7dd2102a6711648791566378 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/directory/GetRoomDirectoryVisibilityTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/directory/GetRoomDirectoryVisibilityTask.kt @@ -20,7 +20,6 @@ import org.matrix.android.sdk.api.session.room.model.RoomDirectoryVisibility import org.matrix.android.sdk.internal.network.GlobalErrorReceiver import org.matrix.android.sdk.internal.network.executeRequest import org.matrix.android.sdk.internal.session.directory.DirectoryAPI -import org.matrix.android.sdk.internal.session.directory.RoomDirectoryVisibilityJson import org.matrix.android.sdk.internal.task.Task import javax.inject.Inject @@ -36,8 +35,8 @@ internal class DefaultGetRoomDirectoryVisibilityTask @Inject constructor( ) : GetRoomDirectoryVisibilityTask { override suspend fun execute(params: GetRoomDirectoryVisibilityTask.Params): RoomDirectoryVisibility { - return executeRequest<RoomDirectoryVisibilityJson>(globalErrorReceiver) { - apiCall = directoryAPI.getRoomDirectoryVisibility(params.roomId) + return executeRequest(globalErrorReceiver) { + directoryAPI.getRoomDirectoryVisibility(params.roomId) } .visibility } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/directory/SetRoomDirectoryVisibilityTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/directory/SetRoomDirectoryVisibilityTask.kt index cbb0b6d5d1b85259fe308ad6652823fa51933316..f46d06bd5cd58a493d85099a5a6a2b625e32cc45 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/directory/SetRoomDirectoryVisibilityTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/directory/SetRoomDirectoryVisibilityTask.kt @@ -37,8 +37,8 @@ internal class DefaultSetRoomDirectoryVisibilityTask @Inject constructor( ) : SetRoomDirectoryVisibilityTask { override suspend fun execute(params: SetRoomDirectoryVisibilityTask.Params) { - executeRequest<Unit>(globalErrorReceiver) { - apiCall = directoryAPI.setRoomDirectoryVisibility( + executeRequest(globalErrorReceiver) { + directoryAPI.setRoomDirectoryVisibility( params.roomId, RoomDirectoryVisibilityJson(visibility = params.roomDirectoryVisibility) ) 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 cd1c9bbbdd8e56a7f32ec0eb26cc24956320bc5a..41e891f78e6221c5dcd3f1be16b5abca5b7ee74c 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 @@ -21,13 +21,11 @@ import dagger.assisted.Assisted import dagger.assisted.AssistedInject import dagger.assisted.AssistedFactory import com.zhuinden.monarchy.Monarchy -import org.matrix.android.sdk.api.MatrixCallback import org.matrix.android.sdk.api.session.identity.ThreePid import org.matrix.android.sdk.api.session.room.members.MembershipService import org.matrix.android.sdk.api.session.room.members.RoomMemberQueryParams import org.matrix.android.sdk.api.session.room.model.Membership import org.matrix.android.sdk.api.session.room.model.RoomMemberSummary -import org.matrix.android.sdk.api.util.Cancelable import org.matrix.android.sdk.internal.database.mapper.asDomain import org.matrix.android.sdk.internal.database.model.RoomMemberSummaryEntity import org.matrix.android.sdk.internal.database.model.RoomMemberSummaryEntityFields @@ -39,8 +37,6 @@ import org.matrix.android.sdk.internal.session.room.membership.joining.InviteTas import org.matrix.android.sdk.internal.session.room.membership.joining.JoinRoomTask import org.matrix.android.sdk.internal.session.room.membership.leaving.LeaveRoomTask import org.matrix.android.sdk.internal.session.room.membership.threepid.InviteThreePidTask -import org.matrix.android.sdk.internal.task.TaskExecutor -import org.matrix.android.sdk.internal.task.configureWith import org.matrix.android.sdk.internal.util.fetchCopied import io.realm.Realm import io.realm.RealmQuery @@ -48,7 +44,6 @@ import io.realm.RealmQuery internal class DefaultMembershipService @AssistedInject constructor( @Assisted private val roomId: String, @SessionDatabase private val monarchy: Monarchy, - private val taskExecutor: TaskExecutor, private val loadRoomMembersTask: LoadRoomMembersTask, private val inviteTask: InviteTask, private val inviteThreePidTask: InviteThreePidTask, @@ -64,13 +59,9 @@ internal class DefaultMembershipService @AssistedInject constructor( fun create(roomId: String): DefaultMembershipService } - override fun loadRoomMembersIfNeeded(matrixCallback: MatrixCallback<Unit>): Cancelable { + override suspend fun loadRoomMembersIfNeeded() { val params = LoadRoomMembersTask.Params(roomId, Membership.LEAVE) - return loadRoomMembersTask - .configureWith(params) { - this.callback = matrixCallback - } - .executeBy(taskExecutor) + loadRoomMembersTask.execute(params) } override fun getRoomMember(userId: String): RoomMemberSummary? { @@ -120,66 +111,38 @@ internal class DefaultMembershipService @AssistedInject constructor( } } - override fun ban(userId: String, reason: String?, callback: MatrixCallback<Unit>): Cancelable { + override suspend fun ban(userId: String, reason: String?) { val params = MembershipAdminTask.Params(MembershipAdminTask.Type.BAN, roomId, userId, reason) - return membershipAdminTask - .configureWith(params) { - this.callback = callback - } - .executeBy(taskExecutor) + membershipAdminTask.execute(params) } - override fun unban(userId: String, reason: String?, callback: MatrixCallback<Unit>): Cancelable { + override suspend fun unban(userId: String, reason: String?) { val params = MembershipAdminTask.Params(MembershipAdminTask.Type.UNBAN, roomId, userId, reason) - return membershipAdminTask - .configureWith(params) { - this.callback = callback - } - .executeBy(taskExecutor) + membershipAdminTask.execute(params) } - override fun kick(userId: String, reason: String?, callback: MatrixCallback<Unit>): Cancelable { + override suspend fun kick(userId: String, reason: String?) { val params = MembershipAdminTask.Params(MembershipAdminTask.Type.KICK, roomId, userId, reason) - return membershipAdminTask - .configureWith(params) { - this.callback = callback - } - .executeBy(taskExecutor) + membershipAdminTask.execute(params) } - override fun invite(userId: String, reason: String?, callback: MatrixCallback<Unit>): Cancelable { + override suspend fun invite(userId: String, reason: String?) { val params = InviteTask.Params(roomId, userId, reason) - return inviteTask - .configureWith(params) { - this.callback = callback - } - .executeBy(taskExecutor) + inviteTask.execute(params) } - override fun invite3pid(threePid: ThreePid, callback: MatrixCallback<Unit>): Cancelable { + override suspend fun invite3pid(threePid: ThreePid) { val params = InviteThreePidTask.Params(roomId, threePid) - return inviteThreePidTask - .configureWith(params) { - this.callback = callback - } - .executeBy(taskExecutor) + return inviteThreePidTask.execute(params) } - override fun join(reason: String?, viaServers: List<String>, callback: MatrixCallback<Unit>): Cancelable { + override suspend fun join(reason: String?, viaServers: List<String>) { val params = JoinRoomTask.Params(roomId, reason, viaServers) - return joinTask - .configureWith(params) { - this.callback = callback - } - .executeBy(taskExecutor) + joinTask.execute(params) } - override fun leave(reason: String?, callback: MatrixCallback<Unit>): Cancelable { + override suspend fun leave(reason: String?) { val params = LeaveRoomTask.Params(roomId, reason) - return leaveRoomTask - .configureWith(params) { - this.callback = callback - } - .executeBy(taskExecutor) + leaveRoomTask.execute(params) } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/membership/LoadRoomMembersTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/membership/LoadRoomMembersTask.kt index 97cfcdaa443bc285080dcd84a9fedf080b1f6113..3d0f51b831cf67c98394608a0d0b67f51f758a79 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/membership/LoadRoomMembersTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/membership/LoadRoomMembersTask.kt @@ -22,6 +22,8 @@ import io.realm.kotlin.createObject import kotlinx.coroutines.TimeoutCancellationException import org.matrix.android.sdk.api.session.room.model.Membership import org.matrix.android.sdk.api.session.room.send.SendState +import org.matrix.android.sdk.internal.crypto.CryptoSessionInfoProvider +import org.matrix.android.sdk.internal.crypto.DeviceListManager import org.matrix.android.sdk.internal.database.awaitNotEmptyResult import org.matrix.android.sdk.internal.database.mapper.toEntity import org.matrix.android.sdk.internal.database.model.CurrentStateEventEntity @@ -57,6 +59,8 @@ internal class DefaultLoadRoomMembersTask @Inject constructor( private val syncTokenStore: SyncTokenStore, private val roomSummaryUpdater: RoomSummaryUpdater, private val roomMemberEventHandler: RoomMemberEventHandler, + private val cryptoSessionInfoProvider: CryptoSessionInfoProvider, + private val deviceListManager: DeviceListManager, private val globalErrorReceiver: GlobalErrorReceiver ) : LoadRoomMembersTask { @@ -86,8 +90,8 @@ internal class DefaultLoadRoomMembersTask @Inject constructor( val lastToken = syncTokenStore.getLastToken() val response = try { - executeRequest<RoomMembersResponse>(globalErrorReceiver) { - apiCall = roomAPI.getMembers(params.roomId, lastToken, null, params.excludeMembership?.value) + executeRequest(globalErrorReceiver) { + roomAPI.getMembers(params.roomId, lastToken, null, params.excludeMembership) } } catch (throwable: Throwable) { // Revert status to NONE @@ -124,6 +128,10 @@ internal class DefaultLoadRoomMembersTask @Inject constructor( roomEntity.membersLoadStatus = RoomMembersLoadStatusType.LOADED roomSummaryUpdater.update(realm, roomId, updateMembers = true) } + + if (cryptoSessionInfoProvider.isRoomEncrypted(roomId)) { + deviceListManager.onRoomMembersLoadedFor(roomId) + } } private fun getRoomMembersLoadStatus(roomId: String): RoomMembersLoadStatusType { diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/membership/RoomDisplayNameResolver.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/membership/RoomDisplayNameResolver.kt index 0e18e30b130ab73a6551f1087f50e94c63853c9a..3aa812d93db69d5ea28e2235fa0b9e25eb221dfb 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/membership/RoomDisplayNameResolver.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/membership/RoomDisplayNameResolver.kt @@ -18,6 +18,7 @@ package org.matrix.android.sdk.internal.session.room.membership import io.realm.Realm import org.matrix.android.sdk.api.MatrixConfiguration +import org.matrix.android.sdk.api.extensions.orFalse import org.matrix.android.sdk.api.session.events.model.EventType import org.matrix.android.sdk.api.session.events.model.toModel import org.matrix.android.sdk.api.session.room.model.Membership @@ -51,14 +52,14 @@ internal class RoomDisplayNameResolver @Inject constructor( * @param roomId: the roomId to resolve the name of. * @return the room display name */ - fun resolve(realm: Realm, roomId: String): CharSequence { + fun resolve(realm: Realm, roomId: String): String { // this algorithm is the one defined in // https://github.com/matrix-org/matrix-js-sdk/blob/develop/lib/models/room.js#L617 // calculateRoomName(room, userId) // For Lazy Loaded room, see algorithm here: // https://docs.google.com/document/d/11i14UI1cUz-OJ0knD5BFu7fmT6Fo327zvMYqfSAR7xs/edit#heading=h.qif6pkqyjgzn - var name: CharSequence? + var name: String? val roomEntity = RoomEntity.where(realm, roomId = roomId).findFirst() val roomName = CurrentStateEventEntity.getOrNull(realm, roomId, type = EventType.STATE_ROOM_NAME, stateKey = "")?.root name = ContentMapper.map(roomName?.content).toModel<RoomNameContent>()?.name @@ -77,14 +78,14 @@ internal class RoomDisplayNameResolver @Inject constructor( if (roomEntity?.membership == Membership.INVITE) { val inviteMeEvent = roomMembers.getLastStateEvent(userId) val inviterId = inviteMeEvent?.sender - name = if (inviterId != null) { - activeMembers.where() - .equalTo(RoomMemberSummaryEntityFields.USER_ID, inviterId) - .findFirst() - ?.displayName - } else { - roomDisplayNameFallbackProvider.getNameForRoomInvite() - } + name = inviterId + ?.let { + activeMembers.where() + .equalTo(RoomMemberSummaryEntityFields.USER_ID, it) + .findFirst() + ?.getBestName() + } + ?: roomDisplayNameFallbackProvider.getNameForRoomInvite() } else if (roomEntity?.membership == Membership.JOIN) { val roomSummary = RoomSummaryEntity.where(realm, roomId).findFirst() val invitedCount = roomSummary?.invitedMembersCount ?: 0 @@ -105,10 +106,17 @@ internal class RoomDisplayNameResolver @Inject constructor( val otherMembersCount = otherMembersSubset.count() name = when (otherMembersCount) { 0 -> { - roomDisplayNameFallbackProvider.getNameForEmptyRoom() - // TODO (was xx and yyy) ... + // Get left members if any + val leftMembersNames = roomMembers.queryLeftRoomMembersEvent() + .findAll() + .map { it.getBestName() } + roomDisplayNameFallbackProvider.getNameForEmptyRoom(roomSummary?.isDirect.orFalse(), leftMembersNames) + } + 1 -> { + roomDisplayNameFallbackProvider.getNameFor1member( + resolveRoomMemberName(otherMembersSubset[0], roomMembers) + ) } - 1 -> resolveRoomMemberName(otherMembersSubset[0], roomMembers) 2 -> { roomDisplayNameFallbackProvider.getNameFor2members( resolveRoomMemberName(otherMembersSubset[0], roomMembers), @@ -145,12 +153,11 @@ internal class RoomDisplayNameResolver @Inject constructor( } /** See [org.matrix.android.sdk.api.session.room.sender.SenderInfo.disambiguatedDisplayName] */ - private fun resolveRoomMemberName(roomMemberSummary: RoomMemberSummaryEntity?, - roomMemberHelper: RoomMemberHelper): String? { - if (roomMemberSummary == null) return null + private fun resolveRoomMemberName(roomMemberSummary: RoomMemberSummaryEntity, + roomMemberHelper: RoomMemberHelper): String { val isUnique = roomMemberHelper.isUniqueDisplayName(roomMemberSummary.displayName) return if (isUnique) { - roomMemberSummary.displayName + roomMemberSummary.getBestName() } else { "${roomMemberSummary.displayName} (${roomMemberSummary.userId})" } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/membership/RoomMemberEventHandler.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/membership/RoomMemberEventHandler.kt index 89fe2901c085e8ced39006bdd0c6594684c8b508..2ecacf335b0a365cd9d9c5c43fc996a1630470c3 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/membership/RoomMemberEventHandler.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/membership/RoomMemberEventHandler.kt @@ -16,12 +16,12 @@ package org.matrix.android.sdk.internal.session.room.membership +import io.realm.Realm import org.matrix.android.sdk.api.session.events.model.Event import org.matrix.android.sdk.api.session.events.model.EventType -import org.matrix.android.sdk.api.session.events.model.toModel import org.matrix.android.sdk.api.session.room.model.RoomMemberContent +import org.matrix.android.sdk.internal.session.events.getFixedRoomMemberContent import org.matrix.android.sdk.internal.session.user.UserEntityFactory -import io.realm.Realm import javax.inject.Inject internal class RoomMemberEventHandler @Inject constructor() { @@ -31,7 +31,7 @@ internal class RoomMemberEventHandler @Inject constructor() { return false } val userId = event.stateKey ?: return false - val roomMember = event.content.toModel<RoomMemberContent>() + val roomMember = event.getFixedRoomMemberContent() return handle(realm, roomId, userId, roomMember) } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/membership/RoomMemberHelper.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/membership/RoomMemberHelper.kt index 2a7c46bd4238fff80d69c97f6f9b56bcbdda7218..9ce8db25a5bec865c4412bc7d7c257b096f94e6d 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/membership/RoomMemberHelper.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/membership/RoomMemberHelper.kt @@ -75,6 +75,11 @@ internal class RoomMemberHelper(private val realm: Realm, .equalTo(RoomMemberSummaryEntityFields.MEMBERSHIP_STR, Membership.INVITE.name) } + fun queryLeftRoomMembersEvent(): RealmQuery<RoomMemberSummaryEntity> { + return queryRoomMembersEvent() + .equalTo(RoomMemberSummaryEntityFields.MEMBERSHIP_STR, Membership.LEAVE.name) + } + fun queryActiveRoomMembersEvent(): RealmQuery<RoomMemberSummaryEntity> { return queryRoomMembersEvent() .beginGroup() diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/membership/admin/MembershipAdminTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/membership/admin/MembershipAdminTask.kt index 4654a2853692d4e813c954d4bd23cb6743fb33cc..d2c21f352038aff9cbc9c0fff3ab87a18769719a 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/membership/admin/MembershipAdminTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/membership/admin/MembershipAdminTask.kt @@ -41,8 +41,8 @@ internal class DefaultMembershipAdminTask @Inject constructor(private val roomAP override suspend fun execute(params: MembershipAdminTask.Params) { val userIdAndReason = UserIdAndReason(params.userId, params.reason) - executeRequest<Unit>(null) { - apiCall = when (params.type) { + executeRequest(null) { + when (params.type) { MembershipAdminTask.Type.BAN -> roomAPI.ban(params.roomId, userIdAndReason) MembershipAdminTask.Type.UNBAN -> roomAPI.unban(params.roomId, userIdAndReason) MembershipAdminTask.Type.KICK -> roomAPI.kick(params.roomId, userIdAndReason) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/membership/joining/InviteTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/membership/joining/InviteTask.kt index 05503bd6436742c551a6991463b1fd0b98fc51d5..7e7b80baaf5cc946ede7df91afcfb98063b7bd73 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/membership/joining/InviteTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/membership/joining/InviteTask.kt @@ -36,11 +36,13 @@ internal class DefaultInviteTask @Inject constructor( ) : InviteTask { override suspend fun execute(params: InviteTask.Params) { - return executeRequest(globalErrorReceiver) { - val body = InviteBody(params.userId, params.reason) - apiCall = roomAPI.invite(params.roomId, body) - isRetryable = true - maxRetryCount = 3 + val body = InviteBody(params.userId, params.reason) + return executeRequest( + globalErrorReceiver, + canRetry = true, + maxRetriesCount = 3 + ) { + roomAPI.invite(params.roomId, body) } } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/membership/joining/JoinRoomTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/membership/joining/JoinRoomTask.kt index 3b7639d42fa28b0fd4f9693451a8e23144c598f8..33776e4f6ece17d41a1e1910167425093ff6d291 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/membership/joining/JoinRoomTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/membership/joining/JoinRoomTask.kt @@ -16,21 +16,23 @@ package org.matrix.android.sdk.internal.session.room.membership.joining +import io.realm.Realm +import io.realm.RealmConfiguration +import kotlinx.coroutines.TimeoutCancellationException import org.matrix.android.sdk.api.session.room.failure.JoinRoomFailure import org.matrix.android.sdk.api.session.room.members.ChangeMembershipState +import org.matrix.android.sdk.api.session.room.model.Membership import org.matrix.android.sdk.internal.database.awaitNotEmptyResult -import org.matrix.android.sdk.internal.database.model.RoomEntity -import org.matrix.android.sdk.internal.database.model.RoomEntityFields +import org.matrix.android.sdk.internal.database.model.RoomSummaryEntity +import org.matrix.android.sdk.internal.database.model.RoomSummaryEntityFields +import org.matrix.android.sdk.internal.database.query.where import org.matrix.android.sdk.internal.di.SessionDatabase +import org.matrix.android.sdk.internal.network.GlobalErrorReceiver import org.matrix.android.sdk.internal.network.executeRequest import org.matrix.android.sdk.internal.session.room.RoomAPI -import org.matrix.android.sdk.internal.session.room.create.JoinRoomResponse import org.matrix.android.sdk.internal.session.room.membership.RoomChangeMembershipStateDataSource import org.matrix.android.sdk.internal.session.room.read.SetReadMarkersTask import org.matrix.android.sdk.internal.task.Task -import io.realm.RealmConfiguration -import kotlinx.coroutines.TimeoutCancellationException -import org.matrix.android.sdk.internal.network.GlobalErrorReceiver import java.util.concurrent.TimeUnit import javax.inject.Inject @@ -54,8 +56,8 @@ internal class DefaultJoinRoomTask @Inject constructor( override suspend fun execute(params: JoinRoomTask.Params) { roomChangeMembershipStateDataSource.updateState(params.roomIdOrAlias, ChangeMembershipState.Joining) val joinRoomResponse = try { - executeRequest<JoinRoomResponse>(globalErrorReceiver) { - apiCall = roomAPI.join( + executeRequest(globalErrorReceiver) { + roomAPI.join( roomIdOrAlias = params.roomIdOrAlias, viaServers = params.viaServers.take(3), params = mapOf("reason" to params.reason) @@ -69,12 +71,18 @@ internal class DefaultJoinRoomTask @Inject constructor( val roomId = joinRoomResponse.roomId try { awaitNotEmptyResult(realmConfiguration, TimeUnit.MINUTES.toMillis(1L)) { realm -> - realm.where(RoomEntity::class.java) - .equalTo(RoomEntityFields.ROOM_ID, roomId) + realm.where(RoomSummaryEntity::class.java) + .equalTo(RoomSummaryEntityFields.ROOM_ID, roomId) + .equalTo(RoomSummaryEntityFields.MEMBERSHIP_STR, Membership.JOIN.name) } } catch (exception: TimeoutCancellationException) { throw JoinRoomFailure.JoinedWithTimeout } + + Realm.getInstance(realmConfiguration).executeTransactionAsync { + RoomSummaryEntity.where(it, roomId).findFirst()?.lastActivityTime = System.currentTimeMillis() + } + setReadMarkers(roomId) } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/membership/leaving/LeaveRoomTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/membership/leaving/LeaveRoomTask.kt index 37bb7570d17d599ab2e984ed4da88b343a1647c4..1b836e36a681da6db9d3031050c7b761cc25726f 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/membership/leaving/LeaveRoomTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/membership/leaving/LeaveRoomTask.kt @@ -68,8 +68,8 @@ internal class DefaultLeaveRoomTask @Inject constructor( leaveRoom(predecessorRoomId, reason) } try { - executeRequest<Unit>(globalErrorReceiver) { - apiCall = roomAPI.leave(roomId, mapOf("reason" to reason)) + executeRequest(globalErrorReceiver) { + roomAPI.leave(roomId, mapOf("reason" to reason)) } } catch (failure: Throwable) { roomChangeMembershipStateDataSource.updateState(roomId, ChangeMembershipState.FailedLeaving(failure)) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/membership/threepid/InviteThreePidTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/membership/threepid/InviteThreePidTask.kt index d237ec795e2334b2ad92c8c75e2dbe35ba57bbb6..fa0a2d608a4bbc34c165cbb14904042f60e0667e 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/membership/threepid/InviteThreePidTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/membership/threepid/InviteThreePidTask.kt @@ -59,7 +59,7 @@ internal class DefaultInviteThreePidTask @Inject constructor( medium = params.threePid.toMedium(), address = params.threePid.value ) - apiCall = roomAPI.invite3pid(params.roomId, body) + roomAPI.invite3pid(params.roomId, body) } } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/peeking/ResolveRoomStateTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/peeking/ResolveRoomStateTask.kt index dbec6b555c6fa791b43c5c9fbe2417298c9f9f6e..64cbef23ecda6186b8c1aa202005b2053f579f5c 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/peeking/ResolveRoomStateTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/peeking/ResolveRoomStateTask.kt @@ -36,7 +36,7 @@ internal class DefaultResolveRoomStateTask @Inject constructor( override suspend fun execute(params: ResolveRoomStateTask.Params): List<Event> { return executeRequest(globalErrorReceiver) { - apiCall = roomAPI.getRoomState(params.roomId) + roomAPI.getRoomState(params.roomId) } } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/read/DefaultReadService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/read/DefaultReadService.kt index 3cf8cfe5eef3d8f181004a51b666cb07347c3448..d4d03dca05abe3f788cd349e81f914d9b8ed3658 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/read/DefaultReadService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/read/DefaultReadService.kt @@ -22,7 +22,6 @@ import dagger.assisted.Assisted import dagger.assisted.AssistedInject import dagger.assisted.AssistedFactory import com.zhuinden.monarchy.Monarchy -import org.matrix.android.sdk.api.MatrixCallback import org.matrix.android.sdk.api.session.room.model.ReadReceipt import org.matrix.android.sdk.api.session.room.read.ReadService import org.matrix.android.sdk.api.util.Optional @@ -36,7 +35,6 @@ import org.matrix.android.sdk.internal.database.query.where import org.matrix.android.sdk.internal.di.SessionDatabase import org.matrix.android.sdk.internal.di.UserId import org.matrix.android.sdk.internal.task.TaskExecutor -import org.matrix.android.sdk.internal.task.configureWith internal class DefaultReadService @AssistedInject constructor( @Assisted private val roomId: String, @@ -52,35 +50,23 @@ internal class DefaultReadService @AssistedInject constructor( fun create(roomId: String): DefaultReadService } - override fun markAsRead(params: ReadService.MarkAsReadParams, callback: MatrixCallback<Unit>) { + override suspend fun markAsRead(params: ReadService.MarkAsReadParams) { val taskParams = SetReadMarkersTask.Params( roomId = roomId, forceReadMarker = params.forceReadMarker(), forceReadReceipt = params.forceReadReceipt() ) - setReadMarkersTask - .configureWith(taskParams) { - this.callback = callback - } - .executeBy(taskExecutor) + setReadMarkersTask.execute(taskParams) } - override fun setReadReceipt(eventId: String, callback: MatrixCallback<Unit>) { + override suspend fun setReadReceipt(eventId: String) { val params = SetReadMarkersTask.Params(roomId, fullyReadEventId = null, readReceiptEventId = eventId) - setReadMarkersTask - .configureWith(params) { - this.callback = callback - } - .executeBy(taskExecutor) + setReadMarkersTask.execute(params) } - override fun setReadMarker(fullyReadEventId: String, callback: MatrixCallback<Unit>) { + override suspend fun setReadMarker(fullyReadEventId: String) { val params = SetReadMarkersTask.Params(roomId, fullyReadEventId = fullyReadEventId, readReceiptEventId = null) - setReadMarkersTask - .configureWith(params) { - this.callback = callback - } - .executeBy(taskExecutor) + setReadMarkersTask.execute(params) } override fun isEventRead(eventId: String): Boolean { diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/read/SetReadMarkersTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/read/SetReadMarkersTask.kt index c7f962a699349900f6fba87f8d8f0d85470c1914..e4147d55b8e24e2542dd8706898db79dbed7800c 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/read/SetReadMarkersTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/read/SetReadMarkersTask.kt @@ -62,7 +62,7 @@ internal class DefaultSetReadMarkersTask @Inject constructor( ) : SetReadMarkersTask { override suspend fun execute(params: SetReadMarkersTask.Params) { - val markers = HashMap<String, String>() + val markers = mutableMapOf<String, String>() Timber.v("Execute set read marker with params: $params") val latestSyncedEventId = latestSyncedEventId(params.roomId) val fullyReadEventId = if (params.forceReadMarker) { @@ -96,9 +96,18 @@ internal class DefaultSetReadMarkersTask @Inject constructor( updateDatabase(params.roomId, markers, shouldUpdateRoomSummary) } if (markers.isNotEmpty()) { - executeRequest<Unit>(globalErrorReceiver) { - isRetryable = true - apiCall = roomAPI.sendReadMarker(params.roomId, markers) + executeRequest( + globalErrorReceiver, + canRetry = true + ) { + if (markers[READ_MARKER] == null) { + if (readReceiptEventId != null) { + roomAPI.sendReceipt(params.roomId, READ_RECEIPT, readReceiptEventId) + } + } else { + // "m.fully_read" value is mandatory to make this call + roomAPI.sendReadMarker(params.roomId, markers) + } } } } @@ -108,7 +117,7 @@ internal class DefaultSetReadMarkersTask @Inject constructor( TimelineEventEntity.latestEvent(realm, roomId = roomId, includesSending = false)?.eventId } - private suspend fun updateDatabase(roomId: String, markers: HashMap<String, String>, shouldUpdateRoomSummary: Boolean) { + private suspend fun updateDatabase(roomId: String, markers: Map<String, String>, shouldUpdateRoomSummary: Boolean) { monarchy.awaitTransaction { realm -> val readMarkerId = markers[READ_MARKER] val readReceiptId = markers[READ_RECEIPT] @@ -117,7 +126,7 @@ internal class DefaultSetReadMarkersTask @Inject constructor( } if (readReceiptId != null) { val readReceiptContent = ReadReceiptHandler.createContent(userId, readReceiptId) - readReceiptHandler.handle(realm, roomId, readReceiptContent, false) + readReceiptHandler.handle(realm, roomId, readReceiptContent, false, null) } if (shouldUpdateRoomSummary) { val roomSummary = RoomSummaryEntity.where(realm, roomId).findFirst() diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/relation/FetchEditHistoryTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/relation/FetchEditHistoryTask.kt index f9fd5f9348755bc43f58b5cf80f009ec045791cc..5f5c000171aeb122cbb525a618dbc1e74c7dec29 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/relation/FetchEditHistoryTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/relation/FetchEditHistoryTask.kt @@ -40,8 +40,8 @@ internal class DefaultFetchEditHistoryTask @Inject constructor( override suspend fun execute(params: FetchEditHistoryTask.Params): List<Event> { val isRoomEncrypted = cryptoSessionInfoProvider.isRoomEncrypted(params.roomId) - val response = executeRequest<RelationsResponse>(globalErrorReceiver) { - apiCall = roomAPI.getRelations( + val response = executeRequest(globalErrorReceiver) { + roomAPI.getRelations( roomId = params.roomId, eventId = params.eventId, relationType = RelationType.REPLACE, diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/relation/SendRelationWorker.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/relation/SendRelationWorker.kt index 403aa274fed8e541cb0f3eabf8495bfb64b25699..5d0879d706e538048adec505b3abed64c6b701a0 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/relation/SendRelationWorker.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/relation/SendRelationWorker.kt @@ -28,7 +28,6 @@ import org.matrix.android.sdk.internal.network.executeRequest import org.matrix.android.sdk.internal.session.SessionComponent import org.matrix.android.sdk.internal.session.room.RoomAPI import org.matrix.android.sdk.internal.session.room.send.LocalEchoRepository -import org.matrix.android.sdk.internal.session.room.send.SendResponse import org.matrix.android.sdk.internal.worker.SessionSafeCoroutineWorker import org.matrix.android.sdk.internal.worker.SessionWorkerParams import javax.inject.Inject @@ -84,8 +83,8 @@ internal class SendRelationWorker(context: Context, params: WorkerParameters) } private suspend fun sendRelation(roomId: String, relationType: String, relatedEventId: String, localEvent: Event) { - executeRequest<SendResponse>(globalErrorReceiver) { - apiCall = roomAPI.sendRelation( + executeRequest(globalErrorReceiver) { + roomAPI.sendRelation( roomId = roomId, parentId = relatedEventId, relationType = relationType, diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/reporting/ReportContentTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/reporting/ReportContentTask.kt index 9c6e9907a4119e88435779f6d850fab12e61a540..29d507dfc546218c4f75d286434fd73f6adf8ce8 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/reporting/ReportContentTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/reporting/ReportContentTask.kt @@ -38,7 +38,7 @@ internal class DefaultReportContentTask @Inject constructor( override suspend fun execute(params: ReportContentTask.Params) { return executeRequest(globalErrorReceiver) { - apiCall = roomAPI.reportContent(params.roomId, params.eventId, ReportContentBody(params.score, params.reason)) + roomAPI.reportContent(params.roomId, params.eventId, ReportContentBody(params.score, params.reason)) } } } 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 c5b8b42b3c48916acb37e1d5460ec044217d2074..26a87557ff490686964c163542bf0965c92e2768 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 @@ -318,7 +318,7 @@ internal class DefaultSendService @AssistedInject constructor( .setConstraints(WorkManagerProvider.workConstraints) .startChain(true) .setInputData(uploadWorkData) - .setBackoffCriteria(BackoffPolicy.LINEAR, WorkManagerProvider.BACKOFF_DELAY, TimeUnit.MILLISECONDS) + .setBackoffCriteria(BackoffPolicy.LINEAR, WorkManagerProvider.BACKOFF_DELAY_MILLIS, TimeUnit.MILLISECONDS) .build() } @@ -332,7 +332,7 @@ internal class DefaultSendService @AssistedInject constructor( // .setConstraints(WorkManagerProvider.workConstraints) .startChain(false) .setInputData(workData) - .setBackoffCriteria(BackoffPolicy.LINEAR, WorkManagerProvider.BACKOFF_DELAY, TimeUnit.MILLISECONDS) + .setBackoffCriteria(BackoffPolicy.LINEAR, WorkManagerProvider.BACKOFF_DELAY_MILLIS, TimeUnit.MILLISECONDS) .build() } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/RedactEventWorker.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/RedactEventWorker.kt index c901c7e18eb0d72c1c95ce56cdcf1f4b29e68d49..306f865408395917213e7d47e787449df414f284 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/RedactEventWorker.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/RedactEventWorker.kt @@ -55,8 +55,8 @@ internal class RedactEventWorker(context: Context, params: WorkerParameters) override suspend fun doSafeWork(params: Params): Result { val eventId = params.eventId return runCatching { - executeRequest<SendResponse>(globalErrorReceiver) { - apiCall = roomAPI.redactEvent( + executeRequest(globalErrorReceiver) { + roomAPI.redactEvent( params.txID, params.roomId, eventId, diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/SendEventWorker.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/SendEventWorker.kt index c1fc2fd9fee405d1f0936a8b44bb8477b8f377a4..d55dce57afaadb87a322ba60772616008c7d482f 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/SendEventWorker.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/SendEventWorker.kt @@ -91,7 +91,7 @@ internal class SendEventWorker(context: Context, if (/*currentAttemptCount >= MAX_NUMBER_OF_RETRY_BEFORE_FAILING ||**/ !exception.shouldBeRetried()) { Timber.e("## SendEvent: [${System.currentTimeMillis()}] Send event Failed cannot retry ${params.eventId} > ${exception.localizedMessage}") localEchoRepository.updateSendState(event.eventId, event.roomId, SendState.UNDELIVERED) - return Result.success() + Result.success() } else { Timber.e("## SendEvent: [${System.currentTimeMillis()}] Send event Failed schedule retry ${params.eventId} > ${exception.localizedMessage}") Result.retry() 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 297233298956f176a8494dc0182e68ea853ff21f..a5c09f5ff69aa80ab593fa0bb49479e0efd6b332 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 @@ -24,6 +24,7 @@ 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.session.crypto.CryptoService import org.matrix.android.sdk.api.session.events.model.Event import org.matrix.android.sdk.api.util.Cancelable @@ -148,8 +149,7 @@ internal class EventSenderProcessorCoroutine @Inject constructor( task.markAsFailedOrRetry(exception, 0) } (exception is Failure.ServerError && exception.error.code == MatrixError.M_LIMIT_EXCEEDED) -> { - val delay = exception.error.retryAfterMillis?.plus(100) ?: 3_000 - task.markAsFailedOrRetry(exception, delay) + task.markAsFailedOrRetry(exception, exception.getRetryDelay(3_000)) } exception is CancellationException -> { Timber.v("## $task has been cancelled, try next task") 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 f2640fd1e733d21629f505d8a3b3a4434403273a..615bc99096d63055e1df98b7cea56f9279a998bc 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 @@ -27,10 +27,8 @@ import org.matrix.android.sdk.api.session.events.model.EventType import org.matrix.android.sdk.api.session.events.model.toContent import org.matrix.android.sdk.api.session.room.model.GuestAccess import org.matrix.android.sdk.api.session.room.model.RoomCanonicalAliasContent -import org.matrix.android.sdk.api.session.room.model.RoomGuestAccessContent import org.matrix.android.sdk.api.session.room.model.RoomHistoryVisibility import org.matrix.android.sdk.api.session.room.model.RoomJoinRules -import org.matrix.android.sdk.api.session.room.model.RoomJoinRulesContent import org.matrix.android.sdk.api.session.room.state.StateService import org.matrix.android.sdk.api.util.JsonDict import org.matrix.android.sdk.api.util.MimeTypes @@ -131,14 +129,14 @@ internal class DefaultStateService @AssistedInject constructor(@Assisted private if (joinRules != null) { sendStateEvent( eventType = EventType.STATE_ROOM_JOIN_RULES, - body = RoomJoinRulesContent(joinRules).toContent(), + body = mapOf("join_rule" to joinRules), stateKey = null ) } if (guestAccess != null) { sendStateEvent( eventType = EventType.STATE_ROOM_GUEST_ACCESS, - body = RoomGuestAccessContent(guestAccess).toContent(), + body = mapOf("guest_access" to guestAccess), stateKey = null ) } 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 63691d9207e816109f0f3a8fdc24dd8a0f91d77c..998e116a0ec94de5759a30bc59ba955812448f7a 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 @@ -39,7 +39,7 @@ internal class DefaultSendStateTask @Inject constructor( override suspend fun execute(params: SendStateTask.Params) { return executeRequest(globalErrorReceiver) { - apiCall = if (params.stateKey == null) { + if (params.stateKey == null) { roomAPI.sendStateEvent( roomId = params.roomId, stateEventType = params.eventType, diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/summary/RoomSummaryDataSource.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/summary/RoomSummaryDataSource.kt index 107055b8c3ce308704201ba4f98585bb1cdf7a1c..dd3fbe04b27890a13a3325e0ac39bb28a65c476d 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/summary/RoomSummaryDataSource.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/summary/RoomSummaryDataSource.kt @@ -18,10 +18,18 @@ package org.matrix.android.sdk.internal.session.room.summary import androidx.lifecycle.LiveData import androidx.lifecycle.Transformations +import androidx.paging.LivePagedListBuilder +import androidx.paging.PagedList import com.zhuinden.monarchy.Monarchy +import io.realm.Realm +import io.realm.RealmQuery +import io.realm.Sort +import org.matrix.android.sdk.api.query.RoomCategoryFilter import org.matrix.android.sdk.api.session.room.RoomSummaryQueryParams +import org.matrix.android.sdk.api.session.room.UpdatableFilterLivePageResult import org.matrix.android.sdk.api.session.room.model.RoomSummary import org.matrix.android.sdk.api.session.room.model.VersioningState +import org.matrix.android.sdk.api.session.room.summary.RoomAggregateNotificationCount import org.matrix.android.sdk.api.util.Optional import org.matrix.android.sdk.api.util.toOptional import org.matrix.android.sdk.internal.database.mapper.RoomSummaryMapper @@ -32,8 +40,6 @@ import org.matrix.android.sdk.internal.database.query.where import org.matrix.android.sdk.internal.di.SessionDatabase import org.matrix.android.sdk.internal.query.process import org.matrix.android.sdk.internal.util.fetchCopyMap -import io.realm.Realm -import io.realm.RealmQuery import javax.inject.Inject internal class RoomSummaryDataSource @Inject constructor(@SessionDatabase private val monarchy: Monarchy, @@ -98,6 +104,62 @@ internal class RoomSummaryDataSource @Inject constructor(@SessionDatabase privat .sort(RoomSummaryEntityFields.BREADCRUMBS_INDEX) } + fun getSortedPagedRoomSummariesLive(queryParams: RoomSummaryQueryParams, + pagedListConfig: PagedList.Config): LiveData<PagedList<RoomSummary>> { + val realmDataSourceFactory = monarchy.createDataSourceFactory { realm -> + roomSummariesQuery(realm, queryParams) + .sort(RoomSummaryEntityFields.LAST_ACTIVITY_TIME, Sort.DESCENDING) + } + val dataSourceFactory = realmDataSourceFactory.map { + roomSummaryMapper.map(it) + } + return monarchy.findAllPagedWithChanges( + realmDataSourceFactory, + LivePagedListBuilder(dataSourceFactory, pagedListConfig) + ) + } + + fun getFilteredPagedRoomSummariesLive(queryParams: RoomSummaryQueryParams, + pagedListConfig: PagedList.Config): UpdatableFilterLivePageResult { + val realmDataSourceFactory = monarchy.createDataSourceFactory { realm -> + roomSummariesQuery(realm, queryParams) + .sort(RoomSummaryEntityFields.LAST_ACTIVITY_TIME, Sort.DESCENDING) + } + val dataSourceFactory = realmDataSourceFactory.map { + roomSummaryMapper.map(it) + } + + val mapped = monarchy.findAllPagedWithChanges( + realmDataSourceFactory, + LivePagedListBuilder(dataSourceFactory, pagedListConfig) + ) + + return object : UpdatableFilterLivePageResult { + override val livePagedList: LiveData<PagedList<RoomSummary>> = mapped + + override fun updateQuery(queryParams: RoomSummaryQueryParams) { + realmDataSourceFactory.updateQuery { + roomSummariesQuery(it, queryParams) + .sort(RoomSummaryEntityFields.LAST_ACTIVITY_TIME, Sort.DESCENDING) + } + } + } + } + + fun getNotificationCountForRooms(queryParams: RoomSummaryQueryParams): RoomAggregateNotificationCount { + var notificationCount: RoomAggregateNotificationCount? = null + monarchy.doWithRealm { realm -> + val roomSummariesQuery = roomSummariesQuery(realm, queryParams) + val notifCount = roomSummariesQuery.sum(RoomSummaryEntityFields.NOTIFICATION_COUNT).toInt() + val highlightCount = roomSummariesQuery.sum(RoomSummaryEntityFields.HIGHLIGHT_COUNT).toInt() + notificationCount = RoomAggregateNotificationCount( + notifCount, + highlightCount + ) + } + return notificationCount!! + } + private fun roomSummariesQuery(realm: Realm, queryParams: RoomSummaryQueryParams): RealmQuery<RoomSummaryEntity> { val query = RoomSummaryEntity.where(realm) query.process(RoomSummaryEntityFields.ROOM_ID, queryParams.roomId) @@ -105,6 +167,28 @@ internal class RoomSummaryDataSource @Inject constructor(@SessionDatabase privat query.process(RoomSummaryEntityFields.CANONICAL_ALIAS, queryParams.canonicalAlias) query.process(RoomSummaryEntityFields.MEMBERSHIP_STR, queryParams.memberships) query.notEqualTo(RoomSummaryEntityFields.VERSIONING_STATE_STR, VersioningState.UPGRADED_ROOM_JOINED.name) + + queryParams.roomCategoryFilter?.let { + when (it) { + RoomCategoryFilter.ONLY_DM -> query.equalTo(RoomSummaryEntityFields.IS_DIRECT, true) + RoomCategoryFilter.ONLY_ROOMS -> query.equalTo(RoomSummaryEntityFields.IS_DIRECT, false) + RoomCategoryFilter.ONLY_WITH_NOTIFICATIONS -> query.greaterThan(RoomSummaryEntityFields.NOTIFICATION_COUNT, 0) + RoomCategoryFilter.ALL -> { + // nop + } + } + } + queryParams.roomTagQueryFilter?.let { + it.isFavorite?.let { fav -> + query.equalTo(RoomSummaryEntityFields.IS_FAVOURITE, fav) + } + it.isLowPriority?.let { lp -> + query.equalTo(RoomSummaryEntityFields.IS_LOW_PRIORITY, lp) + } + it.isServerNotice?.let { sn -> + query.equalTo(RoomSummaryEntityFields.IS_SERVER_NOTICE, sn) + } + } return query } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/summary/RoomSummaryUpdater.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/summary/RoomSummaryUpdater.kt index fff780fb0c65ce5d063d502c087797424c700394..7913bf71a2ac69f55687fc0c840e8091ac47af56 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/summary/RoomSummaryUpdater.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/summary/RoomSummaryUpdater.kt @@ -98,11 +98,16 @@ internal class RoomSummaryUpdater @Inject constructor( val latestPreviewableEvent = RoomSummaryEventsHelper.getLatestPreviewableEvent(realm, roomId) + val lastActivityFromEvent = latestPreviewableEvent?.root?.originServerTs + if (lastActivityFromEvent != null) { + roomSummaryEntity.lastActivityTime = lastActivityFromEvent + } + roomSummaryEntity.hasUnreadMessages = roomSummaryEntity.notificationCount > 0 // avoid this call if we are sure there are unread events || !isEventRead(realm.configuration, userId, roomId, latestPreviewableEvent?.eventId) - roomSummaryEntity.displayName = roomDisplayNameResolver.resolve(realm, roomId).toString() + roomSummaryEntity.displayName = roomDisplayNameResolver.resolve(realm, roomId) roomSummaryEntity.avatarUrl = roomAvatarResolver.resolve(realm, roomId) roomSummaryEntity.name = ContentMapper.map(lastNameEvent?.content).toModel<RoomNameContent>()?.name roomSummaryEntity.topic = ContentMapper.map(lastTopicEvent?.content).toModel<RoomTopicContent>()?.topic @@ -112,9 +117,7 @@ internal class RoomSummaryUpdater @Inject constructor( val roomAliases = ContentMapper.map(lastAliasesEvent?.content).toModel<RoomAliasesContent>()?.aliases .orEmpty() - roomSummaryEntity.aliases.clear() - roomSummaryEntity.aliases.addAll(roomAliases) - roomSummaryEntity.flatAliases = roomAliases.joinToString(separator = "|", prefix = "|") + roomSummaryEntity.updateAliases(roomAliases) roomSummaryEntity.isEncrypted = encryptionEvent != null roomSummaryEntity.encryptionEventTs = encryptionEvent?.originServerTs @@ -131,8 +134,8 @@ internal class RoomSummaryUpdater @Inject constructor( // mmm i want to decrypt now or is it ok to do it async? tryOrNull { eventDecryptor.decryptEvent(root.asDomain(), "") - // eventDecryptor.decryptEventAsync(root.asDomain(), "", NoOpMatrixCallback()) } + ?.let { root.setDecryptionResult(it) } } if (updateMembers) { @@ -144,7 +147,7 @@ internal class RoomSummaryUpdater @Inject constructor( roomSummaryEntity.otherMemberIds.clear() roomSummaryEntity.otherMemberIds.addAll(otherRoomMembers) - if (roomSummaryEntity.isEncrypted) { + if (roomSummaryEntity.isEncrypted && otherRoomMembers.isNotEmpty()) { // mmm maybe we could only refresh shield instead of checking trust also? crossSigningService.onUsersDeviceUpdate(otherRoomMembers) } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/tags/AddTagToRoomTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/tags/AddTagToRoomTask.kt index c3b5c3f78fa2448e6099b7167efb77562ad89323..3e82d674ce0768b9952753bda77bccb898009ccd 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/tags/AddTagToRoomTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/tags/AddTagToRoomTask.kt @@ -39,8 +39,8 @@ internal class DefaultAddTagToRoomTask @Inject constructor( ) : AddTagToRoomTask { override suspend fun execute(params: AddTagToRoomTask.Params) { - executeRequest<Unit>(globalErrorReceiver) { - apiCall = roomAPI.putTag( + executeRequest(globalErrorReceiver) { + roomAPI.putTag( userId = userId, roomId = params.roomId, tag = params.tag, diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/tags/DeleteTagFromRoomTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/tags/DeleteTagFromRoomTask.kt index d578d21fde13c558aa488a3cd5b89232f28d2b86..ae2a050659caa9be7e1ccd65ef316bbc6cfe59b9 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/tags/DeleteTagFromRoomTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/tags/DeleteTagFromRoomTask.kt @@ -38,8 +38,8 @@ internal class DefaultDeleteTagFromRoomTask @Inject constructor( ) : DeleteTagFromRoomTask { override suspend fun execute(params: DeleteTagFromRoomTask.Params) { - executeRequest<Unit>(globalErrorReceiver) { - apiCall = roomAPI.deleteTag( + executeRequest(globalErrorReceiver) { + roomAPI.deleteTag( userId = userId, roomId = params.roomId, tag = params.tag diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/DefaultTimeline.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/DefaultTimeline.kt index d0946abe28c1747ff705dc52d962ee169a1a31c1..e230599f8f6d1d013cfd4d03ae6d720be6bcff18 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/DefaultTimeline.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/DefaultTimeline.kt @@ -28,7 +28,6 @@ import org.matrix.android.sdk.api.NoOpMatrixCallback import org.matrix.android.sdk.api.extensions.orFalse import org.matrix.android.sdk.api.extensions.tryOrNull import org.matrix.android.sdk.api.session.events.model.EventType -import org.matrix.android.sdk.api.session.room.model.ReadReceipt import org.matrix.android.sdk.api.session.room.send.SendState import org.matrix.android.sdk.api.session.room.timeline.Timeline import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent @@ -44,6 +43,7 @@ import org.matrix.android.sdk.internal.database.query.findAllInRoomWithSendState import org.matrix.android.sdk.internal.database.query.where import org.matrix.android.sdk.internal.database.query.whereRoomId import org.matrix.android.sdk.internal.session.room.membership.LoadRoomMembersTask +import org.matrix.android.sdk.internal.session.sync.ReadReceiptHandler import org.matrix.android.sdk.internal.task.TaskExecutor import org.matrix.android.sdk.internal.task.configureWith import org.matrix.android.sdk.internal.util.Debouncer @@ -69,13 +69,12 @@ internal class DefaultTimeline( private val paginationTask: PaginationTask, private val timelineEventMapper: TimelineEventMapper, private val settings: TimelineSettings, - private val hiddenReadReceipts: TimelineHiddenReadReceipts, private val timelineInput: TimelineInput, private val eventDecryptor: TimelineEventDecryptor, private val realmSessionProvider: RealmSessionProvider, - private val loadRoomMembersTask: LoadRoomMembersTask + private val loadRoomMembersTask: LoadRoomMembersTask, + private val readReceiptHandler: ReadReceiptHandler ) : Timeline, - TimelineHiddenReadReceipts.Delegate, TimelineInput.Listener, UIEchoManager.Listener { @@ -91,8 +90,7 @@ internal class DefaultTimeline( private val cancelableBag = CancelableBag() private val debouncer = Debouncer(mainHandler) - private lateinit var nonFilteredEvents: RealmResults<TimelineEventEntity> - private lateinit var filteredEvents: RealmResults<TimelineEventEntity> + private lateinit var timelineEvents: RealmResults<TimelineEventEntity> private lateinit var sendingEvents: RealmResults<TimelineEventEntity> private var prevDisplayIndex: Int? = null @@ -166,29 +164,34 @@ internal class DefaultTimeline( postSnapshot() } - nonFilteredEvents = buildEventQuery(realm).sort(TimelineEventEntityFields.DISPLAY_INDEX, Sort.DESCENDING).findAll() - filteredEvents = nonFilteredEvents.where() - .filterEventsWithSettings(settings) - .findAll() - nonFilteredEvents.addChangeListener(eventsChangeListener) + timelineEvents = buildEventQuery(realm).sort(TimelineEventEntityFields.DISPLAY_INDEX, Sort.DESCENDING).findAll() + timelineEvents.addChangeListener(eventsChangeListener) handleInitialLoad() - if (settings.shouldHandleHiddenReadReceipts()) { - hiddenReadReceipts.start(realm, filteredEvents, nonFilteredEvents, this) - } - loadRoomMembersTask .configureWith(LoadRoomMembersTask.Params(roomId)) { this.callback = NoOpMatrixCallback() } .executeBy(taskExecutor) + // Ensure ReadReceipt from init sync are loaded + ensureReadReceiptAreLoaded(realm) + isReady.set(true) } } } - private fun TimelineSettings.shouldHandleHiddenReadReceipts(): Boolean { - return buildReadReceipts && (filters.filterEdits || filters.filterTypes) + private fun ensureReadReceiptAreLoaded(realm: Realm) { + readReceiptHandler.getContentFromInitSync(roomId) + ?.also { + Timber.w("INIT_SYNC Insert when opening timeline RR for room $roomId") + } + ?.let { readReceiptContent -> + realm.executeTransactionAsync { + readReceiptHandler.handle(it, roomId, readReceiptContent, false, null) + readReceiptHandler.onContentFromInitSyncHandled(roomId) + } + } } override fun dispose() { @@ -202,11 +205,8 @@ internal class DefaultTimeline( if (this::sendingEvents.isInitialized) { sendingEvents.removeAllChangeListeners() } - if (this::nonFilteredEvents.isInitialized) { - nonFilteredEvents.removeAllChangeListeners() - } - if (settings.shouldHandleHiddenReadReceipts()) { - hiddenReadReceipts.dispose() + if (this::timelineEvents.isInitialized) { + timelineEvents.removeAllChangeListeners() } clearAllValues() backgroundRealm.getAndSet(null).also { @@ -238,48 +238,6 @@ internal class DefaultTimeline( } } - override fun getFirstDisplayableEventId(eventId: String): String? { - // If the item is built, the id is obviously displayable - val builtIndex = builtEventsIdMap[eventId] - if (builtIndex != null) { - return eventId - } - // Otherwise, we should check if the event is in the db, but is hidden because of filters - return realmSessionProvider.withRealm { localRealm -> - val nonFilteredEvents = buildEventQuery(localRealm) - .sort(TimelineEventEntityFields.DISPLAY_INDEX, Sort.DESCENDING) - .findAll() - - val nonFilteredEvent = nonFilteredEvents.where() - .equalTo(TimelineEventEntityFields.EVENT_ID, eventId) - .findFirst() - - val filteredEvents = nonFilteredEvents.where() - .filterEventsWithSettings(settings) - .findAll() - val isEventInDb = nonFilteredEvent != null - - val isHidden = isEventInDb && filteredEvents.where() - .equalTo(TimelineEventEntityFields.EVENT_ID, eventId) - .findFirst() == null - - if (isHidden) { - val displayIndex = nonFilteredEvent?.displayIndex - if (displayIndex != null) { - // Then we are looking for the first displayable event after the hidden one - val firstDisplayedEvent = filteredEvents.where() - .lessThanOrEqualTo(TimelineEventEntityFields.DISPLAY_INDEX, displayIndex) - .findFirst() - firstDisplayedEvent?.eventId - } else { - null - } - } else { - null - } - } - } - override fun hasMoreToLoad(direction: Timeline.Direction): Boolean { return hasMoreInCache(direction) || !hasReachedEnd(direction) } @@ -301,18 +259,6 @@ internal class DefaultTimeline( listeners.clear() } -// TimelineHiddenReadReceipts.Delegate - - override fun rebuildEvent(eventId: String, readReceipts: List<ReadReceipt>): Boolean { - return rebuildEvent(eventId) { te -> - te.copy(readReceipts = readReceipts) - } - } - - override fun onReadReceiptsUpdated() { - postSnapshot() - } - override fun onNewTimelineEvents(roomId: String, eventIds: List<String>) { if (isLive && this.roomId == roomId) { listeners.forEach { @@ -323,18 +269,13 @@ internal class DefaultTimeline( override fun onLocalEchoCreated(roomId: String, timelineEvent: TimelineEvent) { if (roomId != this.roomId || !isLive) return - - val postSnapShot = uiEchoManager.onLocalEchoCreated(timelineEvent) - - if (listOf(timelineEvent).filterEventsWithSettings(settings).isNotEmpty()) { - listeners.forEach { + uiEchoManager.onLocalEchoCreated(timelineEvent) + listeners.forEach { + tryOrNull { it.onNewTimelineEvents(listOf(timelineEvent.eventId)) } } - - if (postSnapShot) { - postSnapshot() - } + postSnapshot() } override fun onLocalEchoUpdated(roomId: String, eventId: String, sendState: SendState) { @@ -421,23 +362,21 @@ internal class DefaultTimeline( val builtSendingEvents = mutableListOf<TimelineEvent>() if (hasReachedEnd(Timeline.Direction.FORWARDS) && !hasMoreInCache(Timeline.Direction.FORWARDS)) { uiEchoManager.getInMemorySendingEvents() - .filterSendingEventsTo(builtSendingEvents) + .updateWithUiEchoInto(builtSendingEvents) sendingEvents .filter { timelineEvent -> builtSendingEvents.none { it.eventId == timelineEvent.eventId } } .map { timelineEventMapper.map(it) } - .filterSendingEventsTo(builtSendingEvents) + .updateWithUiEchoInto(builtSendingEvents) } return builtSendingEvents } - private fun List<TimelineEvent>.filterSendingEventsTo(target: MutableList<TimelineEvent>) { + private fun List<TimelineEvent>.updateWithUiEchoInto(target: MutableList<TimelineEvent>) { target.addAll( - // Filter out sending event that are not displayable! - filterEventsWithSettings(settings) - // Get most up to date send state (in memory) - .map { uiEchoManager.updateSentStateWithUiEcho(it) } + // Get most up to date send state (in memory) + map { uiEchoManager.updateSentStateWithUiEcho(it) } ) } @@ -469,9 +408,9 @@ internal class DefaultTimeline( var shouldFetchInitialEvent = false val currentInitialEventId = initialEventId val initialDisplayIndex = if (currentInitialEventId == null) { - nonFilteredEvents.firstOrNull()?.displayIndex + timelineEvents.firstOrNull()?.displayIndex } else { - val initialEvent = nonFilteredEvents.where() + val initialEvent = timelineEvents.where() .equalTo(TimelineEventEntityFields.EVENT_ID, initialEventId) .findFirst() @@ -483,7 +422,7 @@ internal class DefaultTimeline( if (currentInitialEventId != null && shouldFetchInitialEvent) { fetchEvent(currentInitialEventId) } else { - val count = filteredEvents.size.coerceAtMost(settings.initialSize) + val count = timelineEvents.size.coerceAtMost(settings.initialSize) if (initialEventId == null) { paginateInternal(initialDisplayIndex, Timeline.Direction.BACKWARDS, count) } else { @@ -523,8 +462,7 @@ internal class DefaultTimeline( val eventEntity = results[index] eventEntity?.eventId?.let { eventId -> postSnapshot = rebuildEvent(eventId) { - val builtEvent = buildTimelineEvent(eventEntity) - listOf(builtEvent).filterEventsWithSettings(settings).firstOrNull() + buildTimelineEvent(eventEntity) } || postSnapshot } } @@ -545,9 +483,9 @@ internal class DefaultTimeline( // We are in the case where event exists, but we do not know the token. // Fetch (again) the last event to get a token val lastKnownEventId = if (direction == Timeline.Direction.FORWARDS) { - nonFilteredEvents.firstOrNull()?.eventId + timelineEvents.firstOrNull()?.eventId } else { - nonFilteredEvents.lastOrNull()?.eventId + timelineEvents.lastOrNull()?.eventId } if (lastKnownEventId == null) { updateState(direction) { it.copy(isPaginating = false, requestedPaginationCount = 0) } @@ -618,7 +556,7 @@ internal class DefaultTimeline( * Return the current Chunk */ private fun getLiveChunk(): ChunkEntity? { - return nonFilteredEvents.firstOrNull()?.chunk?.firstOrNull() + return timelineEvents.firstOrNull()?.chunk?.firstOrNull() } /** @@ -662,17 +600,18 @@ internal class DefaultTimeline( val time = System.currentTimeMillis() - start Timber.v("Built ${offsetResults.size} items from db in $time ms") // For the case where wo reach the lastForward chunk - updateLoadingStates(filteredEvents) + updateLoadingStates(timelineEvents) return offsetResults.size } - private fun buildTimelineEvent(eventEntity: TimelineEventEntity) = timelineEventMapper.map( - timelineEventEntity = eventEntity, - buildReadReceipts = settings.buildReadReceipts, - correctedReadReceipts = hiddenReadReceipts.correctedReadReceipts(eventEntity.eventId) - ).let { - // eventually enhance with ui echo? - (uiEchoManager.decorateEventWithReactionUiEcho(it) ?: it) + private fun buildTimelineEvent(eventEntity: TimelineEventEntity): TimelineEvent { + return timelineEventMapper.map( + timelineEventEntity = eventEntity, + buildReadReceipts = settings.buildReadReceipts + ).let { timelineEvent -> + // eventually enhance with ui echo? + uiEchoManager.decorateEventWithReactionUiEcho(timelineEvent) ?: timelineEvent + } } /** @@ -681,7 +620,7 @@ internal class DefaultTimeline( private fun getOffsetResults(startDisplayIndex: Int, direction: Timeline.Direction, count: Long): RealmResults<TimelineEventEntity> { - val offsetQuery = filteredEvents.where() + val offsetQuery = timelineEvents.where() if (direction == Timeline.Direction.BACKWARDS) { offsetQuery .sort(TimelineEventEntityFields.DISPLAY_INDEX, Sort.DESCENDING) @@ -729,7 +668,7 @@ internal class DefaultTimeline( if (isReady.get().not()) { return@post } - updateLoadingStates(filteredEvents) + updateLoadingStates(timelineEvents) val snapshot = createSnapshot() val runnable = Runnable { listeners.forEach { diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/DefaultTimelineService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/DefaultTimelineService.kt index d000bbeb5027b17fcf08512053fa03980caf49e7..8de36d0427633af6a7fdfbfa150cf0c1df95028d 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/DefaultTimelineService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/DefaultTimelineService.kt @@ -17,10 +17,10 @@ package org.matrix.android.sdk.internal.session.room.timeline import androidx.lifecycle.LiveData +import com.zhuinden.monarchy.Monarchy import dagger.assisted.Assisted -import dagger.assisted.AssistedInject import dagger.assisted.AssistedFactory -import com.zhuinden.monarchy.Monarchy +import dagger.assisted.AssistedInject import io.realm.Sort import io.realm.kotlin.where import org.matrix.android.sdk.api.session.events.model.isImageMessage @@ -31,27 +31,28 @@ import org.matrix.android.sdk.api.session.room.timeline.TimelineService import org.matrix.android.sdk.api.session.room.timeline.TimelineSettings import org.matrix.android.sdk.api.util.Optional import org.matrix.android.sdk.internal.database.RealmSessionProvider -import org.matrix.android.sdk.internal.database.mapper.ReadReceiptsSummaryMapper import org.matrix.android.sdk.internal.database.mapper.TimelineEventMapper import org.matrix.android.sdk.internal.database.model.TimelineEventEntity import org.matrix.android.sdk.internal.database.model.TimelineEventEntityFields import org.matrix.android.sdk.internal.database.query.where import org.matrix.android.sdk.internal.di.SessionDatabase import org.matrix.android.sdk.internal.session.room.membership.LoadRoomMembersTask +import org.matrix.android.sdk.internal.session.sync.ReadReceiptHandler import org.matrix.android.sdk.internal.task.TaskExecutor -internal class DefaultTimelineService @AssistedInject constructor(@Assisted private val roomId: String, - @SessionDatabase private val monarchy: Monarchy, - private val realmSessionProvider: RealmSessionProvider, - private val timelineInput: TimelineInput, - private val taskExecutor: TaskExecutor, - private val contextOfEventTask: GetContextOfEventTask, - private val eventDecryptor: TimelineEventDecryptor, - private val paginationTask: PaginationTask, - private val fetchTokenAndPaginateTask: FetchTokenAndPaginateTask, - private val timelineEventMapper: TimelineEventMapper, - private val readReceiptsSummaryMapper: ReadReceiptsSummaryMapper, - private val loadRoomMembersTask: LoadRoomMembersTask +internal class DefaultTimelineService @AssistedInject constructor( + @Assisted private val roomId: String, + @SessionDatabase private val monarchy: Monarchy, + private val realmSessionProvider: RealmSessionProvider, + private val timelineInput: TimelineInput, + private val taskExecutor: TaskExecutor, + private val contextOfEventTask: GetContextOfEventTask, + private val eventDecryptor: TimelineEventDecryptor, + private val paginationTask: PaginationTask, + private val fetchTokenAndPaginateTask: FetchTokenAndPaginateTask, + private val timelineEventMapper: TimelineEventMapper, + private val loadRoomMembersTask: LoadRoomMembersTask, + private val readReceiptHandler: ReadReceiptHandler ) : TimelineService { @AssistedFactory @@ -69,12 +70,12 @@ internal class DefaultTimelineService @AssistedInject constructor(@Assisted priv paginationTask = paginationTask, timelineEventMapper = timelineEventMapper, settings = settings, - hiddenReadReceipts = TimelineHiddenReadReceipts(readReceiptsSummaryMapper, roomId, settings), timelineInput = timelineInput, eventDecryptor = eventDecryptor, fetchTokenAndPaginateTask = fetchTokenAndPaginateTask, realmSessionProvider = realmSessionProvider, - loadRoomMembersTask = loadRoomMembersTask + loadRoomMembersTask = loadRoomMembersTask, + readReceiptHandler = readReceiptHandler ) } @@ -87,7 +88,7 @@ internal class DefaultTimelineService @AssistedInject constructor(@Assisted priv } override fun getTimeLineEventLive(eventId: String): LiveData<Optional<TimelineEvent>> { - return LiveTimelineEvent(timelineInput, monarchy, taskExecutor.executorScope, timelineEventMapper, roomId, eventId) + return LiveTimelineEvent(monarchy, taskExecutor.executorScope, timelineEventMapper, roomId, eventId) } override fun getAttachmentMessages(): List<TimelineEvent> { diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/Extensions.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/Extensions.kt deleted file mode 100644 index b2c8021f3b49487f3d705369c0b18c4db9709bc6..0000000000000000000000000000000000000000 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/Extensions.kt +++ /dev/null @@ -1,50 +0,0 @@ -/* - * Copyright (c) 2021 The Matrix.org Foundation C.I.C. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.matrix.android.sdk.internal.session.room.timeline - -import io.realm.RealmQuery -import org.matrix.android.sdk.api.session.events.model.EventType -import org.matrix.android.sdk.api.session.events.model.RelationType -import org.matrix.android.sdk.api.session.events.model.toModel -import org.matrix.android.sdk.api.session.room.model.message.MessageContent -import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent -import org.matrix.android.sdk.api.session.room.timeline.TimelineSettings -import org.matrix.android.sdk.internal.database.model.TimelineEventEntity -import org.matrix.android.sdk.internal.database.query.filterEvents - -internal fun RealmQuery<TimelineEventEntity>.filterEventsWithSettings(settings: TimelineSettings): RealmQuery<TimelineEventEntity> { - return filterEvents(settings.filters) -} - -internal fun List<TimelineEvent>.filterEventsWithSettings(settings: TimelineSettings): List<TimelineEvent> { - return filter { event -> - val filterType = !settings.filters.filterTypes - || settings.filters.allowedTypes.any { it.eventType == event.root.type && (it.stateKey == null || it.stateKey == event.root.senderId) } - if (!filterType) return@filter false - - val filterEdits = if (settings.filters.filterEdits && event.root.getClearType() == EventType.MESSAGE) { - val messageContent = event.root.getClearContent().toModel<MessageContent>() - messageContent?.relatesTo?.type != RelationType.REPLACE && messageContent?.relatesTo?.type != RelationType.RESPONSE - } else { - true - } - if (!filterEdits) return@filter false - - val filterRedacted = settings.filters.filterRedacted && event.root.isRedacted() - !filterRedacted - } -} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/FetchTokenAndPaginateTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/FetchTokenAndPaginateTask.kt index 76c4b3812c59f8b281090a72423d28b8820d743b..96646b42edbf6bafd6595894b1c2becc2d484373 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/FetchTokenAndPaginateTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/FetchTokenAndPaginateTask.kt @@ -48,8 +48,8 @@ internal class DefaultFetchTokenAndPaginateTask @Inject constructor( override suspend fun execute(params: FetchTokenAndPaginateTask.Params): TokenChunkEventPersistor.Result { val filter = filterRepository.getRoomFilter() - val response = executeRequest<EventContextResponse>(globalErrorReceiver) { - apiCall = roomAPI.getContextOfEvent(params.roomId, params.lastKnownEventId, 0, filter) + val response = executeRequest(globalErrorReceiver) { + roomAPI.getContextOfEvent(params.roomId, params.lastKnownEventId, 0, filter) } val fromToken = if (params.direction == PaginationDirection.FORWARDS) { response.end diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/GetContextOfEventTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/GetContextOfEventTask.kt index d02a7bafe9a99dcf893ce56e623015479ef5b2e0..015e55f070365d45453d392ddbf4dc19c6fb6e41 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/GetContextOfEventTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/GetContextOfEventTask.kt @@ -40,9 +40,9 @@ internal class DefaultGetContextOfEventTask @Inject constructor( override suspend fun execute(params: GetContextOfEventTask.Params): TokenChunkEventPersistor.Result { val filter = filterRepository.getRoomFilter() - val response = executeRequest<EventContextResponse>(globalErrorReceiver) { + val response = executeRequest(globalErrorReceiver) { // We are limiting the response to the event with eventId to be sure we don't have any issue with potential merging process. - apiCall = roomAPI.getContextOfEvent(params.roomId, params.eventId, 0, filter) + roomAPI.getContextOfEvent(params.roomId, params.eventId, 0, filter) } return tokenChunkEventPersistor.insertInDb(response, params.roomId, PaginationDirection.FORWARDS) } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/GetEventTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/GetEventTask.kt index b8585b1e747979135b7be0a7aef059b324e7c4d0..cbbc54e90d10b5b3759974bd6cfbea6bc1df026b 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/GetEventTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/GetEventTask.kt @@ -16,28 +16,49 @@ package org.matrix.android.sdk.internal.session.room.timeline +import org.matrix.android.sdk.api.extensions.tryOrNull import org.matrix.android.sdk.api.session.events.model.Event +import org.matrix.android.sdk.internal.crypto.EventDecryptor +import org.matrix.android.sdk.internal.crypto.algorithms.olm.OlmDecryptionResult import org.matrix.android.sdk.internal.network.GlobalErrorReceiver import org.matrix.android.sdk.internal.network.executeRequest import org.matrix.android.sdk.internal.session.room.RoomAPI import org.matrix.android.sdk.internal.task.Task import javax.inject.Inject -// TODO Add parent task - -internal class GetEventTask @Inject constructor( - private val roomAPI: RoomAPI, - private val globalErrorReceiver: GlobalErrorReceiver -) : Task<GetEventTask.Params, Event> { - - internal data class Params( +internal interface GetEventTask : Task<GetEventTask.Params, Event> { + data class Params( val roomId: String, val eventId: String ) +} - override suspend fun execute(params: Params): Event { - return executeRequest(globalErrorReceiver) { - apiCall = roomAPI.getEvent(params.roomId, params.eventId) +internal class DefaultGetEventTask @Inject constructor( + private val roomAPI: RoomAPI, + private val globalErrorReceiver: GlobalErrorReceiver, + private val eventDecryptor: EventDecryptor +) : GetEventTask { + + override suspend fun execute(params: GetEventTask.Params): Event { + val event = executeRequest(globalErrorReceiver) { + roomAPI.getEvent(params.roomId, params.eventId) + } + + // Try to decrypt the Event + if (event.isEncrypted()) { + tryOrNull(message = "Unable to decrypt the event") { + eventDecryptor.decryptEvent(event, "") + } + ?.let { result -> + event.mxDecryptionResult = OlmDecryptionResult( + payload = result.clearEvent, + senderKey = result.senderCurve25519Key, + keysClaimed = result.claimedEd25519Key?.let { mapOf("ed25519" to it) }, + forwardingCurve25519KeyChain = result.forwardingCurve25519KeyChain + ) + } } + + return event } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/LiveTimelineEvent.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/LiveTimelineEvent.kt index 3c0f101e11da4f0a3dd3c892f12a508c31b565fd..eb4900553b89ecec4a81727dce7605b6f3212e2c 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/LiveTimelineEvent.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/LiveTimelineEvent.kt @@ -18,8 +18,9 @@ package org.matrix.android.sdk.internal.session.room.timeline import androidx.lifecycle.LiveData import androidx.lifecycle.MediatorLiveData -import androidx.lifecycle.Transformations import com.zhuinden.monarchy.Monarchy +import io.realm.Realm +import io.realm.RealmQuery import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch @@ -29,66 +30,57 @@ import org.matrix.android.sdk.api.util.Optional import org.matrix.android.sdk.api.util.toOptional import org.matrix.android.sdk.internal.database.mapper.TimelineEventMapper import org.matrix.android.sdk.internal.database.model.TimelineEventEntity +import org.matrix.android.sdk.internal.database.model.TimelineEventEntityFields import org.matrix.android.sdk.internal.database.query.where -import java.util.concurrent.atomic.AtomicBoolean /** * This class takes care of handling case where local echo is replaced by the synced event in the db. */ -internal class LiveTimelineEvent(private val timelineInput: TimelineInput, - private val monarchy: Monarchy, +internal class LiveTimelineEvent(private val monarchy: Monarchy, private val coroutineScope: CoroutineScope, private val timelineEventMapper: TimelineEventMapper, private val roomId: String, private val eventId: String) - : TimelineInput.Listener, - MediatorLiveData<Optional<TimelineEvent>>() { - - private var queryLiveData: LiveData<Optional<TimelineEvent>>? = null - - // If we are listening to local echo, we want to be aware when event is synced - private var shouldObserveSync = AtomicBoolean(LocalEcho.isLocalEchoId(eventId)) + : MediatorLiveData<Optional<TimelineEvent>>() { init { - buildAndObserveQuery(eventId) + buildAndObserveQuery() } + private var initialLiveData: LiveData<List<TimelineEvent>>? = null + // Makes sure it's made on the main thread - private fun buildAndObserveQuery(eventIdToObserve: String) = coroutineScope.launch(Dispatchers.Main) { - queryLiveData?.also { - removeSource(it) - } + private fun buildAndObserveQuery() = coroutineScope.launch(Dispatchers.Main) { val liveData = monarchy.findAllMappedWithChanges( - { TimelineEventEntity.where(it, roomId = roomId, eventId = eventIdToObserve) }, + { TimelineEventEntity.where(it, roomId = roomId, eventId = eventId) }, { timelineEventMapper.map(it) } ) - queryLiveData = Transformations.map(liveData) { events -> - events.firstOrNull().toOptional() - }.also { - addSource(it) { newValue -> value = newValue } + addSource(liveData) { newValue -> + value = newValue.firstOrNull().toOptional() } - } - - override fun onLocalEchoSynced(roomId: String, localEchoEventId: String, syncedEventId: String) { - if (this.roomId == roomId && localEchoEventId == this.eventId) { - timelineInput.listeners.remove(this) - shouldObserveSync.set(false) - // rebuild the query with the new eventId - buildAndObserveQuery(syncedEventId) + initialLiveData = liveData + if (LocalEcho.isLocalEchoId(eventId)) { + observeTimelineEventWithTxId() } } - override fun onActive() { - super.onActive() - if (shouldObserveSync.get()) { - timelineInput.listeners.add(this) + private fun observeTimelineEventWithTxId() { + val liveData = monarchy.findAllMappedWithChanges( + { it.queryTimelineEventWithTxId() }, + { timelineEventMapper.map(it) } + ) + addSource(liveData) { newValue -> + val optionalValue = newValue.firstOrNull().toOptional() + if (optionalValue.hasValue()) { + initialLiveData?.also { removeSource(it) } + value = optionalValue + } } } - override fun onInactive() { - super.onInactive() - if (shouldObserveSync.get()) { - timelineInput.listeners.remove(this) - } + private fun Realm.queryTimelineEventWithTxId(): RealmQuery<TimelineEventEntity> { + return where(TimelineEventEntity::class.java) + .equalTo(TimelineEventEntityFields.ROOM_ID, roomId) + .like(TimelineEventEntityFields.ROOT.UNSIGNED_DATA, """{*"transaction_id":*"$eventId"*}""") } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/PaginationTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/PaginationTask.kt index 1f99893e17142c0373343a569c2bcca8c64d259e..8aeccb66c898ccb2fe777d702fd5c4b1370e7859 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/PaginationTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/PaginationTask.kt @@ -42,9 +42,11 @@ internal class DefaultPaginationTask @Inject constructor( override suspend fun execute(params: PaginationTask.Params): TokenChunkEventPersistor.Result { val filter = filterRepository.getRoomFilter() - val chunk = executeRequest<PaginationResponse>(globalErrorReceiver) { - isRetryable = true - apiCall = roomAPI.getRoomMessagesFrom(params.roomId, params.from, params.direction.value, params.limit, filter) + val chunk = executeRequest( + globalErrorReceiver, + canRetry = true + ) { + roomAPI.getRoomMessagesFrom(params.roomId, params.from, params.direction.value, params.limit, filter) } return tokenChunkEventPersistor.insertInDb(chunk, params.roomId, params.direction) } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/TimelineHiddenReadReceipts.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/TimelineHiddenReadReceipts.kt deleted file mode 100644 index 0ade8ad3b8e6f2a9d55f2f02541dde714b1e7e62..0000000000000000000000000000000000000000 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/TimelineHiddenReadReceipts.kt +++ /dev/null @@ -1,195 +0,0 @@ -/* - * 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.internal.session.room.timeline - -import android.util.SparseArray -import io.realm.OrderedRealmCollectionChangeListener -import io.realm.Realm -import io.realm.RealmQuery -import io.realm.RealmResults -import org.matrix.android.sdk.api.session.room.model.ReadReceipt -import org.matrix.android.sdk.api.session.room.timeline.TimelineSettings -import org.matrix.android.sdk.internal.database.mapper.ReadReceiptsSummaryMapper -import org.matrix.android.sdk.internal.database.model.EventEntityFields -import org.matrix.android.sdk.internal.database.model.ReadReceiptsSummaryEntity -import org.matrix.android.sdk.internal.database.model.ReadReceiptsSummaryEntityFields -import org.matrix.android.sdk.internal.database.model.TimelineEventEntity -import org.matrix.android.sdk.internal.database.model.TimelineEventEntityFields -import org.matrix.android.sdk.internal.database.query.TimelineEventFilter -import org.matrix.android.sdk.internal.database.query.whereInRoom - -/** - * This class is responsible for handling the read receipts for hidden events (check [TimelineSettings] to see filtering). - * When an hidden event has read receipts, we want to transfer these read receipts on the first older displayed event. - * It has to be used in [DefaultTimeline] and we should call the [start] and [dispose] methods to properly handle realm subscription. - */ -internal class TimelineHiddenReadReceipts constructor(private val readReceiptsSummaryMapper: ReadReceiptsSummaryMapper, - private val roomId: String, - private val settings: TimelineSettings) { - - interface Delegate { - fun rebuildEvent(eventId: String, readReceipts: List<ReadReceipt>): Boolean - fun onReadReceiptsUpdated() - } - - private val correctedReadReceiptsEventByIndex = SparseArray<String>() - private val correctedReadReceiptsByEvent = HashMap<String, MutableList<ReadReceipt>>() - - private lateinit var hiddenReadReceipts: RealmResults<ReadReceiptsSummaryEntity> - private lateinit var nonFilteredEvents: RealmResults<TimelineEventEntity> - private lateinit var filteredEvents: RealmResults<TimelineEventEntity> - private lateinit var delegate: Delegate - - private val hiddenReadReceiptsListener = OrderedRealmCollectionChangeListener<RealmResults<ReadReceiptsSummaryEntity>> { collection, changeSet -> - if (!collection.isLoaded || !collection.isValid) { - return@OrderedRealmCollectionChangeListener - } - var hasChange = false - // Deletion here means we don't have any readReceipts for the given hidden events - changeSet.deletions.forEach { - val eventId = correctedReadReceiptsEventByIndex.get(it, "") - val timelineEvent = filteredEvents.where() - .equalTo(TimelineEventEntityFields.EVENT_ID, eventId) - .findFirst() - - // We are rebuilding the corresponding event with only his own RR - val readReceipts = readReceiptsSummaryMapper.map(timelineEvent?.readReceipts) - hasChange = delegate.rebuildEvent(eventId, readReceipts) || hasChange - } - correctedReadReceiptsEventByIndex.clear() - correctedReadReceiptsByEvent.clear() - for (index in 0 until hiddenReadReceipts.size) { - val summary = hiddenReadReceipts[index] ?: continue - val timelineEvent = summary.timelineEvent?.firstOrNull() ?: continue - val isLoaded = nonFilteredEvents.where() - .equalTo(TimelineEventEntityFields.EVENT_ID, timelineEvent.eventId).findFirst() != null - val displayIndex = timelineEvent.displayIndex - - if (isLoaded) { - // Then we are looking for the first displayable event after the hidden one - val firstDisplayedEvent = filteredEvents.where() - .lessThanOrEqualTo(TimelineEventEntityFields.DISPLAY_INDEX, displayIndex) - .findFirst() - - // If we find one, we should - if (firstDisplayedEvent != null) { - correctedReadReceiptsEventByIndex.put(index, firstDisplayedEvent.eventId) - correctedReadReceiptsByEvent - .getOrPut(firstDisplayedEvent.eventId, { - ArrayList(readReceiptsSummaryMapper.map(firstDisplayedEvent.readReceipts)) - }) - .addAll(readReceiptsSummaryMapper.map(summary)) - } - } - } - if (correctedReadReceiptsByEvent.isNotEmpty()) { - correctedReadReceiptsByEvent.forEach { (eventId, correctedReadReceipts) -> - val sortedReadReceipts = correctedReadReceipts.sortedByDescending { - it.originServerTs - } - hasChange = delegate.rebuildEvent(eventId, sortedReadReceipts) || hasChange - } - } - if (hasChange) { - delegate.onReadReceiptsUpdated() - } - } - - /** - * Start the realm query subscription. Has to be called on an HandlerThread - */ - fun start(realm: Realm, - filteredEvents: RealmResults<TimelineEventEntity>, - nonFilteredEvents: RealmResults<TimelineEventEntity>, - delegate: Delegate) { - this.filteredEvents = filteredEvents - this.nonFilteredEvents = nonFilteredEvents - this.delegate = delegate - // We are looking for read receipts set on hidden events. - // We only accept those with a timelineEvent (so coming from pagination/sync). - this.hiddenReadReceipts = ReadReceiptsSummaryEntity.whereInRoom(realm, roomId) - .isNotEmpty(ReadReceiptsSummaryEntityFields.TIMELINE_EVENT.`$`) - .isNotEmpty(ReadReceiptsSummaryEntityFields.READ_RECEIPTS.`$`) - .filterReceiptsWithSettings() - .findAllAsync() - .also { it.addChangeListener(hiddenReadReceiptsListener) } - } - - /** - * Dispose the realm query subscription. Has to be called on an HandlerThread - */ - fun dispose() { - if (this::hiddenReadReceipts.isInitialized) { - this.hiddenReadReceipts.removeAllChangeListeners() - } - } - - /** - * Return the current corrected [ReadReceipt] list for an event, or null - */ - fun correctedReadReceipts(eventId: String?): List<ReadReceipt>? { - return correctedReadReceiptsByEvent[eventId] - } - - /** - * We are looking for receipts related to filtered events. So, it's the opposite of [DefaultTimeline.filterEventsWithSettings] method. - */ - private fun RealmQuery<ReadReceiptsSummaryEntity>.filterReceiptsWithSettings(): RealmQuery<ReadReceiptsSummaryEntity> { - beginGroup() - var needOr = false - if (settings.filters.filterTypes) { - beginGroup() - // Events: A, B, C, D, (E and S1), F, G, (H and S1), I - // Allowed: A, B, C, (E and S1), G, (H and S2) - // Result: D, F, H, I - settings.filters.allowedTypes.forEachIndexed { index, filter -> - if (filter.stateKey == null) { - notEqualTo("${ReadReceiptsSummaryEntityFields.TIMELINE_EVENT.ROOT}.${EventEntityFields.TYPE}", filter.eventType) - } else { - beginGroup() - notEqualTo("${ReadReceiptsSummaryEntityFields.TIMELINE_EVENT.ROOT}.${EventEntityFields.TYPE}", filter.eventType) - or() - notEqualTo("${ReadReceiptsSummaryEntityFields.TIMELINE_EVENT.ROOT}.${EventEntityFields.STATE_KEY}", filter.stateKey) - endGroup() - } - if (index != settings.filters.allowedTypes.size - 1) { - and() - } - } - endGroup() - needOr = true - } - if (settings.filters.filterUseless) { - if (needOr) or() - equalTo("${ReadReceiptsSummaryEntityFields.TIMELINE_EVENT.ROOT}.${EventEntityFields.IS_USELESS}", true) - needOr = true - } - if (settings.filters.filterEdits) { - if (needOr) or() - like("${ReadReceiptsSummaryEntityFields.TIMELINE_EVENT.ROOT}.${EventEntityFields.CONTENT}", TimelineEventFilter.Content.EDIT) - or() - like("${ReadReceiptsSummaryEntityFields.TIMELINE_EVENT.ROOT}.${EventEntityFields.CONTENT}", TimelineEventFilter.Content.RESPONSE) - needOr = true - } - if (settings.filters.filterRedacted) { - if (needOr) or() - like("${ReadReceiptsSummaryEntityFields.TIMELINE_EVENT.ROOT}.${EventEntityFields.UNSIGNED_DATA}", TimelineEventFilter.Unsigned.REDACTED) - } - endGroup() - return this - } -} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/TimelineInput.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/TimelineInput.kt index 8911f265d57367d56fdb1706981b57c9e53ee825..cdc85ea722793334bfb8c1d2b7db38967ee58ae4 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/TimelineInput.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/TimelineInput.kt @@ -35,16 +35,11 @@ internal class TimelineInput @Inject constructor() { listeners.toSet().forEach { it.onNewTimelineEvents(roomId, eventIds) } } - fun onLocalEchoSynced(roomId: String, localEchoEventId: String, syncEventId: String) { - listeners.toSet().forEach { it.onLocalEchoSynced(roomId, localEchoEventId, syncEventId) } - } - val listeners = mutableSetOf<Listener>() internal interface Listener { fun onLocalEchoCreated(roomId: String, timelineEvent: TimelineEvent) = Unit fun onLocalEchoUpdated(roomId: String, eventId: String, sendState: SendState) = Unit fun onNewTimelineEvents(roomId: String, eventIds: List<String>) = Unit - fun onLocalEchoSynced(roomId: String, localEchoEventId: String, syncedEventId: String) = Unit } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/TimelineSendEventWorkCommon.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/TimelineSendEventWorkCommon.kt index bfd4e22cc228cbb15ceed7f7c420d2cc709a4a93..21b508d35a387ceae24da95278ce9e588156c84e 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/TimelineSendEventWorkCommon.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/TimelineSendEventWorkCommon.kt @@ -50,7 +50,7 @@ internal class TimelineSendEventWorkCommon @Inject constructor( .setConstraints(WorkManagerProvider.workConstraints) .startChain(startChain) .setInputData(data) - .setBackoffCriteria(BackoffPolicy.LINEAR, BACKOFF_DELAY, TimeUnit.MILLISECONDS) + .setBackoffCriteria(BackoffPolicy.LINEAR, WorkManagerProvider.BACKOFF_DELAY_MILLIS, TimeUnit.MILLISECONDS) .build() } @@ -60,6 +60,5 @@ internal class TimelineSendEventWorkCommon @Inject constructor( companion object { private const val SEND_WORK = "SEND_WORK" - private const val BACKOFF_DELAY = 10_000L } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/UIEchoManager.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/UIEchoManager.kt index 67d0d90d7705d31d43a3c247aa26d48c19c770c3..4804fbd73143d2ec0b5db1e271d94c717e542bb6 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/UIEchoManager.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/UIEchoManager.kt @@ -70,15 +70,12 @@ internal class UIEchoManager( return existingState != sendState } - // return true if should update - fun onLocalEchoCreated(timelineEvent: TimelineEvent): Boolean { - var postSnapshot = false - + fun onLocalEchoCreated(timelineEvent: TimelineEvent) { // Manage some ui echos (do it before filter because actual event could be filtered out) when (timelineEvent.root.getClearType()) { EventType.REDACTION -> { } - EventType.REACTION -> { + EventType.REACTION -> { val content = timelineEvent.root.content?.toModel<ReactionContent>() if (RelationType.ANNOTATION == content?.relatesTo?.type) { val reaction = content.relatesTo.key @@ -91,21 +88,14 @@ internal class UIEchoManager( reaction = reaction ) ) - postSnapshot = listener.rebuildEvent(relatedEventID) { + listener.rebuildEvent(relatedEventID) { decorateEventWithReactionUiEcho(it) - } || postSnapshot + } } } } - - // do not add events that would have been filtered - if (listOf(timelineEvent).filterEventsWithSettings(settings).isNotEmpty()) { - Timber.v("On local echo created: ${timelineEvent.eventId}") - inMemorySendingEvents.add(0, timelineEvent) - postSnapshot = true - } - - return postSnapshot + Timber.v("On local echo created: ${timelineEvent.eventId}") + inMemorySendingEvents.add(0, timelineEvent) } fun decorateEventWithReactionUiEcho(timelineEvent: TimelineEvent): TimelineEvent? { diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/typing/SendTypingTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/typing/SendTypingTask.kt index 3b56d04872820b454fa234c56e030069dcc26727..0b0df743113285d432cc586afbef88cb5237e0a6 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/typing/SendTypingTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/typing/SendTypingTask.kt @@ -44,8 +44,8 @@ internal class DefaultSendTypingTask @Inject constructor( override suspend fun execute(params: SendTypingTask.Params) { delay(params.delay ?: -1) - executeRequest<Unit>(globalErrorReceiver) { - apiCall = roomAPI.sendTypingState( + executeRequest(globalErrorReceiver) { + roomAPI.sendTypingState( params.roomId, userId, TypingBody(params.isTyping, params.typingTimeoutMillis?.takeIf { params.isTyping }) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/uploads/GetUploadsTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/uploads/GetUploadsTask.kt index b3e4a5aa0597cdcc6353a19b9429ef64530cc392..028c3e91933c1ecbd4bcc555909d9c5be6894995 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/uploads/GetUploadsTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/uploads/GetUploadsTask.kt @@ -37,7 +37,6 @@ import org.matrix.android.sdk.internal.session.filter.FilterFactory import org.matrix.android.sdk.internal.session.room.RoomAPI import org.matrix.android.sdk.internal.session.room.membership.RoomMemberHelper import org.matrix.android.sdk.internal.session.room.timeline.PaginationDirection -import org.matrix.android.sdk.internal.session.room.timeline.PaginationResponse import org.matrix.android.sdk.internal.session.sync.SyncTokenStore import org.matrix.android.sdk.internal.task.Task import javax.inject.Inject @@ -86,8 +85,8 @@ internal class DefaultGetUploadsTask @Inject constructor( val since = params.since ?: tokenStore.getLastToken() ?: throw IllegalStateException("No token available") val filter = FilterFactory.createUploadsFilter(params.numberOfEvents).toJSONString() - val chunk = executeRequest<PaginationResponse>(globalErrorReceiver) { - apiCall = roomAPI.getRoomMessagesFrom(params.roomId, since, PaginationDirection.BACKWARDS.value, params.numberOfEvents, filter) + val chunk = executeRequest(globalErrorReceiver) { + roomAPI.getRoomMessagesFrom(params.roomId, since, PaginationDirection.BACKWARDS.value, params.numberOfEvents, filter) } result = GetUploadsResult( diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/search/SearchAPI.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/search/SearchAPI.kt index 4a74b0a023ed540fc0f23d8039c64ee1ae76acbd..b5099e723874eb70aadbe87c254b6810cdac082b 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/search/SearchAPI.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/search/SearchAPI.kt @@ -19,7 +19,6 @@ package org.matrix.android.sdk.internal.session.search import org.matrix.android.sdk.internal.network.NetworkConstants import org.matrix.android.sdk.internal.session.search.request.SearchRequestBody import org.matrix.android.sdk.internal.session.search.response.SearchResponse -import retrofit2.Call import retrofit2.http.Body import retrofit2.http.POST import retrofit2.http.Query @@ -31,6 +30,6 @@ internal interface SearchAPI { * Ref: https://matrix.org/docs/spec/client_server/r0.6.1#post-matrix-client-r0-search */ @POST(NetworkConstants.URI_API_PREFIX_PATH_R0 + "search") - fun search(@Query("next_batch") nextBatch: String?, - @Body body: SearchRequestBody): Call<SearchResponse> + suspend fun search(@Query("next_batch") nextBatch: String?, + @Body body: SearchRequestBody): SearchResponse } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/search/SearchTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/search/SearchTask.kt index 402602e4d5488382a8c29f375b2ae76b2ea1543b..8de762ee1b6ae540356a2c7749b80e7e639bd20a 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/search/SearchTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/search/SearchTask.kt @@ -51,25 +51,25 @@ internal class DefaultSearchTask @Inject constructor( ) : SearchTask { override suspend fun execute(params: SearchTask.Params): SearchResult { - return executeRequest<SearchResponse>(globalErrorReceiver) { - val searchRequestBody = SearchRequestBody( - searchCategories = SearchRequestCategories( - roomEvents = SearchRequestRoomEvents( - searchTerm = params.searchTerm, - orderBy = if (params.orderByRecent) SearchRequestOrder.RECENT else SearchRequestOrder.RANK, - filter = SearchRequestFilter( - limit = params.limit, - rooms = listOf(params.roomId) - ), - eventContext = SearchRequestEventContext( - beforeLimit = params.beforeLimit, - afterLimit = params.afterLimit, - includeProfile = params.includeProfile - ) - ) - ) - ) - apiCall = searchAPI.search(params.nextBatch, searchRequestBody) + val searchRequestBody = SearchRequestBody( + searchCategories = SearchRequestCategories( + roomEvents = SearchRequestRoomEvents( + searchTerm = params.searchTerm, + orderBy = if (params.orderByRecent) SearchRequestOrder.RECENT else SearchRequestOrder.RANK, + filter = SearchRequestFilter( + limit = params.limit, + rooms = listOf(params.roomId) + ), + eventContext = SearchRequestEventContext( + beforeLimit = params.beforeLimit, + afterLimit = params.afterLimit, + includeProfile = params.includeProfile + ) + ) + ) + ) + return executeRequest(globalErrorReceiver) { + searchAPI.search(params.nextBatch, searchRequestBody) }.toDomain() } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/signout/SignInAgainTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/signout/SignInAgainTask.kt index 2c3cd5d2703fc9423c6073bf08a1604f57b51475..563e85aefc5edbd2e23664a4103a057731314620 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/signout/SignInAgainTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/signout/SignInAgainTask.kt @@ -16,7 +16,6 @@ package org.matrix.android.sdk.internal.session.signout -import org.matrix.android.sdk.api.auth.data.Credentials import org.matrix.android.sdk.api.auth.data.SessionParams import org.matrix.android.sdk.internal.auth.SessionParamsStore import org.matrix.android.sdk.internal.auth.data.PasswordLoginParams @@ -39,8 +38,8 @@ internal class DefaultSignInAgainTask @Inject constructor( ) : SignInAgainTask { override suspend fun execute(params: SignInAgainTask.Params) { - val newCredentials = executeRequest<Credentials>(globalErrorReceiver) { - apiCall = signOutAPI.loginAgain( + val newCredentials = executeRequest(globalErrorReceiver) { + signOutAPI.loginAgain( PasswordLoginParams.userIdentifier( // Reuse the same userId sessionParams.userId, diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/signout/SignOutAPI.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/signout/SignOutAPI.kt index 4c92938b7731d9059a1e6b99e6f99ab47eb5a671..a56362e587e26dfa79ad05c67a17adacf86ed2f9 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/signout/SignOutAPI.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/signout/SignOutAPI.kt @@ -19,7 +19,6 @@ package org.matrix.android.sdk.internal.session.signout import org.matrix.android.sdk.api.auth.data.Credentials import org.matrix.android.sdk.internal.auth.data.PasswordLoginParams import org.matrix.android.sdk.internal.network.NetworkConstants -import retrofit2.Call import retrofit2.http.Body import retrofit2.http.Headers import retrofit2.http.POST @@ -35,11 +34,11 @@ internal interface SignOutAPI { */ @Headers("CONNECT_TIMEOUT:60000", "READ_TIMEOUT:60000", "WRITE_TIMEOUT:60000") @POST(NetworkConstants.URI_API_PREFIX_PATH_R0 + "login") - fun loginAgain(@Body loginParams: PasswordLoginParams): Call<Credentials> + suspend fun loginAgain(@Body loginParams: PasswordLoginParams): Credentials /** * Invalidate the access token, so that it can no longer be used for authorization. */ @POST(NetworkConstants.URI_API_PREFIX_PATH_R0 + "logout") - fun signOut(): Call<Unit> + suspend fun signOut() } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/signout/SignOutTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/signout/SignOutTask.kt index 0cb8704782fa329d354de51da3952bd93553196e..9c25eccb3a50b468450a452dd4c62a50e44c9451 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/signout/SignOutTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/signout/SignOutTask.kt @@ -45,8 +45,8 @@ internal class DefaultSignOutTask @Inject constructor( if (params.signOutFromHomeserver) { Timber.d("SignOut: send request...") try { - executeRequest<Unit>(globalErrorReceiver) { - apiCall = signOutAPI.signOut() + executeRequest(globalErrorReceiver) { + signOutAPI.signOut() } } catch (throwable: Throwable) { // Maybe due to https://github.com/matrix-org/synapse/issues/5756 diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/InitialSyncStatusRepository.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/InitialSyncStatusRepository.kt index 4b82ecc3e5878e450d47f2d7c63ea929ee287f63..cf67bbd805d92b7e18c50ddf30bca13901cce6e7 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/InitialSyncStatusRepository.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/InitialSyncStatusRepository.kt @@ -65,7 +65,7 @@ internal class FileInitialSyncStatusRepository(directory: File) : InitialSyncSta val state = cache?.step ?: InitialSyncStatus.STEP_INIT return if (state >= InitialSyncStatus.STEP_DOWNLOADED && System.currentTimeMillis() > (cache?.downloadedDate ?: 0) + INIT_SYNC_FILE_LIFETIME) { - Timber.v("INIT_SYNC downloaded file is outdated, download it again") + Timber.d("INIT_SYNC downloaded file is outdated, download it again") // The downloaded file is outdated setStep(InitialSyncStatus.STEP_INIT) InitialSyncStatus.STEP_INIT diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/InitialSyncStrategy.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/InitialSyncStrategy.kt index 297cc213ed704f6c76058d05d34901e40a074d32..7d93e30191cbf7ea15907a6e3765e1580f8d7904 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/InitialSyncStrategy.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/InitialSyncStrategy.kt @@ -42,9 +42,9 @@ sealed class InitialSyncStrategy { val minSizeToSplit: Long = 1024 * 1024, /** * Limit per room to reach to decide to store a join room ephemeral Events into a file - * Empiric value: 6 kilobytes + * Empiric value: 1 kilobytes */ - val minSizeToStoreInFile: Long = 6 * 1024, + val minSizeToStoreInFile: Long = 1024, /** * Max number of rooms to insert at a time in database (to avoid too much RAM usage) */ diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/ReadReceiptHandler.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/ReadReceiptHandler.kt index a3c5891f68a678cd3f74350027de5d02d668d5cb..e5d9217db71da4c0a25dda37a5bc2494688b9d51 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/ReadReceiptHandler.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/ReadReceiptHandler.kt @@ -16,12 +16,13 @@ package org.matrix.android.sdk.internal.session.sync +import io.realm.Realm +import org.matrix.android.sdk.api.session.events.model.EventType import org.matrix.android.sdk.internal.database.model.ReadReceiptEntity import org.matrix.android.sdk.internal.database.model.ReadReceiptsSummaryEntity import org.matrix.android.sdk.internal.database.query.createUnmanaged import org.matrix.android.sdk.internal.database.query.getOrCreate import org.matrix.android.sdk.internal.database.query.where -import io.realm.Realm import timber.log.Timber import javax.inject.Inject @@ -35,7 +36,9 @@ typealias ReadReceiptContent = Map<String, Map<String, Map<String, Map<String, D private const val READ_KEY = "m.read" private const val TIMESTAMP_KEY = "ts" -internal class ReadReceiptHandler @Inject constructor() { +internal class ReadReceiptHandler @Inject constructor( + private val roomSyncEphemeralTemporaryStore: RoomSyncEphemeralTemporaryStore +) { companion object { @@ -52,22 +55,29 @@ internal class ReadReceiptHandler @Inject constructor() { } } - fun handle(realm: Realm, roomId: String, content: ReadReceiptContent?, isInitialSync: Boolean) { - if (content == null) { - return - } + fun handle(realm: Realm, + roomId: String, + content: ReadReceiptContent?, + isInitialSync: Boolean, + aggregator: SyncResponsePostTreatmentAggregator?) { + content ?: return + try { - handleReadReceiptContent(realm, roomId, content, isInitialSync) + handleReadReceiptContent(realm, roomId, content, isInitialSync, aggregator) } catch (exception: Exception) { Timber.e("Fail to handle read receipt for room $roomId") } } - private fun handleReadReceiptContent(realm: Realm, roomId: String, content: ReadReceiptContent, isInitialSync: Boolean) { + private fun handleReadReceiptContent(realm: Realm, + roomId: String, + content: ReadReceiptContent, + isInitialSync: Boolean, + aggregator: SyncResponsePostTreatmentAggregator?) { if (isInitialSync) { initialSyncStrategy(realm, roomId, content) } else { - incrementalSyncStrategy(realm, roomId, content) + incrementalSyncStrategy(realm, roomId, content, aggregator) } } @@ -87,7 +97,21 @@ internal class ReadReceiptHandler @Inject constructor() { realm.insertOrUpdate(readReceiptSummaries) } - private fun incrementalSyncStrategy(realm: Realm, roomId: String, content: ReadReceiptContent) { + private fun incrementalSyncStrategy(realm: Realm, + roomId: String, + content: ReadReceiptContent, + aggregator: SyncResponsePostTreatmentAggregator?) { + // First check if we have data from init sync to handle + getContentFromInitSync(roomId)?.let { + Timber.w("INIT_SYNC Insert during incremental sync RR for room $roomId") + doIncrementalSyncStrategy(realm, roomId, it) + aggregator?.ephemeralFilesToDelete?.add(roomId) + } + + doIncrementalSyncStrategy(realm, roomId, content) + } + + private fun doIncrementalSyncStrategy(realm: Realm, roomId: String, content: ReadReceiptContent) { for ((eventId, receiptDict) in content) { val userIdsDict = receiptDict[READ_KEY] ?: continue val readReceiptsSummary = ReadReceiptsSummaryEntity.where(realm, eventId).findFirst() @@ -110,4 +134,27 @@ internal class ReadReceiptHandler @Inject constructor() { } } } + + fun getContentFromInitSync(roomId: String): ReadReceiptContent? { + val dataFromFile = roomSyncEphemeralTemporaryStore.read(roomId) + + dataFromFile ?: return null + + @Suppress("UNCHECKED_CAST") + val content = dataFromFile + .events + .firstOrNull { it.type == EventType.RECEIPT } + ?.content as? ReadReceiptContent + + if (content == null) { + // We can delete the file now + roomSyncEphemeralTemporaryStore.delete(roomId) + } + + return content + } + + fun onContentFromInitSyncHandled(roomId: String) { + roomSyncEphemeralTemporaryStore.delete(roomId) + } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/RoomSyncEphemeralTemporaryStore.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/RoomSyncEphemeralTemporaryStore.kt new file mode 100644 index 0000000000000000000000000000000000000000..c6ff71cfcfdcc2e979dcf16d4e4fda4ff8694834 --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/RoomSyncEphemeralTemporaryStore.kt @@ -0,0 +1,79 @@ +/* + * Copyright (c) 2021 The Matrix.org Foundation C.I.C. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.matrix.android.sdk.internal.session.sync + +import com.squareup.moshi.JsonReader +import com.squareup.moshi.Moshi +import okio.buffer +import okio.source +import org.matrix.android.sdk.internal.di.SessionFilesDirectory +import org.matrix.android.sdk.internal.session.sync.model.RoomSyncEphemeral +import org.matrix.android.sdk.internal.util.md5 +import timber.log.Timber +import java.io.File +import javax.inject.Inject + +internal interface RoomSyncEphemeralTemporaryStore { + fun write(roomId: String, roomSyncEphemeralJson: String) + fun read(roomId: String): RoomSyncEphemeral? + fun reset() + fun delete(roomId: String) +} + +internal class RoomSyncEphemeralTemporaryStoreFile @Inject constructor( + @SessionFilesDirectory fileDirectory: File, + moshi: Moshi +) : RoomSyncEphemeralTemporaryStore { + + private val workingDir = File(fileDirectory, "rr") + .also { it.mkdirs() } + + private val roomSyncEphemeralAdapter = moshi.adapter(RoomSyncEphemeral::class.java) + + /** + * Write RoomSyncEphemeral to a file + */ + override fun write(roomId: String, roomSyncEphemeralJson: String) { + Timber.w("INIT_SYNC Store ephemeral events for room $roomId") + getFile(roomId).writeText(roomSyncEphemeralJson) + } + + /** + * Read RoomSyncEphemeral from a file, or null if there is no file to read + */ + override fun read(roomId: String): RoomSyncEphemeral? { + return getFile(roomId) + .takeIf { it.exists() } + ?.inputStream() + ?.use { pos -> + roomSyncEphemeralAdapter.fromJson(JsonReader.of(pos.source().buffer())) + } + } + + override fun delete(roomId: String) { + getFile(roomId).delete() + } + + override fun reset() { + workingDir.deleteRecursively() + workingDir.mkdirs() + } + + private fun getFile(roomId: String): File { + return File(workingDir, "${roomId.md5()}.json") + } +} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/RoomSyncHandler.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/RoomSyncHandler.kt index 336a83eaadd1d5a26b9f746f599c977c99a36b29..2bb606e921f7536400e4f03820335c4f08a6a82a 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/RoomSyncHandler.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/RoomSyncHandler.kt @@ -49,6 +49,7 @@ import org.matrix.android.sdk.internal.database.query.where import org.matrix.android.sdk.internal.di.MoshiProvider import org.matrix.android.sdk.internal.di.UserId import org.matrix.android.sdk.internal.extensions.clearWith +import org.matrix.android.sdk.internal.session.events.getFixedRoomMemberContent import org.matrix.android.sdk.internal.session.initsync.ProgressReporter import org.matrix.android.sdk.internal.session.initsync.mapWithProgress import org.matrix.android.sdk.internal.session.initsync.reportSubtask @@ -60,12 +61,13 @@ import org.matrix.android.sdk.internal.session.room.timeline.PaginationDirection import org.matrix.android.sdk.internal.session.room.timeline.TimelineInput import org.matrix.android.sdk.internal.session.room.typing.TypingEventContent import org.matrix.android.sdk.internal.session.sync.model.InvitedRoomSync +import org.matrix.android.sdk.internal.session.sync.model.LazyRoomSyncEphemeral import org.matrix.android.sdk.internal.session.sync.model.RoomSync import org.matrix.android.sdk.internal.session.sync.model.RoomSyncAccountData import org.matrix.android.sdk.internal.session.sync.model.RoomsSyncResponse +import org.matrix.android.sdk.internal.util.computeBestChunkSize import timber.log.Timber import javax.inject.Inject -import kotlin.math.ceil internal class RoomSyncHandler @Inject constructor(private val readReceiptHandler: ReadReceiptHandler, private val roomSummaryUpdater: RoomSummaryUpdater, @@ -87,29 +89,21 @@ internal class RoomSyncHandler @Inject constructor(private val readReceiptHandle fun handle(realm: Realm, roomsSyncResponse: RoomsSyncResponse, isInitialSync: Boolean, + aggregator: SyncResponsePostTreatmentAggregator, reporter: ProgressReporter? = null) { Timber.v("Execute transaction from $this") - handleRoomSync(realm, HandlingStrategy.JOINED(roomsSyncResponse.join), isInitialSync, reporter) - handleRoomSync(realm, HandlingStrategy.INVITED(roomsSyncResponse.invite), isInitialSync, reporter) - handleRoomSync(realm, HandlingStrategy.LEFT(roomsSyncResponse.leave), isInitialSync, reporter) - } - - fun handleInitSyncEphemeral(realm: Realm, - roomsSyncResponse: RoomsSyncResponse) { - roomsSyncResponse.join.forEach { roomSync -> - val ephemeralResult = roomSync.value.ephemeral - ?.roomSyncEphemeral - ?.events - ?.takeIf { it.isNotEmpty() } - ?.let { events -> handleEphemeral(realm, roomSync.key, events, true) } - - roomTypingUsersHandler.handle(realm, roomSync.key, ephemeralResult) - } + handleRoomSync(realm, HandlingStrategy.JOINED(roomsSyncResponse.join), isInitialSync, aggregator, reporter) + handleRoomSync(realm, HandlingStrategy.INVITED(roomsSyncResponse.invite), isInitialSync, aggregator, reporter) + handleRoomSync(realm, HandlingStrategy.LEFT(roomsSyncResponse.leave), isInitialSync, aggregator, reporter) } // PRIVATE METHODS ***************************************************************************** - private fun handleRoomSync(realm: Realm, handlingStrategy: HandlingStrategy, isInitialSync: Boolean, reporter: ProgressReporter?) { + private fun handleRoomSync(realm: Realm, + handlingStrategy: HandlingStrategy, + isInitialSync: Boolean, + aggregator: SyncResponsePostTreatmentAggregator, + reporter: ProgressReporter?) { val insertType = if (isInitialSync) { EventInsertType.INITIAL_SYNC } else { @@ -119,12 +113,12 @@ internal class RoomSyncHandler @Inject constructor(private val readReceiptHandle val rooms = when (handlingStrategy) { is HandlingStrategy.JOINED -> { if (isInitialSync && initialSyncStrategy is InitialSyncStrategy.Optimized) { - insertJoinRoomsFromInitSync(realm, handlingStrategy, syncLocalTimeStampMillis, reporter) + insertJoinRoomsFromInitSync(realm, handlingStrategy, syncLocalTimeStampMillis, aggregator, reporter) // Rooms are already inserted, return an empty list emptyList() } else { handlingStrategy.data.mapWithProgress(reporter, InitSyncStep.ImportingAccountJoinedRooms, 0.6f) { - handleJoinedRoom(realm, it.key, it.value, true, insertType, syncLocalTimeStampMillis) + handleJoinedRoom(realm, it.key, it.value, insertType, syncLocalTimeStampMillis, aggregator) } } } @@ -145,29 +139,30 @@ internal class RoomSyncHandler @Inject constructor(private val readReceiptHandle private fun insertJoinRoomsFromInitSync(realm: Realm, handlingStrategy: HandlingStrategy.JOINED, syncLocalTimeStampMillis: Long, + aggregator: SyncResponsePostTreatmentAggregator, reporter: ProgressReporter?) { - val maxSize = (initialSyncStrategy as? InitialSyncStrategy.Optimized)?.maxRoomsToInsert ?: Int.MAX_VALUE - val listSize = handlingStrategy.data.keys.size - val numberOfChunks = ceil(listSize / maxSize.toDouble()).toInt() + val bestChunkSize = computeBestChunkSize( + listSize = handlingStrategy.data.keys.size, + limit = (initialSyncStrategy as? InitialSyncStrategy.Optimized)?.maxRoomsToInsert ?: Int.MAX_VALUE + ) - if (numberOfChunks > 1) { - reportSubtask(reporter, InitSyncStep.ImportingAccountJoinedRooms, numberOfChunks, 0.6f) { - val chunkSize = listSize / numberOfChunks - Timber.v("INIT_SYNC $listSize rooms to insert, split into $numberOfChunks sublists of $chunkSize items") + if (bestChunkSize.shouldChunk()) { + reportSubtask(reporter, InitSyncStep.ImportingAccountJoinedRooms, bestChunkSize.numberOfChunks, 0.6f) { + Timber.d("INIT_SYNC ${handlingStrategy.data.keys.size} rooms to insert, split with $bestChunkSize") // I cannot find a better way to chunk a map, so chunk the keys and then create new maps handlingStrategy.data.keys - .chunked(chunkSize) + .chunked(bestChunkSize.chunkSize) .forEachIndexed { index, roomIds -> val roomEntities = roomIds - .also { Timber.v("INIT_SYNC insert ${roomIds.size} rooms") } + .also { Timber.d("INIT_SYNC insert ${roomIds.size} rooms") } .map { handleJoinedRoom( realm = realm, roomId = it, roomSync = handlingStrategy.data[it] ?: error("Should not happen"), - handleEphemeralEvents = false, insertType = EventInsertType.INITIAL_SYNC, - syncLocalTimestampMillis = syncLocalTimeStampMillis + syncLocalTimestampMillis = syncLocalTimeStampMillis, + aggregator ) } realm.insertOrUpdate(roomEntities) @@ -177,7 +172,7 @@ internal class RoomSyncHandler @Inject constructor(private val readReceiptHandle } else { // No need to split val rooms = handlingStrategy.data.mapWithProgress(reporter, InitSyncStep.ImportingAccountJoinedRooms, 0.6f) { - handleJoinedRoom(realm, it.key, it.value, false, EventInsertType.INITIAL_SYNC, syncLocalTimeStampMillis) + handleJoinedRoom(realm, it.key, it.value, EventInsertType.INITIAL_SYNC, syncLocalTimeStampMillis, aggregator) } realm.insertOrUpdate(rooms) } @@ -186,17 +181,16 @@ internal class RoomSyncHandler @Inject constructor(private val readReceiptHandle private fun handleJoinedRoom(realm: Realm, roomId: String, roomSync: RoomSync, - handleEphemeralEvents: Boolean, insertType: EventInsertType, - syncLocalTimestampMillis: Long): RoomEntity { + syncLocalTimestampMillis: Long, + aggregator: SyncResponsePostTreatmentAggregator): RoomEntity { Timber.v("Handle join sync for room $roomId") - var ephemeralResult: EphemeralResult? = null - if (handleEphemeralEvents) { - ephemeralResult = roomSync.ephemeral?.roomSyncEphemeral?.events - ?.takeIf { it.isNotEmpty() } - ?.let { handleEphemeral(realm, roomId, it, insertType == EventInsertType.INITIAL_SYNC) } - } + val ephemeralResult = (roomSync.ephemeral as? LazyRoomSyncEphemeral.Parsed) + ?._roomSyncEphemeral + ?.events + ?.takeIf { it.isNotEmpty() } + ?.let { handleEphemeral(realm, roomId, it, insertType == EventInsertType.INITIAL_SYNC, aggregator) } if (roomSync.accountData?.events?.isNotEmpty() == true) { handleRoomAccountDataEvents(realm, roomId, roomSync.accountData) @@ -400,7 +394,6 @@ internal class RoomSyncHandler @Inject constructor(private val readReceiptHandle event.mxDecryptionResult = adapter.fromJson(json) } } - timelineInput.onLocalEchoSynced(roomId, it, event.eventId) // Finally delete the local echo sendingEventEntity.deleteOnCascade(true) } else { @@ -415,6 +408,7 @@ internal class RoomSyncHandler @Inject constructor(private val readReceiptHandle private fun decryptIfNeeded(event: Event, roomId: String) { try { + // Event from sync does not have roomId, so add it to the event first val result = cryptoService.decryptEvent(event.copy(roomId = roomId), "") event.mxDecryptionResult = OlmDecryptionResult( payload = result.clearEvent, @@ -437,14 +431,15 @@ internal class RoomSyncHandler @Inject constructor(private val readReceiptHandle private fun handleEphemeral(realm: Realm, roomId: String, ephemeralEvents: List<Event>, - isInitialSync: Boolean): EphemeralResult { + isInitialSync: Boolean, + aggregator: SyncResponsePostTreatmentAggregator): EphemeralResult { var result = EphemeralResult() for (event in ephemeralEvents) { when (event.type) { EventType.RECEIPT -> { @Suppress("UNCHECKED_CAST") (event.content as? ReadReceiptContent)?.let { readReceiptContent -> - readReceiptHandler.handle(realm, roomId, readReceiptContent, isInitialSync) + readReceiptHandler.handle(realm, roomId, readReceiptContent, isInitialSync, aggregator) } } EventType.TYPING -> { @@ -471,18 +466,4 @@ internal class RoomSyncHandler @Inject constructor(private val readReceiptHandle } } } - - private fun Event.getFixedRoomMemberContent(): RoomMemberContent? { - val content = content.toModel<RoomMemberContent>() - // if user is leaving, we should grab his last name and avatar from prevContent - return if (content?.membership?.isLeft() == true) { - val prevContent = resolvedPrevContent().toModel<RoomMemberContent>() - content.copy( - displayName = prevContent?.displayName, - avatarUrl = prevContent?.avatarUrl - ) - } else { - content - } - } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/RoomTagHandler.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/RoomTagHandler.kt index f9ae41bc9406ea81d5195bfa8c45d9ec0d3cb190..add5d841d1b9682292a52e552165c868cdaa84eb 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/RoomTagHandler.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/RoomTagHandler.kt @@ -19,8 +19,8 @@ package org.matrix.android.sdk.internal.session.sync import org.matrix.android.sdk.api.session.room.model.tag.RoomTagContent import org.matrix.android.sdk.internal.database.model.RoomSummaryEntity import org.matrix.android.sdk.internal.database.model.RoomTagEntity -import org.matrix.android.sdk.internal.database.query.where import io.realm.Realm +import org.matrix.android.sdk.internal.database.query.getOrCreate import javax.inject.Inject internal class RoomTagHandler @Inject constructor() { @@ -31,12 +31,8 @@ internal class RoomTagHandler @Inject constructor() { } val tags = content.tags.entries.map { (tagName, params) -> RoomTagEntity(tagName, params["order"] as? Double) + Pair(tagName, params["order"] as? Double) } - val roomSummaryEntity = RoomSummaryEntity.where(realm, roomId).findFirst() - ?: RoomSummaryEntity(roomId) - - roomSummaryEntity.tags.clear() - roomSummaryEntity.tags.addAll(tags) - realm.insertOrUpdate(roomSummaryEntity) + RoomSummaryEntity.getOrCreate(realm, roomId).updateTags(tags) } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/RoomTypingUsersHandler.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/RoomTypingUsersHandler.kt index f4f3e6ce43714f8b113fe7f1e470d4f74925ad13..b7851031adcf6ba1f14ca328c328af7c5c4d396d 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/RoomTypingUsersHandler.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/RoomTypingUsersHandler.kt @@ -26,6 +26,7 @@ import javax.inject.Inject internal class RoomTypingUsersHandler @Inject constructor(@UserId private val userId: String, private val typingUsersTracker: DefaultTypingUsersTracker) { + // TODO This could be handled outside of the Realm transaction. Use the new aggregator? fun handle(realm: Realm, roomId: String, ephemeralResult: RoomSyncHandler.EphemeralResult?) { val roomMemberHelper = RoomMemberHelper(realm, roomId) val typingIds = ephemeralResult?.typingUserIds?.filter { it != userId }.orEmpty() diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/SyncAPI.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/SyncAPI.kt index 8e3523bc5767ab44d773d1463770b8bb317a0e95..2616803463a6c6707bda2a772951349dac1c6783 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/SyncAPI.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/SyncAPI.kt @@ -31,11 +31,11 @@ internal interface SyncAPI { * Set all the timeouts to 1 minute by default */ @GET(NetworkConstants.URI_API_PREFIX_PATH_R0 + "sync") - fun sync(@QueryMap params: Map<String, String>, - @Header(TimeOutInterceptor.CONNECT_TIMEOUT) connectTimeOut: Long = TimeOutInterceptor.DEFAULT_LONG_TIMEOUT, - @Header(TimeOutInterceptor.READ_TIMEOUT) readTimeOut: Long = TimeOutInterceptor.DEFAULT_LONG_TIMEOUT, - @Header(TimeOutInterceptor.WRITE_TIMEOUT) writeTimeOut: Long = TimeOutInterceptor.DEFAULT_LONG_TIMEOUT - ): Call<SyncResponse> + suspend fun sync(@QueryMap params: Map<String, String>, + @Header(TimeOutInterceptor.CONNECT_TIMEOUT) connectTimeOut: Long = TimeOutInterceptor.DEFAULT_LONG_TIMEOUT, + @Header(TimeOutInterceptor.READ_TIMEOUT) readTimeOut: Long = TimeOutInterceptor.DEFAULT_LONG_TIMEOUT, + @Header(TimeOutInterceptor.WRITE_TIMEOUT) writeTimeOut: Long = TimeOutInterceptor.DEFAULT_LONG_TIMEOUT + ): SyncResponse /** * Set all the timeouts to 1 minute by default diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/SyncModule.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/SyncModule.kt index 010c029c97f14ae7951bb6893f69c640194f01c3..4b31dc4d9b00b7d9b488c0abc50604ca1c98364f 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/SyncModule.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/SyncModule.kt @@ -37,4 +37,7 @@ internal abstract class SyncModule { @Binds abstract fun bindSyncTask(task: DefaultSyncTask): SyncTask + + @Binds + abstract fun bindRoomSyncEphemeralTemporaryStore(store: RoomSyncEphemeralTemporaryStoreFile): RoomSyncEphemeralTemporaryStore } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/SyncResponseHandler.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/SyncResponseHandler.kt index d17a672485d7b4ceb077bc8f3bfe59bd4c8647eb..8e243c3443d9deb6b09f2479f230bc215e3b9656 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/SyncResponseHandler.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/SyncResponseHandler.kt @@ -41,17 +41,19 @@ import kotlin.system.measureTimeMillis private const val GET_GROUP_DATA_WORKER = "GET_GROUP_DATA_WORKER" -internal class SyncResponseHandler @Inject constructor(@SessionDatabase private val monarchy: Monarchy, - @SessionId private val sessionId: String, - private val workManagerProvider: WorkManagerProvider, - private val roomSyncHandler: RoomSyncHandler, - private val userAccountDataSyncHandler: UserAccountDataSyncHandler, - private val groupSyncHandler: GroupSyncHandler, - private val cryptoSyncHandler: CryptoSyncHandler, - private val cryptoService: DefaultCryptoService, - private val tokenStore: SyncTokenStore, - private val processEventForPushTask: ProcessEventForPushTask, - private val pushRuleService: PushRuleService) { +internal class SyncResponseHandler @Inject constructor( + @SessionDatabase private val monarchy: Monarchy, + @SessionId private val sessionId: String, + private val workManagerProvider: WorkManagerProvider, + private val roomSyncHandler: RoomSyncHandler, + private val userAccountDataSyncHandler: UserAccountDataSyncHandler, + private val groupSyncHandler: GroupSyncHandler, + private val cryptoSyncHandler: CryptoSyncHandler, + private val aggregatorHandler: SyncResponsePostTreatmentAggregatorHandler, + private val cryptoService: DefaultCryptoService, + private val tokenStore: SyncTokenStore, + private val processEventForPushTask: ProcessEventForPushTask, + private val pushRuleService: PushRuleService) { suspend fun handleResponse(syncResponse: SyncResponse, fromToken: String?, @@ -81,13 +83,14 @@ internal class SyncResponseHandler @Inject constructor(@SessionDatabase private }.also { Timber.v("Finish handling toDevice in $it ms") } + val aggregator = SyncResponsePostTreatmentAggregator() // Start one big transaction monarchy.awaitTransaction { realm -> measureTimeMillis { Timber.v("Handle rooms") reportSubtask(reporter, InitSyncStep.ImportingAccountRoom, 1, 0.7f) { if (syncResponse.rooms != null) { - roomSyncHandler.handle(realm, syncResponse.rooms, isInitialSync, reporter) + roomSyncHandler.handle(realm, syncResponse.rooms, isInitialSync, aggregator, reporter) } } }.also { @@ -115,7 +118,10 @@ internal class SyncResponseHandler @Inject constructor(@SessionDatabase private } tokenStore.saveToken(realm, syncResponse.nextBatch) } + // Everything else we need to do outside the transaction + aggregatorHandler.handle(aggregator) + syncResponse.rooms?.let { checkPushRules(it, isInitialSync) userAccountDataSyncHandler.synchronizeWithServerIfNeeded(it.invite) @@ -128,15 +134,6 @@ internal class SyncResponseHandler @Inject constructor(@SessionDatabase private cryptoSyncHandler.onSyncCompleted(syncResponse) } - suspend fun handleInitSyncSecondTransaction(syncResponse: SyncResponse) { - // Start another transaction to handle the ephemeral events - monarchy.awaitTransaction { realm -> - if (syncResponse.rooms != null) { - roomSyncHandler.handleInitSyncEphemeral(realm, syncResponse.rooms) - } - } - } - /** * At the moment we don't get any group data through the sync, so we poll where every hour. * You can also force to refetch group data using [Group] API. diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/SyncResponsePostTreatmentAggregator.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/SyncResponsePostTreatmentAggregator.kt new file mode 100644 index 0000000000000000000000000000000000000000..ea10a32f3eef33350edc5450b858d28d82d0b656 --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/SyncResponsePostTreatmentAggregator.kt @@ -0,0 +1,22 @@ +/* + * Copyright (c) 2021 The Matrix.org Foundation C.I.C. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.matrix.android.sdk.internal.session.sync + +internal class SyncResponsePostTreatmentAggregator { + // List of RoomId + val ephemeralFilesToDelete = mutableListOf<String>() +} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/SyncResponsePostTreatmentAggregatorHandler.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/SyncResponsePostTreatmentAggregatorHandler.kt new file mode 100644 index 0000000000000000000000000000000000000000..12b77c706b3759ffd993554a296fddd326d7102d --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/SyncResponsePostTreatmentAggregatorHandler.kt @@ -0,0 +1,33 @@ +/* + * Copyright (c) 2021 The Matrix.org Foundation C.I.C. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.matrix.android.sdk.internal.session.sync + +import javax.inject.Inject + +internal class SyncResponsePostTreatmentAggregatorHandler @Inject constructor( + private val ephemeralTemporaryStore: RoomSyncEphemeralTemporaryStore +) { + fun handle(synResHaResponsePostTreatmentAggregator: SyncResponsePostTreatmentAggregator) { + cleanupEphemeralFiles(synResHaResponsePostTreatmentAggregator.ephemeralFilesToDelete) + } + + private fun cleanupEphemeralFiles(ephemeralFilesToDelete: List<String>) { + ephemeralFilesToDelete.forEach { + ephemeralTemporaryStore.delete(it) + } + } +} 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 00060a33b11db2fee0dbcf34228b59520e651147..83a2ffc4466cd7a90e4ef890413562f8de4c122c 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 @@ -29,7 +29,6 @@ import org.matrix.android.sdk.internal.session.homeserver.GetHomeServerCapabilit import org.matrix.android.sdk.internal.session.initsync.DefaultInitialSyncProgressService import org.matrix.android.sdk.internal.session.initsync.reportSubtask import org.matrix.android.sdk.internal.session.sync.model.LazyRoomSyncEphemeral -import org.matrix.android.sdk.internal.session.sync.model.SyncResponse import org.matrix.android.sdk.internal.session.sync.parsing.InitialSyncResponseParser import org.matrix.android.sdk.internal.session.user.UserStore import org.matrix.android.sdk.internal.task.Task @@ -62,7 +61,8 @@ internal class DefaultSyncTask @Inject constructor( private val globalErrorReceiver: GlobalErrorReceiver, @SessionFilesDirectory private val fileDirectory: File, - private val syncResponseParser: InitialSyncResponseParser + private val syncResponseParser: InitialSyncResponseParser, + private val roomSyncEphemeralTemporaryStore: RoomSyncEphemeralTemporaryStore ) : SyncTask { private val workingDir = File(fileDirectory, "is") @@ -100,19 +100,22 @@ internal class DefaultSyncTask @Inject constructor( val readTimeOut = (params.timeout + TIMEOUT_MARGIN).coerceAtLeast(TimeOutInterceptor.DEFAULT_LONG_TIMEOUT) if (isInitialSync) { - Timber.v("INIT_SYNC with filter: ${requestParams["filter"]}") + Timber.d("INIT_SYNC with filter: ${requestParams["filter"]}") val initSyncStrategy = initialSyncStrategy - var syncResp: SyncResponse? = null logDuration("INIT_SYNC strategy: $initSyncStrategy") { if (initSyncStrategy is InitialSyncStrategy.Optimized) { + roomSyncEphemeralTemporaryStore.reset() + workingDir.mkdirs() val file = downloadInitSyncResponse(requestParams) - syncResp = reportSubtask(initialSyncProgressService, InitSyncStep.ImportingAccount, 1, 0.7F) { + reportSubtask(initialSyncProgressService, InitSyncStep.ImportingAccount, 1, 0.7F) { handleSyncFile(file, initSyncStrategy) } + // Delete all files + workingDir.deleteRecursively() } else { val syncResponse = logDuration("INIT_SYNC Request") { - executeRequest<SyncResponse>(globalErrorReceiver) { - apiCall = syncAPI.sync( + executeRequest(globalErrorReceiver) { + syncAPI.sync( params = requestParams, readTimeOut = readTimeOut ) @@ -125,18 +128,9 @@ internal class DefaultSyncTask @Inject constructor( } } initialSyncProgressService.endAll() - - if (initSyncStrategy is InitialSyncStrategy.Optimized) { - logDuration("INIT_SYNC Handle ephemeral") { - syncResponseHandler.handleInitSyncSecondTransaction(syncResp!!) - } - initialSyncStatusRepository.setStep(InitialSyncStatus.STEP_SUCCESS) - // Delete all files - workingDir.deleteRecursively() - } } else { - val syncResponse = executeRequest<SyncResponse>(globalErrorReceiver) { - apiCall = syncAPI.sync( + val syncResponse = executeRequest(globalErrorReceiver) { + syncAPI.sync( params = requestParams, readTimeOut = readTimeOut ) @@ -147,11 +141,10 @@ internal class DefaultSyncTask @Inject constructor( } private suspend fun downloadInitSyncResponse(requestParams: Map<String, String>): File { - workingDir.mkdirs() val workingFile = File(workingDir, "initSync.json") val status = initialSyncStatusRepository.getStep() if (workingFile.exists() && status >= InitialSyncStatus.STEP_DOWNLOADED) { - Timber.v("INIT_SYNC file is already here") + Timber.d("INIT_SYNC file is already here") reportSubtask(initialSyncProgressService, InitSyncStep.Downloading, 1, 0.3f) { // Empty task } @@ -201,8 +194,8 @@ internal class DefaultSyncTask @Inject constructor( } } - private suspend fun handleSyncFile(workingFile: File, initSyncStrategy: InitialSyncStrategy.Optimized): SyncResponse { - return logDuration("INIT_SYNC handleSyncFile()") { + private suspend fun handleSyncFile(workingFile: File, initSyncStrategy: InitialSyncStrategy.Optimized) { + logDuration("INIT_SYNC handleSyncFile()") { val syncResponse = logDuration("INIT_SYNC Read file and parse") { syncResponseParser.parse(initSyncStrategy, workingFile) } @@ -210,12 +203,12 @@ internal class DefaultSyncTask @Inject constructor( // Log some stats val nbOfJoinedRooms = syncResponse.rooms?.join?.size ?: 0 val nbOfJoinedRoomsInFile = syncResponse.rooms?.join?.values?.count { it.ephemeral is LazyRoomSyncEphemeral.Stored } - Timber.v("INIT_SYNC $nbOfJoinedRooms rooms, $nbOfJoinedRoomsInFile ephemeral stored into files") + Timber.d("INIT_SYNC $nbOfJoinedRooms rooms, $nbOfJoinedRoomsInFile ephemeral stored into files") logDuration("INIT_SYNC Database insertion") { syncResponseHandler.handleResponse(syncResponse, null, initialSyncProgressService) } - syncResponse + initialSyncStatusRepository.setStep(InitialSyncStatus.STEP_SUCCESS) } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/UserAccountDataSyncHandler.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/UserAccountDataSyncHandler.kt index 449d47abe5580670a8a245623b6210062fb3da81..b8d987d5009471f7a08b1ce7553dccdc01ea976a 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/UserAccountDataSyncHandler.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/UserAccountDataSyncHandler.kt @@ -45,6 +45,8 @@ 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.SessionDatabase import org.matrix.android.sdk.internal.di.UserId +import org.matrix.android.sdk.internal.session.room.RoomAvatarResolver +import org.matrix.android.sdk.internal.session.room.membership.RoomDisplayNameResolver import org.matrix.android.sdk.internal.session.room.membership.RoomMemberHelper import org.matrix.android.sdk.internal.session.sync.model.InvitedRoomSync import org.matrix.android.sdk.internal.session.sync.model.accountdata.BreadcrumbsContent @@ -60,7 +62,10 @@ internal class UserAccountDataSyncHandler @Inject constructor( @SessionDatabase private val monarchy: Monarchy, @UserId private val userId: String, private val directChatsHelper: DirectChatsHelper, - private val updateUserAccountDataTask: UpdateUserAccountDataTask) { + private val updateUserAccountDataTask: UpdateUserAccountDataTask, + private val roomAvatarResolver: RoomAvatarResolver, + private val roomDisplayNameResolver: RoomDisplayNameResolver +) { fun handle(realm: Realm, accountData: UserAccountDataSync?) { accountData?.list?.forEach { event -> @@ -151,23 +156,29 @@ internal class UserAccountDataSyncHandler @Inject constructor( } private fun handleDirectChatRooms(realm: Realm, event: UserAccountDataEvent) { - val oldDirectRooms = RoomSummaryEntity.getDirectRooms(realm) - oldDirectRooms.forEach { - it.isDirect = false - it.directUserId = null - } val content = event.content.toModel<DirectMessagesContent>() ?: return - content.forEach { - val userId = it.key - it.value.forEach { roomId -> + content.forEach { (userId, roomIds) -> + roomIds.forEach { roomId -> val roomSummaryEntity = RoomSummaryEntity.where(realm, roomId).findFirst() if (roomSummaryEntity != null) { roomSummaryEntity.isDirect = true roomSummaryEntity.directUserId = userId - realm.insertOrUpdate(roomSummaryEntity) + // Also update the avatar and displayname, there is a specific treatment for DMs + roomSummaryEntity.avatarUrl = roomAvatarResolver.resolve(realm, roomId) + roomSummaryEntity.displayName = roomDisplayNameResolver.resolve(realm, roomId) } } } + + // Handle previous direct rooms + RoomSummaryEntity.getDirectRooms(realm, excludeRoomIds = content.values.flatten().toSet()) + .forEach { + it.isDirect = false + it.directUserId = null + // Also update the avatar and displayname, there was a specific treatment for DMs + it.avatarUrl = roomAvatarResolver.resolve(realm, it.roomId) + it.displayName = roomDisplayNameResolver.resolve(realm, it.roomId) + } } private fun handleIgnoredUsers(realm: Realm, event: UserAccountDataEvent) { 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 211b643516d5cb729703698130345f950d837ab7..b81804feb53983556b1a5bd834dfe06c25519e36 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 @@ -106,7 +106,7 @@ internal class SyncWorker(context: Context, val data = WorkerParamsFactory.toData(Params(sessionId, serverTimeout, 0L, false)) val workRequest = workManagerProvider.matrixOneTimeWorkRequestBuilder<SyncWorker>() .setConstraints(WorkManagerProvider.workConstraints) - .setBackoffCriteria(BackoffPolicy.LINEAR, 1_000, TimeUnit.MILLISECONDS) + .setBackoffCriteria(BackoffPolicy.LINEAR, WorkManagerProvider.BACKOFF_DELAY_MILLIS, TimeUnit.MILLISECONDS) .setInputData(data) .build() workManagerProvider.workManager @@ -118,7 +118,7 @@ internal class SyncWorker(context: Context, val workRequest = workManagerProvider.matrixOneTimeWorkRequestBuilder<SyncWorker>() .setConstraints(WorkManagerProvider.workConstraints) .setInputData(data) - .setBackoffCriteria(BackoffPolicy.LINEAR, 1_000, TimeUnit.MILLISECONDS) + .setBackoffCriteria(BackoffPolicy.LINEAR, WorkManagerProvider.BACKOFF_DELAY_MILLIS, TimeUnit.MILLISECONDS) .setInitialDelay(delayInSeconds, TimeUnit.SECONDS) .build() diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/model/LazyRoomSyncEphemeral.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/model/LazyRoomSyncEphemeral.kt index 938168b5f4bb368c0863c763f6813cca5a79e078..83006c646b2710eac33e92e9838a9fbaa31928cf 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/model/LazyRoomSyncEphemeral.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/model/LazyRoomSyncEphemeral.kt @@ -16,28 +16,10 @@ package org.matrix.android.sdk.internal.session.sync.model -import com.squareup.moshi.JsonAdapter import com.squareup.moshi.JsonClass -import com.squareup.moshi.JsonReader -import okio.buffer -import okio.source -import java.io.File @JsonClass(generateAdapter = false) internal sealed class LazyRoomSyncEphemeral { data class Parsed(val _roomSyncEphemeral: RoomSyncEphemeral) : LazyRoomSyncEphemeral() - data class Stored(val roomSyncEphemeralAdapter: JsonAdapter<RoomSyncEphemeral>, val file: File) : LazyRoomSyncEphemeral() - - val roomSyncEphemeral: RoomSyncEphemeral - get() { - return when (this) { - is Parsed -> _roomSyncEphemeral - is Stored -> { - // Parse the file now - file.inputStream().use { pos -> - roomSyncEphemeralAdapter.fromJson(JsonReader.of(pos.source().buffer()))!! - } - } - } - } + object Stored : LazyRoomSyncEphemeral() } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/model/accountdata/DirectMessagesContent.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/model/accountdata/DirectMessagesContent.kt index c406f3acf1e9212f38750afc3060df7febb8ce5e..41173dea96b8b3ef5aa27ea0e25f1f4848c04aae 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/model/accountdata/DirectMessagesContent.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/model/accountdata/DirectMessagesContent.kt @@ -16,4 +16,7 @@ package org.matrix.android.sdk.internal.session.sync.model.accountdata -typealias DirectMessagesContent = Map<String, List<String>> +/** + * Keys are userIds, values are list of roomIds + */ +internal typealias DirectMessagesContent = Map<String, List<String>> diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/parsing/DefaultLazyRoomSyncEphemeralJsonAdapter.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/parsing/DefaultLazyRoomSyncEphemeralJsonAdapter.kt index ef56802a668c2206d0d73f24f1be0732c098ec3c..940ea219fb61c9d544bf9c7da8168644c8821bee 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/parsing/DefaultLazyRoomSyncEphemeralJsonAdapter.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/parsing/DefaultLazyRoomSyncEphemeralJsonAdapter.kt @@ -22,11 +22,10 @@ import com.squareup.moshi.JsonReader import com.squareup.moshi.JsonWriter import com.squareup.moshi.ToJson import org.matrix.android.sdk.internal.session.sync.InitialSyncStrategy +import org.matrix.android.sdk.internal.session.sync.RoomSyncEphemeralTemporaryStore import org.matrix.android.sdk.internal.session.sync.model.LazyRoomSyncEphemeral import org.matrix.android.sdk.internal.session.sync.model.RoomSyncEphemeral import timber.log.Timber -import java.io.File -import java.util.concurrent.atomic.AtomicInteger internal class DefaultLazyRoomSyncEphemeralJsonAdapter { @@ -44,32 +43,26 @@ internal class DefaultLazyRoomSyncEphemeralJsonAdapter { } } -internal class SplitLazyRoomSyncJsonAdapter( - private val workingDirectory: File, +internal class SplitLazyRoomSyncEphemeralJsonAdapter( + private val roomSyncEphemeralTemporaryStore: RoomSyncEphemeralTemporaryStore, private val syncStrategy: InitialSyncStrategy.Optimized ) { - private val atomicInteger = AtomicInteger(0) - - private fun createFile(): File { - val index = atomicInteger.getAndIncrement() - return File(workingDirectory, "room_$index.json") - } - @FromJson fun fromJson(reader: JsonReader, delegate: JsonAdapter<RoomSyncEphemeral>): LazyRoomSyncEphemeral? { val path = reader.path + val roomId = path.substringAfter("\$.rooms.join.").substringBeforeLast(".ephemeral") + val json = reader.nextSource().inputStream().bufferedReader().use { it.readText() } val limit = syncStrategy.minSizeToStoreInFile return if (json.length > limit) { - Timber.v("INIT_SYNC $path content length: ${json.length} copy to a file") + Timber.d("INIT_SYNC $path content length: ${json.length} copy to a file") // Copy the source to a file - val file = createFile() - file.writeText(json) - LazyRoomSyncEphemeral.Stored(delegate, file) + roomSyncEphemeralTemporaryStore.write(roomId, json) + LazyRoomSyncEphemeral.Stored } else { - Timber.v("INIT_SYNC $path content length: ${json.length} parse it now") + Timber.d("INIT_SYNC $path content length: ${json.length} parse it now") val roomSync = delegate.fromJson(json) ?: return null LazyRoomSyncEphemeral.Parsed(roomSync) } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/parsing/InitialSyncResponseParser.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/parsing/InitialSyncResponseParser.kt index ae7b2a44681f2369b5301f6cd9eeaf5b1d8487d8..0b44887aedcff07a73fe9f87bd7e75ba023e1c97 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/parsing/InitialSyncResponseParser.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/parsing/InitialSyncResponseParser.kt @@ -20,29 +20,33 @@ import com.squareup.moshi.Moshi import okio.buffer import okio.source import org.matrix.android.sdk.internal.session.sync.InitialSyncStrategy +import org.matrix.android.sdk.internal.session.sync.RoomSyncEphemeralTemporaryStore import org.matrix.android.sdk.internal.session.sync.model.SyncResponse import timber.log.Timber import java.io.File import javax.inject.Inject -internal class InitialSyncResponseParser @Inject constructor(private val moshi: Moshi) { +internal class InitialSyncResponseParser @Inject constructor( + private val moshi: Moshi, + private val roomSyncEphemeralTemporaryStore: RoomSyncEphemeralTemporaryStore +) { fun parse(syncStrategy: InitialSyncStrategy.Optimized, workingFile: File): SyncResponse { val syncResponseLength = workingFile.length().toInt() - Timber.v("INIT_SYNC Sync file size is $syncResponseLength bytes") + Timber.d("INIT_SYNC Sync file size is $syncResponseLength bytes") val shouldSplit = syncResponseLength >= syncStrategy.minSizeToSplit - Timber.v("INIT_SYNC should split in several files: $shouldSplit") - return getMoshi(syncStrategy, workingFile.parentFile!!, shouldSplit) + Timber.d("INIT_SYNC should split in several files: $shouldSplit") + return getMoshi(syncStrategy, shouldSplit) .adapter(SyncResponse::class.java) .fromJson(workingFile.source().buffer())!! } - private fun getMoshi(syncStrategy: InitialSyncStrategy.Optimized, workingDirectory: File, shouldSplit: Boolean): Moshi { + private fun getMoshi(syncStrategy: InitialSyncStrategy.Optimized, shouldSplit: Boolean): Moshi { // If we don't have to split we'll rely on the already default moshi if (!shouldSplit) return moshi // Otherwise, we create a new adapter for handling Map of Lazy sync return moshi.newBuilder() - .add(SplitLazyRoomSyncJsonAdapter(workingDirectory, syncStrategy)) + .add(SplitLazyRoomSyncEphemeralJsonAdapter(roomSyncEphemeralTemporaryStore, syncStrategy)) .build() } } 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 41914cc7997b6419ee273c49c64681bf11285b52..bac725fad28f25cb3a495b62656ee3fea94f4815 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 @@ -17,7 +17,8 @@ package org.matrix.android.sdk.internal.session.terms import dagger.Lazy -import kotlinx.coroutines.withContext +import okhttp3.OkHttpClient +import org.matrix.android.sdk.api.session.accountdata.UserAccountDataTypes import org.matrix.android.sdk.api.session.events.model.toModel import org.matrix.android.sdk.api.session.terms.GetTermsResponse import org.matrix.android.sdk.api.session.terms.TermsService @@ -29,12 +30,9 @@ import org.matrix.android.sdk.internal.session.identity.IdentityAuthAPI import org.matrix.android.sdk.internal.session.identity.IdentityRegisterTask import org.matrix.android.sdk.internal.session.openid.GetOpenIdTokenTask import org.matrix.android.sdk.internal.session.sync.model.accountdata.AcceptedTermsContent -import org.matrix.android.sdk.api.session.accountdata.UserAccountDataTypes import org.matrix.android.sdk.internal.session.user.accountdata.AccountDataDataSource import org.matrix.android.sdk.internal.session.user.accountdata.UpdateUserAccountDataTask -import org.matrix.android.sdk.internal.util.MatrixCoroutineDispatchers import org.matrix.android.sdk.internal.util.ensureTrailingSlash -import okhttp3.OkHttpClient import javax.inject.Inject internal class DefaultTermsService @Inject constructor( @@ -45,43 +43,39 @@ internal class DefaultTermsService @Inject constructor( private val retrofitFactory: RetrofitFactory, private val getOpenIdTokenTask: GetOpenIdTokenTask, private val identityRegisterTask: IdentityRegisterTask, - private val updateUserAccountDataTask: UpdateUserAccountDataTask, - private val coroutineDispatchers: MatrixCoroutineDispatchers + private val updateUserAccountDataTask: UpdateUserAccountDataTask ) : TermsService { + override suspend fun getTerms(serviceType: TermsService.ServiceType, - baseUrl: String): GetTermsResponse { - return withContext(coroutineDispatchers.main) { - val url = buildUrl(baseUrl, serviceType) - val termsResponse = executeRequest<TermsResponse>(null) { - apiCall = termsAPI.getTerms("${url}terms") - } - GetTermsResponse(termsResponse, getAlreadyAcceptedTermUrlsFromAccountData()) + baseUrl: String): GetTermsResponse { + val url = buildUrl(baseUrl, serviceType) + val termsResponse = executeRequest(null) { + termsAPI.getTerms("${url}terms") } + return GetTermsResponse(termsResponse, getAlreadyAcceptedTermUrlsFromAccountData()) } override suspend fun agreeToTerms(serviceType: TermsService.ServiceType, baseUrl: String, agreedUrls: List<String>, token: String?) { - withContext(coroutineDispatchers.main) { - val url = buildUrl(baseUrl, serviceType) - val tokenToUse = token?.takeIf { it.isNotEmpty() } ?: getToken(baseUrl) + val url = buildUrl(baseUrl, serviceType) + val tokenToUse = token?.takeIf { it.isNotEmpty() } ?: getToken(baseUrl) - executeRequest<Unit>(null) { - apiCall = termsAPI.agreeToTerms("${url}terms", AcceptTermsBody(agreedUrls), "Bearer $tokenToUse") - } + executeRequest(null) { + termsAPI.agreeToTerms("${url}terms", AcceptTermsBody(agreedUrls), "Bearer $tokenToUse") + } - // client SHOULD update this account data section adding any the URLs - // of any additional documents that the user agreed to this list. - // Get current m.accepted_terms append new ones and update account data - val listOfAcceptedTerms = getAlreadyAcceptedTermUrlsFromAccountData() + // client SHOULD update this account data section adding any the URLs + // of any additional documents that the user agreed to this list. + // Get current m.accepted_terms append new ones and update account data + val listOfAcceptedTerms = getAlreadyAcceptedTermUrlsFromAccountData() - val newList = listOfAcceptedTerms.toMutableSet().apply { addAll(agreedUrls) }.toList() + val newList = listOfAcceptedTerms.toMutableSet().apply { addAll(agreedUrls) }.toList() - updateUserAccountDataTask.execute(UpdateUserAccountDataTask.AcceptedTermsParams( - acceptedTermsContent = AcceptedTermsContent(newList) - )) - } + updateUserAccountDataTask.execute(UpdateUserAccountDataTask.AcceptedTermsParams( + acceptedTermsContent = AcceptedTermsContent(newList) + )) } private suspend fun getToken(url: String): String { @@ -97,7 +91,7 @@ internal class DefaultTermsService @Inject constructor( private fun buildUrl(baseUrl: String, serviceType: TermsService.ServiceType): String { val servicePath = when (serviceType) { TermsService.ServiceType.IntegrationManager -> NetworkConstants.URI_INTEGRATION_MANAGER_PATH - TermsService.ServiceType.IdentityService -> NetworkConstants.URI_IDENTITY_PATH_V2 + TermsService.ServiceType.IdentityService -> NetworkConstants.URI_IDENTITY_PATH_V2 } return "${baseUrl.ensureTrailingSlash()}$servicePath" } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/terms/TermsAPI.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/terms/TermsAPI.kt index 4c97f462eb16c2b4ce410cb2dca0bd45eeb73781..91d27030de905d3d20172a2decd09eee08568005 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/terms/TermsAPI.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/terms/TermsAPI.kt @@ -17,7 +17,6 @@ package org.matrix.android.sdk.internal.session.terms import org.matrix.android.sdk.internal.network.HttpHeaders -import retrofit2.Call import retrofit2.http.Body import retrofit2.http.GET import retrofit2.http.Header @@ -29,13 +28,13 @@ internal interface TermsAPI { * This request does not require authentication */ @GET - fun getTerms(@Url url: String): Call<TermsResponse> + suspend fun getTerms(@Url url: String): TermsResponse /** * This request requires authentication */ @POST - fun agreeToTerms(@Url url: String, - @Body params: AcceptTermsBody, - @Header(HttpHeaders.Authorization) token: String): Call<Unit> + suspend fun agreeToTerms(@Url url: String, + @Body params: AcceptTermsBody, + @Header(HttpHeaders.Authorization) token: String) } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/thirdparty/GetThirdPartyProtocolsTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/thirdparty/GetThirdPartyProtocolsTask.kt index fd1ed741e991b3f80506f4571abcab2c908ad394..026e17b51301ffd869b91ead48e549b041b7d0a4 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/thirdparty/GetThirdPartyProtocolsTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/thirdparty/GetThirdPartyProtocolsTask.kt @@ -31,7 +31,7 @@ internal class DefaultGetThirdPartyProtocolsTask @Inject constructor( override suspend fun execute(params: Unit): Map<String, ThirdPartyProtocol> { return executeRequest(globalErrorReceiver) { - apiCall = thirdPartyAPI.thirdPartyProtocols() + thirdPartyAPI.thirdPartyProtocols() } } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/thirdparty/GetThirdPartyUserTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/thirdparty/GetThirdPartyUserTask.kt index 01a8b576783c404f53409176ec48d6f5a48b6c6b..f541dcb8145ad315d4265152f961cf62dbec92c5 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/thirdparty/GetThirdPartyUserTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/thirdparty/GetThirdPartyUserTask.kt @@ -37,7 +37,7 @@ internal class DefaultGetThirdPartyUserTask @Inject constructor( override suspend fun execute(params: GetThirdPartyUserTask.Params): List<ThirdPartyUser> { return executeRequest(globalErrorReceiver) { - apiCall = thirdPartyAPI.getThirdPartyUser(params.protocol, params.fields) + thirdPartyAPI.getThirdPartyUser(params.protocol, params.fields) } } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/thirdparty/ThirdPartyAPI.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/thirdparty/ThirdPartyAPI.kt index 0c60a27341239c661054f993e2cfbe757a735557..2e03bc7a869e76a8284cafe0ff016b3d20233c9d 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/thirdparty/ThirdPartyAPI.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/thirdparty/ThirdPartyAPI.kt @@ -19,7 +19,6 @@ package org.matrix.android.sdk.internal.session.thirdparty import org.matrix.android.sdk.api.session.room.model.thirdparty.ThirdPartyProtocol import org.matrix.android.sdk.api.session.thirdparty.model.ThirdPartyUser import org.matrix.android.sdk.internal.network.NetworkConstants -import retrofit2.Call import retrofit2.http.GET import retrofit2.http.Path import retrofit2.http.QueryMap @@ -32,7 +31,7 @@ internal interface ThirdPartyAPI { * Ref: https://matrix.org/docs/spec/client_server/r0.6.1.html#get-matrix-client-r0-thirdparty-protocols */ @GET(NetworkConstants.URI_API_PREFIX_PATH_R0 + "thirdparty/protocols") - fun thirdPartyProtocols(): Call<Map<String, ThirdPartyProtocol>> + suspend fun thirdPartyProtocols(): Map<String, ThirdPartyProtocol> /** * Retrieve a Matrix User ID linked to a user on the third party service, given a set of user parameters. @@ -40,5 +39,6 @@ internal interface ThirdPartyAPI { * Ref: https://matrix.org/docs/spec/client_server/r0.6.1#get-matrix-client-r0-thirdparty-user-protocol */ @GET(NetworkConstants.URI_API_PREFIX_PATH_R0 + "thirdparty/protocols/user/{protocol}") - fun getThirdPartyUser(@Path("protocol") protocol: String, @QueryMap params: Map<String, String>?): Call<List<ThirdPartyUser>> + suspend fun getThirdPartyUser(@Path("protocol") protocol: String, + @QueryMap params: Map<String, String>?): List<ThirdPartyUser> } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/user/DefaultUserService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/user/DefaultUserService.kt index 17409569150f97a462ad72fee0087dd8d9691e1b..52b8cc36892422ea50766d714dbf66c16c74d031 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/user/DefaultUserService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/user/DefaultUserService.kt @@ -18,54 +18,35 @@ package org.matrix.android.sdk.internal.session.user import androidx.lifecycle.LiveData import androidx.paging.PagedList -import org.matrix.android.sdk.api.MatrixCallback import org.matrix.android.sdk.api.session.profile.ProfileService import org.matrix.android.sdk.api.session.user.UserService import org.matrix.android.sdk.api.session.user.model.User -import org.matrix.android.sdk.api.util.Cancelable -import org.matrix.android.sdk.api.util.JsonDict import org.matrix.android.sdk.api.util.Optional import org.matrix.android.sdk.internal.session.profile.GetProfileInfoTask import org.matrix.android.sdk.internal.session.user.accountdata.UpdateIgnoredUserIdsTask import org.matrix.android.sdk.internal.session.user.model.SearchUserTask -import org.matrix.android.sdk.internal.task.TaskExecutor -import org.matrix.android.sdk.internal.task.configureWith import javax.inject.Inject internal class DefaultUserService @Inject constructor(private val userDataSource: UserDataSource, private val searchUserTask: SearchUserTask, private val updateIgnoredUserIdsTask: UpdateIgnoredUserIdsTask, - private val getProfileInfoTask: GetProfileInfoTask, - private val taskExecutor: TaskExecutor) : UserService { + private val getProfileInfoTask: GetProfileInfoTask) : UserService { override fun getUser(userId: String): User? { return userDataSource.getUser(userId) } - override fun resolveUser(userId: String, callback: MatrixCallback<User>) { + override suspend fun resolveUser(userId: String): User { val known = getUser(userId) if (known != null) { - callback.onSuccess(known) + return known } else { val params = GetProfileInfoTask.Params(userId) - getProfileInfoTask - .configureWith(params) { - this.callback = object : MatrixCallback<JsonDict> { - override fun onSuccess(data: JsonDict) { - callback.onSuccess( - User( - userId, - data[ProfileService.DISPLAY_NAME_KEY] as? String, - data[ProfileService.AVATAR_URL_KEY] as? String) - ) - } - - override fun onFailure(failure: Throwable) { - callback.onFailure(failure) - } - } - } - .executeBy(taskExecutor) + val data = getProfileInfoTask.execute(params) + return User( + userId, + data[ProfileService.DISPLAY_NAME_KEY] as? String, + data[ProfileService.AVATAR_URL_KEY] as? String) } } @@ -85,33 +66,20 @@ internal class DefaultUserService @Inject constructor(private val userDataSource return userDataSource.getIgnoredUsersLive() } - override fun searchUsersDirectory(search: String, - limit: Int, - excludedUserIds: Set<String>, - callback: MatrixCallback<List<User>>): Cancelable { + override suspend fun searchUsersDirectory(search: String, + limit: Int, + excludedUserIds: Set<String>): List<User> { val params = SearchUserTask.Params(limit, search, excludedUserIds) - return searchUserTask - .configureWith(params) { - this.callback = callback - } - .executeBy(taskExecutor) + return searchUserTask.execute(params) } - override fun ignoreUserIds(userIds: List<String>, callback: MatrixCallback<Unit>): Cancelable { + override suspend fun ignoreUserIds(userIds: List<String>) { val params = UpdateIgnoredUserIdsTask.Params(userIdsToIgnore = userIds.toList()) - return updateIgnoredUserIdsTask - .configureWith(params) { - this.callback = callback - } - .executeBy(taskExecutor) + updateIgnoredUserIdsTask.execute(params) } - override fun unIgnoreUserIds(userIds: List<String>, callback: MatrixCallback<Unit>): Cancelable { + override suspend fun unIgnoreUserIds(userIds: List<String>) { val params = UpdateIgnoredUserIdsTask.Params(userIdsToUnIgnore = userIds.toList()) - return updateIgnoredUserIdsTask - .configureWith(params) { - this.callback = callback - } - .executeBy(taskExecutor) + updateIgnoredUserIdsTask.execute(params) } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/user/SearchUserAPI.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/user/SearchUserAPI.kt index c5c546bbed55a2382d4bd43d9ec5f96f0400ebc2..e03d4066399697f183fd04b75db134156dea6efa 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/user/SearchUserAPI.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/user/SearchUserAPI.kt @@ -19,7 +19,6 @@ package org.matrix.android.sdk.internal.session.user import org.matrix.android.sdk.internal.network.NetworkConstants import org.matrix.android.sdk.internal.session.user.model.SearchUsersParams import org.matrix.android.sdk.internal.session.user.model.SearchUsersResponse -import retrofit2.Call import retrofit2.http.Body import retrofit2.http.POST @@ -31,5 +30,5 @@ internal interface SearchUserAPI { * @param searchUsersParams the search params. */ @POST(NetworkConstants.URI_API_PREFIX_PATH_R0 + "user_directory/search") - fun searchUsers(@Body searchUsersParams: SearchUsersParams): Call<SearchUsersResponse> + suspend fun searchUsers(@Body searchUsersParams: SearchUsersParams): SearchUsersResponse } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/user/accountdata/AccountDataAPI.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/user/accountdata/AccountDataAPI.kt index 3de484fab344f3bab31ed97fe6329c570290824f..cc5625b2554b3b4d56954d595cf81e58fdd69373 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/user/accountdata/AccountDataAPI.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/user/accountdata/AccountDataAPI.kt @@ -17,7 +17,6 @@ package org.matrix.android.sdk.internal.session.user.accountdata import org.matrix.android.sdk.internal.network.NetworkConstants -import retrofit2.Call import retrofit2.http.Body import retrofit2.http.PUT import retrofit2.http.Path @@ -32,7 +31,7 @@ interface AccountDataAPI { * @param params the put params */ @PUT(NetworkConstants.URI_API_PREFIX_PATH_R0 + "user/{userId}/account_data/{type}") - fun setAccountData(@Path("userId") userId: String, - @Path("type") type: String, - @Body params: Any): Call<Unit> + suspend fun setAccountData(@Path("userId") userId: String, + @Path("type") type: String, + @Body params: Any) } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/user/accountdata/DefaultAccountDataService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/user/accountdata/DefaultAccountDataService.kt index 1f1e987ebfd8391ec5e72ac85f0532933cc24f8d..27db30f3b3dc6f9671d06ef4178d97c77318e192 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/user/accountdata/DefaultAccountDataService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/user/accountdata/DefaultAccountDataService.kt @@ -18,16 +18,15 @@ package org.matrix.android.sdk.internal.session.user.accountdata import androidx.lifecycle.LiveData import com.zhuinden.monarchy.Monarchy -import org.matrix.android.sdk.api.MatrixCallback import org.matrix.android.sdk.api.session.accountdata.AccountDataService import org.matrix.android.sdk.api.session.events.model.Content -import org.matrix.android.sdk.api.util.Cancelable import org.matrix.android.sdk.api.util.Optional import org.matrix.android.sdk.internal.di.SessionDatabase import org.matrix.android.sdk.internal.session.sync.UserAccountDataSyncHandler import org.matrix.android.sdk.api.session.accountdata.UserAccountDataEvent import org.matrix.android.sdk.internal.task.TaskExecutor import org.matrix.android.sdk.internal.task.configureWith +import org.matrix.android.sdk.internal.util.awaitCallback import javax.inject.Inject internal class DefaultAccountDataService @Inject constructor( @@ -54,26 +53,18 @@ internal class DefaultAccountDataService @Inject constructor( return accountDataDataSource.getLiveAccountDataEvents(types) } - override fun updateAccountData(type: String, content: Content, callback: MatrixCallback<Unit>?): Cancelable { - return updateUserAccountDataTask.configureWith(UpdateUserAccountDataTask.AnyParams( - type = type, - any = content - )) { - this.retryCount = 5 - this.callback = object : MatrixCallback<Unit> { - override fun onSuccess(data: Unit) { - // TODO Move that to the task (but it created a circular dependencies...) - monarchy.runTransactionSync { realm -> - userAccountDataSyncHandler.handleGenericAccountData(realm, type, content) - } - callback?.onSuccess(data) - } - - override fun onFailure(failure: Throwable) { - callback?.onFailure(failure) - } + override suspend fun updateAccountData(type: String, content: Content) { + val params = UpdateUserAccountDataTask.AnyParams(type = type, any = content) + awaitCallback<Unit> { callback -> + updateUserAccountDataTask.configureWith(params) { + this.retryCount = 5 // TODO: Need to refactor retrying out into a helper method. + this.callback = callback } + .executeBy(taskExecutor) + } + // TODO Move that to the task (but it created a circular dependencies...) + monarchy.runTransactionSync { realm -> + userAccountDataSyncHandler.handleGenericAccountData(realm, type, content) } - .executeBy(taskExecutor) } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/user/accountdata/UpdateIgnoredUserIdsTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/user/accountdata/UpdateIgnoredUserIdsTask.kt index 26e8d3380a04cf8f2dff84e73cf6ffbcd9f633c2..445b78104cfb7bd0431ab34a4296f9266a07bd13 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/user/accountdata/UpdateIgnoredUserIdsTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/user/accountdata/UpdateIgnoredUserIdsTask.kt @@ -63,8 +63,8 @@ internal class DefaultUpdateIgnoredUserIdsTask @Inject constructor( val list = ignoredUserIds.toList() val body = IgnoredUsersContent.createWithUserIds(list) - executeRequest<Unit>(globalErrorReceiver) { - apiCall = accountDataApi.setAccountData(userId, UserAccountDataTypes.TYPE_IGNORED_USER_LIST, body) + executeRequest(globalErrorReceiver) { + accountDataApi.setAccountData(userId, UserAccountDataTypes.TYPE_IGNORED_USER_LIST, body) } // Update the DB right now (do not wait for the sync to come back with updated data, for a faster UI update) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/user/accountdata/UpdateUserAccountDataTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/user/accountdata/UpdateUserAccountDataTask.kt index dba28253a7440fc1358c6605a86792634068d39a..1a588d2245b0ad494d6d9db294aa3325490f12a6 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/user/accountdata/UpdateUserAccountDataTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/user/accountdata/UpdateUserAccountDataTask.kt @@ -105,7 +105,7 @@ internal class DefaultUpdateUserAccountDataTask @Inject constructor( override suspend fun execute(params: UpdateUserAccountDataTask.Params) { return executeRequest(globalErrorReceiver) { - apiCall = accountDataApi.setAccountData(userId, params.type, params.getData()) + accountDataApi.setAccountData(userId, params.type, params.getData()) } } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/user/model/SearchUserTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/user/model/SearchUserTask.kt index 380fa6e209cdb47293c1ee9df2c63bc79356036d..5a8779f40fedd299a950d07363a6419cdc60cc62 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/user/model/SearchUserTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/user/model/SearchUserTask.kt @@ -38,8 +38,8 @@ internal class DefaultSearchUserTask @Inject constructor( ) : SearchUserTask { override suspend fun execute(params: SearchUserTask.Params): List<User> { - val response = executeRequest<SearchUsersResponse>(globalErrorReceiver) { - apiCall = searchUserAPI.searchUsers(SearchUsersParams(params.search, params.limit)) + val response = executeRequest(globalErrorReceiver) { + searchUserAPI.searchUsers(SearchUsersParams(params.search, params.limit)) } return response.users.map { User(it.userId, it.displayName, it.avatarUrl) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/widgets/CreateWidgetTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/widgets/CreateWidgetTask.kt index ae807ce30fefed2f472b4101b386c611d7105699..18a043be453177a78d791e88ee854ad6a6eb1a0c 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/widgets/CreateWidgetTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/widgets/CreateWidgetTask.kt @@ -46,8 +46,8 @@ internal class DefaultCreateWidgetTask @Inject constructor(@SessionDatabase priv private val globalErrorReceiver: GlobalErrorReceiver) : CreateWidgetTask { override suspend fun execute(params: CreateWidgetTask.Params) { - executeRequest<Unit>(globalErrorReceiver) { - apiCall = roomAPI.sendStateEvent( + executeRequest(globalErrorReceiver) { + roomAPI.sendStateEvent( roomId = params.roomId, stateEventType = EventType.STATE_ROOM_WIDGET_LEGACY, stateKey = params.widgetId, diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/widgets/DefaultWidgetService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/widgets/DefaultWidgetService.kt index 9f5a9360ee9a54fed08196a7954b638e5302dcb4..5912dc7b537c42ba39a3c76e707ca5b7097b9db3 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/widgets/DefaultWidgetService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/widgets/DefaultWidgetService.kt @@ -17,14 +17,12 @@ package org.matrix.android.sdk.internal.session.widgets import androidx.lifecycle.LiveData -import org.matrix.android.sdk.api.MatrixCallback import org.matrix.android.sdk.api.query.QueryStringValue import org.matrix.android.sdk.api.session.events.model.Content import org.matrix.android.sdk.api.session.widgets.WidgetPostAPIMediator import org.matrix.android.sdk.api.session.widgets.WidgetService import org.matrix.android.sdk.api.session.widgets.WidgetURLFormatter import org.matrix.android.sdk.api.session.widgets.model.Widget -import org.matrix.android.sdk.api.util.Cancelable import javax.inject.Inject import javax.inject.Provider @@ -77,21 +75,19 @@ internal class DefaultWidgetService @Inject constructor(private val widgetManage return widgetManager.getUserWidgets(widgetTypes, excludedTypes) } - override fun createRoomWidget( + override suspend fun createRoomWidget( roomId: String, widgetId: String, - content: Content, - callback: MatrixCallback<Widget> - ): Cancelable { - return widgetManager.createRoomWidget(roomId, widgetId, content, callback) + content: Content + ): Widget { + return widgetManager.createRoomWidget(roomId, widgetId, content) } - override fun destroyRoomWidget( + override suspend fun destroyRoomWidget( roomId: String, - widgetId: String, - callback: MatrixCallback<Unit> - ): Cancelable { - return widgetManager.destroyRoomWidget(roomId, widgetId, callback) + widgetId: String + ) { + return widgetManager.destroyRoomWidget(roomId, widgetId) } override fun hasPermissionsToHandleWidgets(roomId: String): Boolean { diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/widgets/WidgetManager.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/widgets/WidgetManager.kt index 73a4cc697dc320bd03e401f2a943858fc3506144..32442124872eaf11da5faab986e5f585a01aac4e 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/widgets/WidgetManager.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/widgets/WidgetManager.kt @@ -21,7 +21,6 @@ import androidx.lifecycle.LifecycleOwner import androidx.lifecycle.LifecycleRegistry import androidx.lifecycle.LiveData import androidx.lifecycle.Transformations -import org.matrix.android.sdk.api.MatrixCallback import org.matrix.android.sdk.api.query.QueryStringValue import org.matrix.android.sdk.api.session.accountdata.UserAccountDataEvent import org.matrix.android.sdk.api.session.accountdata.UserAccountDataTypes @@ -34,7 +33,6 @@ import org.matrix.android.sdk.api.session.room.model.PowerLevelsContent import org.matrix.android.sdk.api.session.room.powerlevels.PowerLevelsHelper import org.matrix.android.sdk.api.session.widgets.WidgetManagementFailure import org.matrix.android.sdk.api.session.widgets.model.Widget -import org.matrix.android.sdk.api.util.Cancelable import org.matrix.android.sdk.internal.di.UserId import org.matrix.android.sdk.internal.session.SessionLifecycleObserver import org.matrix.android.sdk.internal.session.SessionScope @@ -43,8 +41,6 @@ import org.matrix.android.sdk.internal.session.room.state.StateEventDataSource import org.matrix.android.sdk.internal.session.user.accountdata.AccountDataDataSource import org.matrix.android.sdk.internal.session.widgets.helper.WidgetFactory import org.matrix.android.sdk.internal.session.widgets.helper.extractWidgetSequence -import org.matrix.android.sdk.internal.task.TaskExecutor -import org.matrix.android.sdk.internal.task.launchToCallback import java.util.HashMap import javax.inject.Inject @@ -52,7 +48,6 @@ import javax.inject.Inject internal class WidgetManager @Inject constructor(private val integrationManager: IntegrationManager, private val accountDataDataSource: AccountDataDataSource, private val stateEventDataSource: StateEventDataSource, - private val taskExecutor: TaskExecutor, private val createWidgetTask: CreateWidgetTask, private val widgetFactory: WidgetFactory, @UserId private val userId: String) @@ -165,37 +160,33 @@ internal class WidgetManager @Inject constructor(private val integrationManager: .toList() } - fun createRoomWidget(roomId: String, widgetId: String, content: Content, callback: MatrixCallback<Widget>): Cancelable { - return taskExecutor.executorScope.launchToCallback(callback = callback) { - if (!hasPermissionsToHandleWidgets(roomId)) { - throw WidgetManagementFailure.NotEnoughPower - } - val params = CreateWidgetTask.Params( - roomId = roomId, - widgetId = widgetId, - content = content - ) - createWidgetTask.execute(params) - try { - getRoomWidgets(roomId, widgetId = QueryStringValue.Equals(widgetId, QueryStringValue.Case.INSENSITIVE)).first() - } catch (failure: Throwable) { - throw WidgetManagementFailure.CreationFailed - } + suspend fun createRoomWidget(roomId: String, widgetId: String, content: Content): Widget { + if (!hasPermissionsToHandleWidgets(roomId)) { + throw WidgetManagementFailure.NotEnoughPower + } + val params = CreateWidgetTask.Params( + roomId = roomId, + widgetId = widgetId, + content = content + ) + createWidgetTask.execute(params) + try { + return getRoomWidgets(roomId, widgetId = QueryStringValue.Equals(widgetId, QueryStringValue.Case.INSENSITIVE)).first() + } catch (failure: Throwable) { + throw WidgetManagementFailure.CreationFailed } } - fun destroyRoomWidget(roomId: String, widgetId: String, callback: MatrixCallback<Unit>): Cancelable { - return taskExecutor.executorScope.launchToCallback(callback = callback) { - if (!hasPermissionsToHandleWidgets(roomId)) { - throw WidgetManagementFailure.NotEnoughPower - } - val params = CreateWidgetTask.Params( - roomId = roomId, - widgetId = widgetId, - content = emptyMap() - ) - createWidgetTask.execute(params) + suspend fun destroyRoomWidget(roomId: String, widgetId: String) { + if (!hasPermissionsToHandleWidgets(roomId)) { + throw WidgetManagementFailure.NotEnoughPower } + val params = CreateWidgetTask.Params( + roomId = roomId, + widgetId = widgetId, + content = emptyMap() + ) + createWidgetTask.execute(params) } fun hasPermissionsToHandleWidgets(roomId: String): Boolean { diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/widgets/WidgetsAPI.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/widgets/WidgetsAPI.kt index 1fece8b5807f83d44e9890849670d2fda69a63cf..6652628026018bc3defde3f164151ef4391646f8 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/widgets/WidgetsAPI.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/widgets/WidgetsAPI.kt @@ -16,7 +16,6 @@ package org.matrix.android.sdk.internal.session.widgets import org.matrix.android.sdk.internal.session.openid.RequestOpenIdTokenResponse -import retrofit2.Call import retrofit2.http.Body import retrofit2.http.GET import retrofit2.http.POST @@ -30,10 +29,10 @@ internal interface WidgetsAPI { * @param body the body content (Ref: https://github.com/matrix-org/matrix-doc/pull/1961) */ @POST("register") - fun register(@Body body: RequestOpenIdTokenResponse, - @Query("v") version: String?): Call<RegisterWidgetResponse> + suspend fun register(@Body body: RequestOpenIdTokenResponse, + @Query("v") version: String?): RegisterWidgetResponse @GET("account") - fun validateToken(@Query("scalar_token") scalarToken: String?, - @Query("v") version: String?): Call<Unit> + suspend fun validateToken(@Query("scalar_token") scalarToken: String?, + @Query("v") version: String?) } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/widgets/token/GetScalarTokenTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/widgets/token/GetScalarTokenTask.kt index 6db79da35f3cf068872702c6226a2778f5afb607..78a40d197759e131c91639d45a175a0fa8effdbf 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/widgets/token/GetScalarTokenTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/widgets/token/GetScalarTokenTask.kt @@ -20,7 +20,6 @@ import org.matrix.android.sdk.api.failure.Failure import org.matrix.android.sdk.api.failure.MatrixError import org.matrix.android.sdk.internal.network.executeRequest import org.matrix.android.sdk.internal.session.openid.GetOpenIdTokenTask -import org.matrix.android.sdk.internal.session.widgets.RegisterWidgetResponse import org.matrix.android.sdk.api.session.widgets.WidgetManagementFailure import org.matrix.android.sdk.internal.session.widgets.WidgetsAPI import org.matrix.android.sdk.internal.session.widgets.WidgetsAPIProvider @@ -59,8 +58,8 @@ internal class DefaultGetScalarTokenTask @Inject constructor(private val widgets private suspend fun getNewScalarToken(widgetsAPI: WidgetsAPI, serverUrl: String): String { val openId = getOpenIdTokenTask.execute(Unit) - val registerWidgetResponse = executeRequest<RegisterWidgetResponse>(null) { - apiCall = widgetsAPI.register(openId, WIDGET_API_VERSION) + val registerWidgetResponse = executeRequest(null) { + widgetsAPI.register(openId, WIDGET_API_VERSION) } if (registerWidgetResponse.scalarToken == null) { // Should not happen @@ -72,8 +71,8 @@ internal class DefaultGetScalarTokenTask @Inject constructor(private val widgets private suspend fun validateToken(widgetsAPI: WidgetsAPI, serverUrl: String, scalarToken: String): String { return try { - executeRequest<Unit>(null) { - apiCall = widgetsAPI.validateToken(scalarToken, WIDGET_API_VERSION) + executeRequest(null) { + widgetsAPI.validateToken(scalarToken, WIDGET_API_VERSION) } scalarToken } catch (failure: Throwable) { diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/util/LogUtil.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/util/LogUtil.kt index fe68b49a5c7712a6d85c7375f0e2d958cb71c4db..4656856bf788d50ecda8e47cecad840d65ab0a5f 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/util/LogUtil.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/util/LogUtil.kt @@ -19,15 +19,27 @@ package org.matrix.android.sdk.internal.util import org.matrix.android.sdk.BuildConfig import timber.log.Timber +internal fun <T> Collection<T>.logLimit(maxQuantity: Int = 5): String { + return buildString { + append(size) + append(" item(s)") + if (size > maxQuantity) { + append(", first $maxQuantity items") + } + append(": ") + append(this@logLimit.take(maxQuantity)) + } +} + internal suspend fun <T> logDuration(message: String, block: suspend () -> T): T { - Timber.v("$message -- BEGIN") + Timber.d("$message -- BEGIN") val start = System.currentTimeMillis() val result = logRamUsage(message) { block() } val duration = System.currentTimeMillis() - start - Timber.v("$message -- END duration: $duration ms") + Timber.d("$message -- END duration: $duration ms") return result } @@ -38,12 +50,12 @@ internal suspend fun <T> logRamUsage(message: String, block: suspend () -> T): T runtime.gc() val freeMemoryInMb = runtime.freeMemory() / 1048576L val usedMemInMBStart = runtime.totalMemory() / 1048576L - freeMemoryInMb - Timber.v("$message -- BEGIN (free memory: $freeMemoryInMb MB)") + Timber.d("$message -- BEGIN (free memory: $freeMemoryInMb MB)") val result = block() runtime.gc() val usedMemInMBEnd = (runtime.totalMemory() - runtime.freeMemory()) / 1048576L val usedMemInMBDiff = usedMemInMBEnd - usedMemInMBStart - Timber.v("$message -- END RAM usage: $usedMemInMBDiff MB") + Timber.d("$message -- END RAM usage: $usedMemInMBDiff MB") result } else { block() diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/util/MathUtils.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/util/MathUtils.kt new file mode 100644 index 0000000000000000000000000000000000000000..c9c597e93d159131eac02bc8e4f8ce9d78e1591b --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/util/MathUtils.kt @@ -0,0 +1,44 @@ +/* + * Copyright (c) 2021 The Matrix.org Foundation C.I.C. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.matrix.android.sdk.internal.util + +import kotlin.math.ceil + +internal data class BestChunkSize( + val numberOfChunks: Int, + val chunkSize: Int +) { + fun shouldChunk() = numberOfChunks > 1 +} + +internal fun computeBestChunkSize(listSize: Int, limit: Int): BestChunkSize { + return if (listSize <= limit) { + BestChunkSize( + numberOfChunks = 1, + chunkSize = listSize + ) + } else { + val numberOfChunks = ceil(listSize / limit.toDouble()).toInt() + // Round on next Int + val chunkSize = ceil(listSize / numberOfChunks.toDouble()).toInt() + + BestChunkSize( + numberOfChunks = numberOfChunks, + chunkSize = chunkSize + ) + } +} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/wellknown/GetWellknownTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/wellknown/GetWellknownTask.kt index 3f0e27f4105bb1e5b2bbd3ac023e9ae42f5cddfc..7a9beac8c08e1d8db524a90cc0308ee6054dc967 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/wellknown/GetWellknownTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/wellknown/GetWellknownTask.kt @@ -89,8 +89,8 @@ internal class DefaultGetWellknownTask @Inject constructor( .create(WellKnownAPI::class.java) return try { - val wellKnown = executeRequest<WellKnown>(null) { - apiCall = wellKnownAPI.getWellKnown(domain) + val wellKnown = executeRequest(null) { + wellKnownAPI.getWellKnown(domain) } // Success @@ -140,8 +140,8 @@ internal class DefaultGetWellknownTask @Inject constructor( .create(CapabilitiesAPI::class.java) try { - executeRequest<Unit>(null) { - apiCall = capabilitiesAPI.ping() + executeRequest(null) { + capabilitiesAPI.ping() } } catch (throwable: Throwable) { return WellknownResult.FailError @@ -178,8 +178,8 @@ internal class DefaultGetWellknownTask @Inject constructor( .create(IdentityAuthAPI::class.java) return try { - executeRequest<Unit>(null) { - apiCall = identityPingApi.ping() + executeRequest(null) { + identityPingApi.ping() } true diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/wellknown/WellKnownAPI.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/wellknown/WellKnownAPI.kt index 981d013f49c994d216b2f00de7c13887d9c7a196..428f7f65c075b36760f4f23fb2bb6b9954e88b23 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/wellknown/WellKnownAPI.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/wellknown/WellKnownAPI.kt @@ -16,11 +16,10 @@ package org.matrix.android.sdk.internal.wellknown import org.matrix.android.sdk.api.auth.data.WellKnown -import retrofit2.Call import retrofit2.http.GET import retrofit2.http.Path internal interface WellKnownAPI { @GET("https://{domain}/.well-known/matrix/client") - fun getWellKnown(@Path("domain") domain: String): Call<WellKnown> + suspend fun getWellKnown(@Path("domain") domain: String): WellKnown } diff --git a/matrix-sdk-android/src/main/res/values-fi/strings_sas.xml b/matrix-sdk-android/src/main/res/values-fi/strings_sas.xml index b690fee4ed4fe74d7f208927a370bc2dccc8bfe9..12edb39070eba41111744c38dee06ac1c1ad80b1 100644 --- a/matrix-sdk-android/src/main/res/values-fi/strings_sas.xml +++ b/matrix-sdk-android/src/main/res/values-fi/strings_sas.xml @@ -35,7 +35,7 @@ <string name="verification_emoji_robot">Robotti</string> <string name="verification_emoji_hat">Hattu</string> <string name="verification_emoji_glasses">Silmälasit</string> - <string name="verification_emoji_spanner">Mutteriavain</string> + <string name="verification_emoji_spanner">Kiintoavain</string> <string name="verification_emoji_santa">Joulupukki</string> <string name="verification_emoji_thumbs_up">Peukalo ylös</string> <string name="verification_emoji_umbrella">Sateenvarjo</string> diff --git a/matrix-sdk-android/src/main/res/values-ja/strings_sas.xml b/matrix-sdk-android/src/main/res/values-ja/strings_sas.xml index 618302eb4f9f20c61e078bbcb8fa1d87c0a87ac6..12f90e316d63fcc817be47c8e0755bbfbb1ebf2a 100644 --- a/matrix-sdk-android/src/main/res/values-ja/strings_sas.xml +++ b/matrix-sdk-android/src/main/res/values-ja/strings_sas.xml @@ -3,18 +3,66 @@ <!-- Generated file, do not edit --> <string name="verification_emoji_dog">犬</string> <string name="verification_emoji_cat">猫</string> + <string name="verification_emoji_lion">ライオン</string> <string name="verification_emoji_horse">馬</string> + <string name="verification_emoji_unicorn">ユニコーン</string> + <string name="verification_emoji_pig">ブタ</string> + <string name="verification_emoji_elephant">ゾウ</string> + <string name="verification_emoji_rabbit">ã†ã•ãŽ</string> + <string name="verification_emoji_panda">パンダ</string> + <string name="verification_emoji_rooster">ニワトリ</string> + <string name="verification_emoji_penguin">ペンギン</string> + <string name="verification_emoji_turtle">亀</string> + <string name="verification_emoji_fish">éš</string> <string name="verification_emoji_octopus">ãŸã“</string> + <string name="verification_emoji_butterfly">ã¡ã‚‡ã†ã¡ã‚‡</string> <string name="verification_emoji_flower">花</string> <string name="verification_emoji_tree">木</string> + <string name="verification_emoji_cactus">サボテン</string> <string name="verification_emoji_mushroom">ãã®ã“</string> + <string name="verification_emoji_globe">地çƒ</string> <string name="verification_emoji_moon">月</string> + <string name="verification_emoji_cloud">雲</string> + <string name="verification_emoji_fire">ç‚Ž</string> + <string name="verification_emoji_banana">ãƒãƒŠãƒŠ</string> <string name="verification_emoji_apple">リンゴ</string> + <string name="verification_emoji_strawberry">ã„ã¡ã”</string> + <string name="verification_emoji_corn">ã¨ã†ã‚‚ã‚ã“ã—</string> + <string name="verification_emoji_pizza">ピザ</string> <string name="verification_emoji_cake">ケーã‚</string> + <string name="verification_emoji_heart">ãƒãƒ¼ãƒˆ</string> + <string name="verification_emoji_smiley">スマイル</string> <string name="verification_emoji_robot">ãƒãƒœã¨</string> + <string name="verification_emoji_hat">帽å</string> <string name="verification_emoji_glasses">ã‚ãŒã</string> + <string name="verification_emoji_spanner">スパナ</string> + <string name="verification_emoji_santa">サンタ</string> + <string name="verification_emoji_thumbs_up">ã„ã„ã</string> + <string name="verification_emoji_umbrella">傘</string> + <string name="verification_emoji_hourglass">ç ‚æ™‚è¨ˆ</string> + <string name="verification_emoji_clock">時計</string> + <string name="verification_emoji_gift">ギフト</string> + <string name="verification_emoji_light_bulb">é›»çƒ</string> <string name="verification_emoji_book">本</string> + <string name="verification_emoji_pencil">鉛ç†</string> + <string name="verification_emoji_paperclip">クリップ</string> + <string name="verification_emoji_scissors">ã¯ã•ã¿</string> + <string name="verification_emoji_lock">éŒ å‰</string> + <string name="verification_emoji_key">éµ</string> + <string name="verification_emoji_hammer">金槌</string> <string name="verification_emoji_telephone">電話機</string> + <string name="verification_emoji_flag">æ——</string> <string name="verification_emoji_train">電車</string> <string name="verification_emoji_bicycle">自転車</string> + <string name="verification_emoji_aeroplane">飛行機</string> + <string name="verification_emoji_rocket">ãƒã‚±ãƒƒãƒˆ</string> + <string name="verification_emoji_trophy">トãƒãƒ•ã‚£ãƒ¼</string> + <string name="verification_emoji_ball">ボール</string> + <string name="verification_emoji_guitar">ギター</string> + <string name="verification_emoji_trumpet">トランペット</string> + <string name="verification_emoji_bell">ベル</string> + <string name="verification_emoji_anchor">ã„ã‹ã‚Š</string> + <string name="verification_emoji_headphones">ヘッドホン</string> + <string name="verification_emoji_folder">フォルダ</string> + <string name="verification_emoji_pin">ピン</string> </resources> diff --git a/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/util/MathUtilTest.kt b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/util/MathUtilTest.kt new file mode 100644 index 0000000000000000000000000000000000000000..ade811f9b76aa9b270908c931a5e9377c797e1dc --- /dev/null +++ b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/util/MathUtilTest.kt @@ -0,0 +1,71 @@ +/* + * 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.internal.util + +import org.amshove.kluent.shouldBeEqualTo +import org.amshove.kluent.shouldHaveSize +import org.junit.FixMethodOrder +import org.junit.Test +import org.junit.runners.MethodSorters +import org.matrix.android.sdk.MatrixTest + +@FixMethodOrder(MethodSorters.JVM) +class MathUtilTest : MatrixTest { + + @Test + fun testComputeBestChunkSize0() = doTest(0, 100, 1, 0) + + @Test + fun testComputeBestChunkSize1to99() { + for (i in 1..99) { + doTest(i, 100, 1, i) + } + } + + @Test + fun testComputeBestChunkSize100() = doTest(100, 100, 1, 100) + + @Test + fun testComputeBestChunkSize101() = doTest(101, 100, 2, 51) + + @Test + fun testComputeBestChunkSize199() = doTest(199, 100, 2, 100) + + @Test + fun testComputeBestChunkSize200() = doTest(200, 100, 2, 100) + + @Test + fun testComputeBestChunkSize201() = doTest(201, 100, 3, 67) + + @Test + fun testComputeBestChunkSize240() = doTest(240, 100, 3, 80) + + private fun doTest(listSize: Int, limit: Int, expectedNumberOfChunks: Int, expectedChunkSize: Int) { + val result = computeBestChunkSize(listSize, limit) + + result.numberOfChunks shouldBeEqualTo expectedNumberOfChunks + result.chunkSize shouldBeEqualTo expectedChunkSize + + // Test that the result make sense, when we use chunked() + if (result.chunkSize > 0) { + generateSequence { "a" } + .take(listSize) + .chunked(result.chunkSize) + .shouldHaveSize(result.numberOfChunks) + } + } +}