From 882e9adf50297587eb62028a0787a6f8adde1424 Mon Sep 17 00:00:00 2001 From: Benoit Marty <benoit@matrix.org> Date: Wed, 8 Sep 2021 22:27:38 +0200 Subject: [PATCH] Import from Element 1.2.1 --- matrix-sdk-android/build.gradle | 14 +-- matrix-sdk-android/lint.xml | 34 ------ .../matrix/android/sdk/PermalinkParserTest.kt | 60 ++++++++++ .../android/sdk/common/CommonTestHelper.kt | 2 + .../android/sdk/common/CryptoTestHelper.kt | 2 + .../sdk/internal/crypto/ssss/QuadSTests.kt | 2 + .../timeline/TimelineForwardPaginationTest.kt | 2 +- .../TimelinePreviousLastForwardTest.kt | 4 +- .../sdk/session/space/SpaceCreationTest.kt | 33 +++--- .../sdk/session/space/SpaceHierarchyTest.kt | 9 +- .../android/sdk/api/failure/Extensions.kt | 4 +- .../android/sdk/api/failure/GlobalError.kt | 1 + .../android/sdk/api/failure/MatrixError.kt | 25 ++++ .../sdk/api/pushrules/PushRuleService.kt | 5 +- .../android/sdk/api/pushrules/RuleIds.kt | 5 + .../verification/VerificationService.kt | 2 +- .../api/session/identity/IdentityService.kt | 14 +++ .../api/session/permalinks/PermalinkData.kt | 21 ++++ .../api/session/permalinks/PermalinkParser.kt | 79 +++++++++++-- .../sdk/api/session/room/RoomService.kt | 13 +++ .../sdk/api/session/room/RoomSortOrder.kt | 1 + .../room/model/RoomJoinRulesAllowEntry.kt | 14 ++- .../room/model/RoomJoinRulesContent.kt | 4 +- .../api/session/room/model/SpaceChildInfo.kt | 2 +- .../room/model/message/AudioWaveformInfo.kt | 2 +- .../android/sdk/api/session/space/Space.kt | 6 +- .../api/session/space/SpaceHierarchyData.kt | 28 +++++ .../sdk/api/session/space/SpaceService.kt | 11 +- .../session/space/model/SpaceChildContent.kt | 12 +- .../crypto/CancelGossipRequestWorker.kt | 13 ++- .../crypto/IncomingGossipingRequestManager.kt | 8 +- .../crypto/OutgoingGossipingRequestManager.kt | 9 +- .../crypto/SendGossipRequestWorker.kt | 12 +- .../sdk/internal/crypto/SendGossipWorker.kt | 12 +- .../crypto/store/db/RealmCryptoStore.kt | 6 +- .../internal/crypto/tasks/SendToDeviceTask.kt | 17 ++- .../internal/crypto/util/RequestIdHelper.kt | 23 ++++ .../database/EventInsertLiveObserver.kt | 31 +---- .../database/RealmSessionStoreMigration.kt | 10 +- .../database/mapper/RoomSummaryMapper.kt | 2 +- .../internal/database/model/EventEntity.kt | 10 +- .../database/model/EventInsertEntity.kt | 7 +- .../database/query/EventEntityQueries.kt | 4 +- .../sdk/internal/network/NetworkConstants.kt | 1 + .../internal/network/RetrofitExtensions.kt | 25 ++-- .../internal/query/QueryRoomOrderProcessor.kt | 14 ++- .../homeserver/GetCapabilitiesResult.kt | 2 +- .../identity/DefaultIdentityService.kt | 10 ++ .../internal/session/identity/IdentityAPI.kt | 14 +++ .../identity/Sign3pidInvitationTask.kt | 49 ++++++++ .../identity/model/SignInvitationBody.kt | 29 +++++ .../identity/model/SignInvitationResult.kt | 31 +++++ .../notification/DefaultPushRuleService.kt | 22 +++- .../session/permalinks/ViaParameterFinder.kt | 4 + .../internal/session/pushers/EnabledBody.kt | 25 ++++ .../session/pushers/GetPushersResponse.kt | 2 +- .../internal/session/pushers/PushRulesApi.kt | 2 +- .../session/pushers/RemovePushRuleTask.kt | 5 +- .../pushers/UpdatePushRuleActionsTask.kt | 6 +- .../pushers/UpdatePushRuleEnableStatusTask.kt | 6 +- .../session/room/DefaultRoomService.kt | 7 ++ .../sdk/internal/session/room/RoomAPI.kt | 5 +- .../sdk/internal/session/room/RoomModule.kt | 5 + .../session/room/create/CreateRoomTask.kt | 2 +- .../room/membership/RoomMemberEventHandler.kt | 24 +++- .../room/membership/joining/JoinRoomTask.kt | 11 +- .../SetRoomNotificationStateTask.kt | 2 +- .../relationship/RoomChildRelationInfo.kt | 4 +- .../session/room/send/DefaultSendService.kt | 2 +- .../session/room/send/LocalEchoRepository.kt | 2 +- .../session/room/state/DefaultStateService.kt | 2 +- .../room/summary/RoomSummaryUpdater.kt | 2 +- .../room/timeline/TimelineEventDecryptor.kt | 3 +- .../internal/session/space/DefaultSpace.kt | 67 ++++++----- .../session/space/DefaultSpaceService.kt | 107 ++++++++++-------- .../internal/session/space/JoinSpaceTask.kt | 65 ++++++----- .../session/space/ResolveSpaceInfoTask.kt | 38 ++----- .../sdk/internal/session/space/SpaceApi.kt | 32 +++--- .../space/SpaceChildSummaryResponse.kt | 18 ++- .../session/space/SpaceSummaryParams.kt | 34 ------ .../internal/session/space/SpacesResponse.kt | 5 +- .../session/space/peeking/PeekSpaceTask.kt | 6 +- .../session/space/peeking/SpacePeekResult.kt | 6 +- .../internal/session/sync/RoomSyncHandler.kt | 10 +- .../SyncResponsePostTreatmentAggregator.kt | 2 + ...cResponsePostTreatmentAggregatorHandler.kt | 40 ++++++- .../sync/UserAccountDataSyncHandler.kt | 3 +- .../accountdata/DirectMessagesContent.kt | 9 ++ .../user/accountdata/DirectChatsHelper.kt | 3 +- .../session/widgets/DefaultWidgetService.kt | 2 +- .../internal/task/CoroutineSequencersTest.kt | 3 + 91 files changed, 942 insertions(+), 401 deletions(-) delete mode 100644 matrix-sdk-android/lint.xml create mode 100644 matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/PermalinkParserTest.kt create mode 100644 matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/space/SpaceHierarchyData.kt create mode 100644 matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/util/RequestIdHelper.kt create mode 100644 matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/identity/Sign3pidInvitationTask.kt create mode 100644 matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/identity/model/SignInvitationBody.kt create mode 100644 matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/identity/model/SignInvitationResult.kt create mode 100644 matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/EnabledBody.kt delete mode 100644 matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/space/SpaceSummaryParams.kt diff --git a/matrix-sdk-android/build.gradle b/matrix-sdk-android/build.gradle index 23e087b7..81136f43 100644 --- a/matrix-sdk-android/build.gradle +++ b/matrix-sdk-android/build.gradle @@ -10,7 +10,7 @@ buildscript { mavenCentral() } dependencies { - classpath "io.realm:realm-gradle-plugin:10.6.1" + classpath "io.realm:realm-gradle-plugin:10.8.0" } } @@ -22,7 +22,7 @@ android { minSdkVersion 21 targetSdkVersion 30 versionCode 1 - versionName "1.2.0" + versionName "1.2.1" // Multidex is useful for tests multiDexEnabled true testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" @@ -68,10 +68,6 @@ android { installOptions "-g" } - lintOptions { - lintConfig file("lint.xml") - } - compileOptions { sourceCompatibility JavaVersion.VERSION_11 targetCompatibility JavaVersion.VERSION_11 @@ -113,7 +109,7 @@ dependencies { def lifecycle_version = '2.2.0' def arch_version = '2.1.0' def markwon_version = '3.1.0' - def daggerVersion = '2.38' + def daggerVersion = '2.38.1' def work_version = '2.5.0' def retrofit_version = '2.9.0' @@ -142,7 +138,7 @@ dependencies { implementation "ru.noties.markwon:core:$markwon_version" // Image - implementation 'androidx.exifinterface:exifinterface:1.3.2' + implementation 'androidx.exifinterface:exifinterface:1.3.3' // Database implementation 'com.github.Zhuinden:realm-monarchy:0.7.1' @@ -170,7 +166,7 @@ dependencies { implementation 'com.otaliastudios:transcoder:0.10.3' // Phone number https://github.com/google/libphonenumber - implementation 'com.googlecode.libphonenumber:libphonenumber:8.12.28' + implementation 'com.googlecode.libphonenumber:libphonenumber:8.12.31' testImplementation 'junit:junit:4.13.2' testImplementation 'org.robolectric:robolectric:4.5.1' diff --git a/matrix-sdk-android/lint.xml b/matrix-sdk-android/lint.xml deleted file mode 100644 index 134aba82..00000000 --- a/matrix-sdk-android/lint.xml +++ /dev/null @@ -1,34 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<lint> - <!-- Modify some severity --> - - <!-- Resource --> - <issue id="MissingTranslation" severity="warning" /> - <issue id="TypographyEllipsis" severity="error" /> - <issue id="ImpliedQuantity" severity="warning" /> - - <!-- UX --> - <issue id="ButtonOrder" severity="error" /> - - <!-- Layout --> - <issue id="UnknownIdInLayout" severity="error" /> - <issue id="StringFormatCount" severity="error" /> - <issue id="HardcodedText" severity="error" /> - <issue id="SpUsage" severity="error" /> - <issue id="ObsoleteLayoutParam" severity="error" /> - <issue id="InefficientWeight" severity="error" /> - <issue id="DisableBaselineAlignment" severity="error" /> - <issue id="ScrollViewSize" severity="error" /> - - <!-- RTL --> - <issue id="RtlEnabled" severity="error" /> - <issue id="RtlHardcoded" severity="error" /> - <issue id="RtlSymmetry" severity="error" /> - - <!-- Code --> - <issue id="SetTextI18n" severity="error" /> - <issue id="ViewConstructor" severity="error" /> - <issue id="UseValueOf" severity="error" /> - <issue id="ObsoleteSdkInt" severity="error" /> - -</lint> diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/PermalinkParserTest.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/PermalinkParserTest.kt new file mode 100644 index 00000000..b11a5389 --- /dev/null +++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/PermalinkParserTest.kt @@ -0,0 +1,60 @@ +/* + * Copyright 2021 The Matrix.org Foundation C.I.C. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.matrix.android.sdk + +import org.junit.Assert +import org.junit.FixMethodOrder +import org.junit.Test +import org.junit.runners.MethodSorters +import org.matrix.android.sdk.api.session.permalinks.PermalinkData +import org.matrix.android.sdk.api.session.permalinks.PermalinkParser + +@FixMethodOrder(MethodSorters.JVM) +class PermalinkParserTest { + + @Test + fun testParseEmailInvite() { + val rawInvite = """ + https://app.element.io/#/room/%21MRBNLPtFnMAazZVPMO%3Amatrix.org?email=bob%2Bspace%40example.com&signurl=https%3A%2F%2Fvector.im%2F_matrix%2Fidentity%2Fapi%2Fv1%2Fsign-ed25519%3Ftoken%3DXmOwRZnSFabCRhTywFbJWKXWVNPysOpXIbroMGaUymqkJSvHeVKRsjHajwjCYdBsvGSvHauxbKfJmOxtXldtyLnyBMLKpBQCMzyYggrdapbVIceWZBtmslOQrXLABRoe%26private_key%3DT2gq0c3kJB_8OroXVxl1pBnzHsN7V6Xn4bEBSeW1ep4&room_name=Team2&room_avatar_url=&inviter_name=hiphop5&guest_access_token=&guest_user_id= + """.trimIndent() + .replace("https://app.element.io/#/room/", "https://matrix.to/#/") + + val parsedLink = PermalinkParser.parse(rawInvite) + Assert.assertTrue("Should be parsed as email invite but was ${parsedLink::class.java}", parsedLink is PermalinkData.RoomEmailInviteLink) + parsedLink as PermalinkData.RoomEmailInviteLink + Assert.assertEquals("!MRBNLPtFnMAazZVPMO:matrix.org", parsedLink.roomId) + Assert.assertEquals("XmOwRZnSFabCRhTywFbJWKXWVNPysOpXIbroMGaUymqkJSvHeVKRsjHajwjCYdBsvGSvHauxbKfJmOxtXldtyLnyBMLKpBQCMzyYggrdapbVIceWZBtmslOQrXLABRoe", parsedLink.token) + Assert.assertEquals("vector.im", parsedLink.identityServer) + Assert.assertEquals("Team2", parsedLink.roomName) + Assert.assertEquals("hiphop5", parsedLink.inviterName) + } + + @Test + fun testParseLinkWIthEvent() { + val rawInvite = "https://matrix.to/#/!OGEhHVWSdvArJzumhm:matrix.org/\$xuvJUVDJnwEeVjPx029rAOZ50difpmU_5gZk_T0jGfc?via=matrix.org&via=libera.chat&via=matrix.example.io" + + val parsedLink = PermalinkParser.parse(rawInvite) + Assert.assertTrue("Should be parsed as room link", parsedLink is PermalinkData.RoomLink) + parsedLink as PermalinkData.RoomLink + Assert.assertEquals("!OGEhHVWSdvArJzumhm:matrix.org", parsedLink.roomIdOrAlias) + Assert.assertEquals("\$xuvJUVDJnwEeVjPx029rAOZ50difpmU_5gZk_T0jGfc", parsedLink.eventId) + Assert.assertEquals(3, parsedLink.viaParameters.size) + Assert.assertTrue(parsedLink.viaParameters.contains("matrix.example.io")) + Assert.assertTrue(parsedLink.viaParameters.contains("matrix.org")) + Assert.assertTrue(parsedLink.viaParameters.contains("matrix.example.io")) + } +} diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/common/CommonTestHelper.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/common/CommonTestHelper.kt index 6e07223a..7817351e 100644 --- a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/common/CommonTestHelper.kt +++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/common/CommonTestHelper.kt @@ -91,6 +91,7 @@ class CommonTestHelper(context: Context) { * * @param session the session to sync */ + @Suppress("EXPERIMENTAL_API_USAGE") fun syncSession(session: Session, timeout: Long = TestConstants.timeOutMillis) { val lock = CountDownLatch(1) @@ -327,6 +328,7 @@ class CommonTestHelper(context: Context) { assertTrue(latch.await(timeout ?: TestConstants.timeOutMillis, TimeUnit.MILLISECONDS)) } + @Suppress("EXPERIMENTAL_API_USAGE") fun retryPeriodicallyWithLatch(latch: CountDownLatch, condition: (() -> Boolean)) { GlobalScope.launch { while (true) { 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 da176491..a8cbc160 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 @@ -84,6 +84,7 @@ class CryptoTestHelper(private val mTestHelper: CommonTestHelper) { /** * @return alice and bob sessions */ + @Suppress("EXPERIMENTAL_API_USAGE") fun doE2ETestWithAliceAndBobInARoom(encryptedRoom: Boolean = true): CryptoTestData { val cryptoTestData = doE2ETestWithAliceInARoom(encryptedRoom) val aliceSession = cryptoTestData.firstSession @@ -255,6 +256,7 @@ class CryptoTestHelper(private val mTestHelper: CommonTestHelper) { ) } + @Suppress("EXPERIMENTAL_API_USAGE") fun createDM(alice: Session, bob: Session): String { val roomId = mTestHelper.runBlockingTest { alice.createDirectRoom(bob.myUserId) 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 d14de30c..74855b86 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 @@ -60,6 +60,7 @@ class QuadSTests : InstrumentedTest { } @Test + @Suppress("EXPERIMENTAL_API_USAGE") fun test_Generate4SKey() { val aliceSession = mTestHelper.createAccount(TestConstants.USER_ALICE, SessionTestParams(true)) @@ -275,6 +276,7 @@ class QuadSTests : InstrumentedTest { mTestHelper.signOutAndClose(aliceSession) } + @Suppress("EXPERIMENTAL_API_USAGE") private fun assertAccountData(session: Session, type: String): UserAccountDataEvent { val accountDataLock = CountDownLatch(1) var accountData: UserAccountDataEvent? = null diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/room/timeline/TimelineForwardPaginationTest.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/room/timeline/TimelineForwardPaginationTest.kt index f156a5eb..0fe341ca 100644 --- a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/room/timeline/TimelineForwardPaginationTest.kt +++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/room/timeline/TimelineForwardPaginationTest.kt @@ -139,7 +139,7 @@ class TimelineForwardPaginationTest : InstrumentedTest { // Alice can see the first event of the room (so Back pagination has worked) snapshot.lastOrNull()?.root?.getClearType() == EventType.STATE_ROOM_CREATE // 6 for room creation item (backward pagination), 1 for the context, and 50 for the forward pagination - && snapshot.size == 6 + 1 + 50 + && snapshot.size == 57 // 6 + 1 + 50 } aliceTimeline.addListener(aliceEventsListener) diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/room/timeline/TimelinePreviousLastForwardTest.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/room/timeline/TimelinePreviousLastForwardTest.kt index 9ebac876..03a4d419 100644 --- a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/room/timeline/TimelinePreviousLastForwardTest.kt +++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/room/timeline/TimelinePreviousLastForwardTest.kt @@ -189,7 +189,7 @@ class TimelinePreviousLastForwardTest : InstrumentedTest { Timber.w(" event ${it.root}") } - snapshot.size == 8 + 1 + 35 + snapshot.size == 44 // 8 + 1 + 35 } bobTimeline.addListener(eventsListener) @@ -218,7 +218,7 @@ class TimelinePreviousLastForwardTest : InstrumentedTest { // Bob can see the first event of the room (so Back pagination has worked) snapshot.lastOrNull()?.root?.getClearType() == EventType.STATE_ROOM_CREATE // 8 for room creation item 60 message from Alice - && snapshot.size == 8 + 60 + && snapshot.size == 68 // 8 + 60 && snapshot.checkSendOrder(secondMessage, 30, 0) && snapshot.checkSendOrder(firstMessage, 30, 30) } diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/space/SpaceCreationTest.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/space/SpaceCreationTest.kt index a1744a0d..5911414c 100644 --- a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/space/SpaceCreationTest.kt +++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/space/SpaceCreationTest.kt @@ -28,12 +28,9 @@ 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.query.ActiveSpaceFilter 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.RoomSummaryQueryParams import org.matrix.android.sdk.api.session.room.model.GuestAccess -import org.matrix.android.sdk.api.session.room.model.Membership import org.matrix.android.sdk.api.session.room.model.PowerLevelsContent import org.matrix.android.sdk.api.session.room.model.RoomGuestAccessContent import org.matrix.android.sdk.api.session.room.model.RoomHistoryVisibility @@ -42,7 +39,6 @@ import org.matrix.android.sdk.api.session.room.model.RoomType 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.api.session.room.model.create.RoomCreateContent -import org.matrix.android.sdk.api.session.room.roomSummaryQueryParams import org.matrix.android.sdk.api.session.space.JoinSpaceResult import org.matrix.android.sdk.common.CommonTestHelper import org.matrix.android.sdk.common.SessionTestParams @@ -54,6 +50,7 @@ class SpaceCreationTest : InstrumentedTest { private val commonTestHelper = CommonTestHelper(context()) @Test + @Suppress("EXPERIMENTAL_API_USAGE") fun createSimplePublicSpace() { val session = commonTestHelper.createAccount("Hubble", SessionTestParams(true)) val roomName = "My Space" @@ -137,6 +134,7 @@ class SpaceCreationTest : InstrumentedTest { } @Test + @Suppress("EXPERIMENTAL_API_USAGE") fun testSimplePublicSpaceWithChildren() { val aliceSession = commonTestHelper.createAccount("alice", SessionTestParams(true)) val bobSession = commonTestHelper.createAccount("bob", SessionTestParams(true)) @@ -162,7 +160,7 @@ class SpaceCreationTest : InstrumentedTest { commonTestHelper.waitWithLatch { GlobalScope.launch { - syncedSpace?.addChildren(firstChild!!, listOf(aliceSession.sessionParams.homeServerHost ?: ""), "a", true, suggested = true) + syncedSpace?.addChildren(firstChild!!, listOf(aliceSession.sessionParams.homeServerHost ?: ""), "a", suggested = true) it.countDown() } } @@ -181,7 +179,7 @@ class SpaceCreationTest : InstrumentedTest { commonTestHelper.waitWithLatch { GlobalScope.launch { - syncedSpace?.addChildren(secondChild!!, listOf(aliceSession.sessionParams.homeServerHost ?: ""), "b", false, suggested = true) + syncedSpace?.addChildren(secondChild!!, listOf(aliceSession.sessionParams.homeServerHost ?: ""), "b", suggested = true) it.countDown() } } @@ -202,19 +200,20 @@ class SpaceCreationTest : InstrumentedTest { assertEquals("Room name should be set", roomName, spaceBobPov?.asRoom()?.roomSummary()?.name) assertEquals("Room topic should be set", topic, spaceBobPov?.asRoom()?.roomSummary()?.topic) + // /!\ AUTO_JOIN has been descoped // check if bob has joined automatically the first room - val bobMembershipFirstRoom = bobSession.getRoomSummary(firstChild!!)?.membership - assertEquals("Bob should have joined this room", Membership.JOIN, bobMembershipFirstRoom) - RoomSummaryQueryParams.Builder() - - val childCount = bobSession.getRoomSummaries( - roomSummaryQueryParams { - activeSpaceFilter = ActiveSpaceFilter.ActiveSpace(spaceId) - } - ).size - - assertEquals("Unexpected number of joined children", 1, childCount) +// val bobMembershipFirstRoom = bobSession.getRoomSummary(firstChild!!)?.membership +// assertEquals("Bob should have joined this room", Membership.JOIN, bobMembershipFirstRoom) +// RoomSummaryQueryParams.Builder() +// +// val childCount = bobSession.getRoomSummaries( +// roomSummaryQueryParams { +// activeSpaceFilter = ActiveSpaceFilter.ActiveSpace(spaceId) +// } +// ).size +// +// assertEquals("Unexpected number of joined children", 1, childCount) commonTestHelper.signOutAndClose(aliceSession) commonTestHelper.signOutAndClose(bobSession) diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/space/SpaceHierarchyTest.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/space/SpaceHierarchyTest.kt index 521b5805..301cdea4 100644 --- a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/space/SpaceHierarchyTest.kt +++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/space/SpaceHierarchyTest.kt @@ -47,6 +47,7 @@ class SpaceHierarchyTest : InstrumentedTest { private val commonTestHelper = CommonTestHelper(context()) @Test + @Suppress("EXPERIMENTAL_API_USAGE") fun createCanonicalChildRelation() { val session = commonTestHelper.createAccount("John", SessionTestParams(true)) val spaceName = "My Space" @@ -171,6 +172,7 @@ class SpaceHierarchyTest : InstrumentedTest { // } @Test + @Suppress("EXPERIMENTAL_API_USAGE") fun testFilteringBySpace() { val session = commonTestHelper.createAccount("John", SessionTestParams(true)) @@ -179,7 +181,7 @@ class SpaceHierarchyTest : InstrumentedTest { Triple("A2", true, true) )) - val spaceBInfo = createPublicSpace(session, "SpaceB", listOf( + /* val spaceBInfo = */ createPublicSpace(session, "SpaceB", listOf( Triple("B1", true /*auto-join*/, true/*canonical*/), Triple("B2", true, true), Triple("B3", true, true) @@ -254,6 +256,7 @@ class SpaceHierarchyTest : InstrumentedTest { } @Test + @Suppress("EXPERIMENTAL_API_USAGE") fun testBreakCycle() { val session = commonTestHelper.createAccount("John", SessionTestParams(true)) @@ -301,6 +304,7 @@ class SpaceHierarchyTest : InstrumentedTest { } @Test + @Suppress("EXPERIMENTAL_API_USAGE") fun testLiveFlatChildren() { val session = commonTestHelper.createAccount("John", SessionTestParams(true)) @@ -389,6 +393,7 @@ class SpaceHierarchyTest : InstrumentedTest { val roomIds: List<String> ) + @Suppress("EXPERIMENTAL_API_USAGE") private fun createPublicSpace(session: Session, spaceName: String, childInfo: List<Triple<String, Boolean, Boolean?>> @@ -433,7 +438,7 @@ class SpaceHierarchyTest : InstrumentedTest { fun testRootSpaces() { val session = commonTestHelper.createAccount("John", SessionTestParams(true)) - val spaceAInfo = createPublicSpace(session, "SpaceA", listOf( + /* val spaceAInfo = */ createPublicSpace(session, "SpaceA", listOf( Triple("A1", true /*auto-join*/, true/*canonical*/), Triple("A2", true, true) )) 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 0ba61e58..3149a021 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 @@ -29,7 +29,9 @@ fun Throwable.is401() = fun Throwable.isTokenError() = this is Failure.ServerError - && (error.code == MatrixError.M_UNKNOWN_TOKEN || error.code == MatrixError.M_MISSING_TOKEN) + && (error.code == MatrixError.M_UNKNOWN_TOKEN + || error.code == MatrixError.M_MISSING_TOKEN + || error.code == MatrixError.ORG_MATRIX_EXPIRED_ACCOUNT) fun Throwable.shouldBeRetried(): Boolean { return this is Failure.NetworkConnection diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/failure/GlobalError.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/failure/GlobalError.kt index ef770ea1..50c84da0 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/failure/GlobalError.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/failure/GlobalError.kt @@ -23,4 +23,5 @@ sealed class GlobalError { data class InvalidToken(val softLogout: Boolean) : GlobalError() data class ConsentNotGivenError(val consentUri: String) : GlobalError() data class CertificateError(val fingerprint: Fingerprint) : GlobalError() + object ExpiredAccount : GlobalError() } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/failure/MatrixError.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/failure/MatrixError.kt index 73b0fe0a..1bc86361 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/failure/MatrixError.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/failure/MatrixError.kt @@ -182,6 +182,24 @@ data class MatrixError( /** (Not documented yet) */ const val M_WEAK_PASSWORD = "M_WEAK_PASSWORD" + /** The provided password's length is shorter than the minimum length required by the server. */ + const val M_PASSWORD_TOO_SHORT = "M_PASSWORD_TOO_SHORT" + + /** The password doesn't contain any digit but the server requires at least one. */ + const val M_PASSWORD_NO_DIGIT = "M_PASSWORD_NO_DIGIT" + + /** The password doesn't contain any uppercase letter but the server requires at least one. */ + const val M_PASSWORD_NO_UPPERCASE = "M_PASSWORD_NO_UPPERCASE" + + /** The password doesn't contain any lowercase letter but the server requires at least one. */ + const val M_PASSWORD_NO_LOWERCASE = "M_PASSWORD_NO_LOWERCASE" + + /** The password doesn't contain any symbol but the server requires at least one. */ + const val M_PASSWORD_NO_SYMBOL = "M_PASSWORD_NO_SYMBOL" + + /** The password was found in a dictionary, and is not acceptable. */ + const val M_PASSWORD_IN_DICTIONARY = "M_PASSWORD_IN_DICTIONARY" + const val M_TERMS_NOT_SIGNED = "M_TERMS_NOT_SIGNED" // For identity service @@ -189,5 +207,12 @@ data class MatrixError( // Possible value for "limit_type" const val LIMIT_TYPE_MAU = "monthly_active_user" + + /** + * The user account has expired. It has to be renewed by clicking on an email or by sending a renewal token. + * + * More documentation can be found in the dedicated Synapse plugin module repository: https://github.com/matrix-org/synapse-email-account-validity + */ + const val ORG_MATRIX_EXPIRED_ACCOUNT = "ORG_MATRIX_EXPIRED_ACCOUNT" } } 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 45343686..1d0acf38 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 @@ -15,6 +15,7 @@ */ package org.matrix.android.sdk.api.pushrules +import androidx.lifecycle.LiveData import org.matrix.android.sdk.api.pushrules.rest.PushRule import org.matrix.android.sdk.api.pushrules.rest.RuleSet import org.matrix.android.sdk.api.session.events.model.Event @@ -39,7 +40,7 @@ interface PushRuleService { suspend fun updatePushRuleActions(kind: RuleKind, ruleId: String, enable: Boolean, actions: List<Action>?) - suspend fun removePushRule(kind: RuleKind, pushRule: PushRule) + suspend fun removePushRule(kind: RuleKind, ruleId: String) fun addPushRuleListener(listener: PushRuleListener) @@ -56,4 +57,6 @@ interface PushRuleService { fun onEventRedacted(redactedEventId: String) fun batchFinish() } + + fun getKeywords(): LiveData<Set<String>> } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/pushrules/RuleIds.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/pushrules/RuleIds.kt index 4c01588b..5b14e97d 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/pushrules/RuleIds.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/pushrules/RuleIds.kt @@ -35,6 +35,11 @@ object RuleIds { // Default Content Rules const val RULE_ID_CONTAIN_USER_NAME = ".m.rule.contains_user_name" + // The keywords rule id is not a "real" id in that it does not exist server-side. + // It is used client-side as a placeholder for rendering the keyword push rule setting + // alongside the others. A similar approach and naming is used on Web and iOS. + const val RULE_ID_KEYWORDS = "_keywords" + // Default Underride Rules const val RULE_ID_CALL = ".m.rule.call" const val RULE_ID_ONE_TO_ONE_ENCRYPTED_ROOM = ".m.rule.encrypted_room_one_to_one" 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 54a1e896..b9d0c0ad 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 @@ -52,7 +52,7 @@ interface VerificationService { transactionId: String?): String? /** - * Request a key verification from another user using toDevice events. + * Request key verification with another user via room events (instead of the to-device API) */ fun requestKeyVerificationInDMs(methods: List<VerificationMethod>, otherUserId: 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 ae546b6c..1485ec47 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,6 +16,8 @@ package org.matrix.android.sdk.api.session.identity +import org.matrix.android.sdk.internal.session.identity.model.SignInvitationResult + /** * Provides access to the identity server configuration and services identity server can provide */ @@ -121,6 +123,18 @@ interface IdentityService { */ suspend fun getShareStatus(threePids: List<ThreePid>): Map<ThreePid, SharedState> + /** + * When one performs a 3pid invite and the third party identifier is unknown, the home server + * will store the invitation in the Identity server and store some information in the room state membership event. + * The email invite will contains the token and secret that can be used to claim the stored invitation + * + * To aid clients who may not be able to perform crypto themselves, + * the identity server offers some crypto functionality to help in accepting invitations. + * This is less secure than the client doing it itself, but may be useful where this isn't possible. + */ + suspend fun sign3pidInvitation(identiyServer: String, token: String, secret: String) : SignInvitationResult + fun addListener(listener: IdentityServiceListener) + fun removeListener(listener: IdentityServiceListener) } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/permalinks/PermalinkData.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/permalinks/PermalinkData.kt index b3fbdcf1..85291cf0 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/permalinks/PermalinkData.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/permalinks/PermalinkData.kt @@ -17,6 +17,8 @@ package org.matrix.android.sdk.api.session.permalinks import android.net.Uri +import android.os.Parcelable +import kotlinx.parcelize.Parcelize /** * This sealed class represents all the permalink cases. @@ -31,6 +33,25 @@ sealed class PermalinkData { val viaParameters: List<String> ) : PermalinkData() + /** + * &room_name=Team2 + &room_avatar_url=mxc: + &inviter_name=bob + */ + @Parcelize + data class RoomEmailInviteLink( + val roomId: String, + val email: String, + val signUrl: String, + val roomName: String?, + val roomAvatarUrl: String?, + val inviterName: String?, + val identityServer: String, + val token: String, + val privateKey: String, + val roomType: String? + ) : PermalinkData(), Parcelable + data class UserLink(val userId: String) : PermalinkData() data class GroupLink(val groupId: String) : PermalinkData() diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/permalinks/PermalinkParser.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/permalinks/PermalinkParser.kt index 347a3bb5..005a2eda 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/permalinks/PermalinkParser.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/permalinks/PermalinkParser.kt @@ -19,14 +19,18 @@ package org.matrix.android.sdk.api.session.permalinks import android.net.Uri import android.net.UrlQuerySanitizer import org.matrix.android.sdk.api.MatrixPatterns +import timber.log.Timber +import java.net.URLDecoder /** - * This class turns an uri to a [PermalinkData] + * This class turns a uri to a [PermalinkData] + * element-based domains (e.g. https://app.element.io/#/user/@chagai95:matrix.org) permalinks + * or matrix.to permalinks (e.g. https://matrix.to/#/@chagai95:matrix.org) */ object PermalinkParser { /** - * Turns an uri string to a [PermalinkData] + * Turns a uri string to a [PermalinkData] */ fun parse(uriString: String): PermalinkData { val uri = Uri.parse(uriString) @@ -34,13 +38,16 @@ object PermalinkParser { } /** - * Turns an uri to a [PermalinkData] + * Turns a uri to a [PermalinkData] + * https://github.com/matrix-org/matrix-doc/blob/master/proposals/1704-matrix.to-permalinks.md */ fun parse(uri: Uri): PermalinkData { if (!uri.toString().startsWith(PermalinkService.MATRIX_TO_URL_BASE)) { return PermalinkData.FallbackLink(uri) } - val fragment = uri.fragment + // We can't use uri.fragment as it is decoding to early and it will break the parsing + // of parameters that represents url (like signurl) + val fragment = uri.toString().substringAfter("#") // uri.fragment if (fragment.isNullOrEmpty()) { return PermalinkData.FallbackLink(uri) } @@ -51,21 +58,23 @@ object PermalinkParser { val params = safeFragment .split(MatrixPatterns.SEP_REGEX) .filter { it.isNotEmpty() } + .map { URLDecoder.decode(it, "UTF-8") } .take(2) - val identifier = params.getOrNull(0) + // the element-based domain permalinks (e.g. https://app.element.io/#/user/@chagai95:matrix.org) don't have the + // mxid in the first param (like matrix.to does - https://matrix.to/#/@chagai95:matrix.org) but rather in the second after /user/ so /user/mxid + var identifier = params.getOrNull(0) + if (identifier.equals("user")) { + identifier = params.getOrNull(1) + } + val extraParameter = params.getOrNull(1) return when { identifier.isNullOrEmpty() -> PermalinkData.FallbackLink(uri) MatrixPatterns.isUserId(identifier) -> PermalinkData.UserLink(userId = identifier) MatrixPatterns.isGroupId(identifier) -> PermalinkData.GroupLink(groupId = identifier) MatrixPatterns.isRoomId(identifier) -> { - PermalinkData.RoomLink( - roomIdOrAlias = identifier, - isRoomAlias = false, - eventId = extraParameter.takeIf { !it.isNullOrEmpty() && MatrixPatterns.isEventId(it) }, - viaParameters = viaQueryParameters - ) + handleRoomIdCase(fragment, identifier, uri, extraParameter, viaQueryParameters) } MatrixPatterns.isRoomAlias(identifier) -> { PermalinkData.RoomLink( @@ -79,13 +88,59 @@ object PermalinkParser { } } + private fun handleRoomIdCase(fragment: String, identifier: String, uri: Uri, extraParameter: String?, viaQueryParameters: List<String>): PermalinkData { + // Can't rely on built in parsing because it's messing around the signurl + val paramList = safeExtractParams(fragment) + val signUrl = paramList.firstOrNull { it.first == "signurl" }?.second + val email = paramList.firstOrNull { it.first == "email" }?.second + return if (signUrl.isNullOrEmpty().not() && email.isNullOrEmpty().not()) { + try { + val signValidUri = Uri.parse(signUrl) + val identityServerHost = signValidUri.authority ?: throw IllegalArgumentException() + val token = signValidUri.getQueryParameter("token") ?: throw IllegalArgumentException() + val privateKey = signValidUri.getQueryParameter("private_key") ?: throw IllegalArgumentException() + PermalinkData.RoomEmailInviteLink( + roomId = identifier, + email = email!!, + signUrl = signUrl!!, + roomName = paramList.firstOrNull { it.first == "room_name" }?.second, + inviterName = paramList.firstOrNull { it.first == "inviter_name" }?.second, + roomAvatarUrl = paramList.firstOrNull { it.first == "room_avatar_url" }?.second, + roomType = paramList.firstOrNull { it.first == "room_type" }?.second, + identityServer = identityServerHost, + token = token, + privateKey = privateKey + ) + } catch (failure: Throwable) { + Timber.i("## Permalink: Failed to parse permalink $signUrl") + PermalinkData.FallbackLink(uri) + } + } else { + PermalinkData.RoomLink( + roomIdOrAlias = identifier, + isRoomAlias = false, + eventId = extraParameter.takeIf { !it.isNullOrEmpty() && MatrixPatterns.isEventId(it) }, + viaParameters = viaQueryParameters + ) + } + } + + private fun safeExtractParams(fragment: String) = fragment.substringAfter("?").split('&').mapNotNull { + val splitNameValue = it.split("=") + if (splitNameValue.size == 2) { + Pair(splitNameValue[0], URLDecoder.decode(splitNameValue[1], "UTF-8")) + } else null + } + private fun String.getViaParameters(): List<String> { return UrlQuerySanitizer(this) .parameterList .filter { it.mParameter == "via" }.map { - it.mValue + it.mValue.let { + URLDecoder.decode(it, "UTF-8") + } } } } 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 b7377df1..5d26b213 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 @@ -27,6 +27,7 @@ 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.Optional +import org.matrix.android.sdk.internal.session.identity.model.SignInvitationResult import org.matrix.android.sdk.internal.session.room.alias.RoomAliasDescription /** @@ -63,6 +64,18 @@ interface RoomService { reason: String? = null, viaServers: List<String> = emptyList()) + /** + * @param roomId the roomId of the room to join + * @param reason optional reason for joining the room + * @param thirdPartySigned A signature of an m.third_party_invite token to prove that this user owns a third party identity + * which has been invited to the room. + */ + suspend fun joinRoom( + roomId: String, + reason: String? = null, + thirdPartySigned: SignInvitationResult + ) + /** * Get a room from a roomId * @param roomId the roomId to look for. diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/RoomSortOrder.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/RoomSortOrder.kt index 36da2425..e721abd6 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/RoomSortOrder.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/RoomSortOrder.kt @@ -19,5 +19,6 @@ package org.matrix.android.sdk.api.session.room enum class RoomSortOrder { NAME, ACTIVITY, + PRIORITY_AND_ACTIVITY, NONE } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/RoomJoinRulesAllowEntry.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/RoomJoinRulesAllowEntry.kt index 7b87bc34..16298b8b 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/RoomJoinRulesAllowEntry.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/RoomJoinRulesAllowEntry.kt @@ -23,11 +23,15 @@ import com.squareup.moshi.JsonClass @JsonClass(generateAdapter = true) data class RoomJoinRulesAllowEntry( /** - * space: The room ID of the space to check the membership of. + * The room ID to check the membership of. */ - @Json(name = "space") val spaceID: String, + @Json(name = "room_id") val roomId: String?, /** - * via: A list of servers which may be used to peek for membership of the space. + * "m.room_membership" to describe that we are allowing access via room membership. Future MSCs may define other types. */ - @Json(name = "via") val via: List<String> -) + @Json(name = "type") val type: String? +) { + companion object { + fun restrictedToRoom(roomId: String) = RoomJoinRulesAllowEntry(roomId, "m.room_membership") + } +} 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 33f402ca..871b299f 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 @@ -29,7 +29,9 @@ import timber.log.Timber data class RoomJoinRulesContent( @Json(name = "join_rule") val _joinRules: String? = null, /** - * If the allow key is an empty list (or not a list at all), then the room reverts to standard public join rules + * If the allow key is an empty list (or not a list at all), + * then no users are allowed to join without an invite. + * Each entry is expected to be an object with the following keys: */ @Json(name = "allow") val allowList: List<RoomJoinRulesAllowEntry>? = null ) { diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/SpaceChildInfo.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/SpaceChildInfo.kt index 8cd2a053..7d3109fb 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/SpaceChildInfo.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/SpaceChildInfo.kt @@ -27,7 +27,7 @@ data class SpaceChildInfo( val avatarUrl: String?, val order: String?, val activeMemberCount: Int?, - val autoJoin: Boolean, +// val autoJoin: Boolean, val viaServers: List<String>, val parentRoomId: String?, val suggested: Boolean?, diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/AudioWaveformInfo.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/AudioWaveformInfo.kt index d576f105..aa05eb71 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/AudioWaveformInfo.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/AudioWaveformInfo.kt @@ -32,5 +32,5 @@ data class AudioWaveformInfo( * List of integers between zero and 1024, inclusive. */ @Json(name = "waveform") - val waveform: List<Int>? = null + val waveform: List<Int?>? = null ) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/space/Space.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/space/Space.kt index 3bae6126..207050be 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/space/Space.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/space/Space.kt @@ -36,7 +36,7 @@ interface Space { suspend fun addChildren(roomId: String, viaServers: List<String>?, order: String?, - autoJoin: Boolean = false, +// autoJoin: Boolean = false, suggested: Boolean? = false) fun getChildInfo(roomId: String): SpaceChildContent? @@ -46,8 +46,8 @@ interface Space { @Throws suspend fun setChildrenOrder(roomId: String, order: String?) - @Throws - suspend fun setChildrenAutoJoin(roomId: String, autoJoin: Boolean) +// @Throws +// suspend fun setChildrenAutoJoin(roomId: String, autoJoin: Boolean) @Throws suspend fun setChildrenSuggested(roomId: String, suggested: Boolean) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/space/SpaceHierarchyData.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/space/SpaceHierarchyData.kt new file mode 100644 index 00000000..ecc3eb52 --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/space/SpaceHierarchyData.kt @@ -0,0 +1,28 @@ +/* + * Copyright 2021 The Matrix.org Foundation C.I.C. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.matrix.android.sdk.api.session.space + +import org.matrix.android.sdk.api.session.events.model.Event +import org.matrix.android.sdk.api.session.room.model.RoomSummary +import org.matrix.android.sdk.api.session.room.model.SpaceChildInfo + +data class SpaceHierarchyData( + val rootSummary: RoomSummary, + val children: List<SpaceChildInfo>, + val childrenState: List<Event>, + val nextToken: String? = null +) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/space/SpaceService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/space/SpaceService.kt index e5288e40..bcc36b57 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/space/SpaceService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/space/SpaceService.kt @@ -18,9 +18,9 @@ package org.matrix.android.sdk.api.session.space import android.net.Uri import androidx.lifecycle.LiveData +import org.matrix.android.sdk.api.session.events.model.Event import org.matrix.android.sdk.api.session.room.RoomSummaryQueryParams import org.matrix.android.sdk.api.session.room.model.RoomSummary -import org.matrix.android.sdk.api.session.room.model.SpaceChildInfo import org.matrix.android.sdk.internal.session.space.peeking.SpacePeekResult typealias SpaceSummaryQueryParams = RoomSummaryQueryParams @@ -58,10 +58,17 @@ interface SpaceService { /** * Get's information of a space by querying the server + * @param suggestedOnly If true, return only child events and rooms where the m.space.child event has suggested: true. + * @param limit a client-defined limit to the maximum number of rooms to return per page. Must be a non-negative integer. + * @param from: Optional. Pagination token given to retrieve the next set of rooms. Note that if a pagination token is provided, + * then the parameters given for suggested_only and max_depth must be the same. */ suspend fun querySpaceChildren(spaceId: String, suggestedOnly: Boolean? = null, - autoJoinedOnly: Boolean? = null): Pair<RoomSummary, List<SpaceChildInfo>> + limit: Int? = null, + from: String? = null, + // when paginating, pass back the m.space.child state events + knownStateList: List<Event>? = null): SpaceHierarchyData /** * Get a live list of space summaries. This list is refreshed as soon as the data changes. diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/space/model/SpaceChildContent.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/space/model/SpaceChildContent.kt index 0c33cfa1..b55f90ce 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/space/model/SpaceChildContent.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/space/model/SpaceChildContent.kt @@ -40,12 +40,12 @@ data class SpaceChildContent( * or consist of more than 50 characters, are forbidden and should be ignored if received.) */ @Json(name = "order") val order: String? = null, - /** - * The auto_join flag on a child listing allows a space admin to list the sub-spaces and rooms in that space which should - * be automatically joined by members of that space. - * (This is not a force-join, which are descoped for a future MSC; the user can subsequently part these room if they desire.) - */ - @Json(name = "auto_join") val autoJoin: Boolean? = false, +// /** +// * The auto_join flag on a child listing allows a space admin to list the sub-spaces and rooms in that space which should +// * be automatically joined by members of that space. +// * (This is not a force-join, which are descoped for a future MSC; the user can subsequently part these room if they desire.) +// */ +// @Json(name = "auto_join") val autoJoin: Boolean? = false, /** * If `suggested` is set to `true`, that indicates that the child should be advertised to diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/CancelGossipRequestWorker.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/CancelGossipRequestWorker.kt index 7e92ff3e..0ec020bc 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/CancelGossipRequestWorker.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/CancelGossipRequestWorker.kt @@ -23,12 +23,12 @@ import org.matrix.android.sdk.api.auth.data.Credentials import org.matrix.android.sdk.api.failure.shouldBeRetried 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.LocalEcho import org.matrix.android.sdk.api.session.events.model.toContent import org.matrix.android.sdk.internal.crypto.model.MXUsersDevicesMap import org.matrix.android.sdk.internal.crypto.model.rest.ShareRequestCancellation import org.matrix.android.sdk.internal.crypto.store.IMXCryptoStore import org.matrix.android.sdk.internal.crypto.tasks.SendToDeviceTask +import org.matrix.android.sdk.internal.crypto.tasks.createUniqueTxnId import org.matrix.android.sdk.internal.session.SessionComponent import org.matrix.android.sdk.internal.worker.SessionSafeCoroutineWorker import org.matrix.android.sdk.internal.worker.SessionWorkerParams @@ -43,6 +43,9 @@ internal class CancelGossipRequestWorker(context: Context, override val sessionId: String, val requestId: String, val recipients: Map<String, List<String>>, + // The txnId for the sendToDevice request. Nullable for compatibility reasons, but MUST always be provided + // to use the same value if this worker is retried. + val txnId: String? = null, override val lastFailureMessage: String? = null ) : SessionWorkerParams { companion object { @@ -51,6 +54,7 @@ internal class CancelGossipRequestWorker(context: Context, sessionId = sessionId, requestId = request.requestId, recipients = request.recipients, + txnId = createUniqueTxnId(), lastFailureMessage = null ) } @@ -66,7 +70,10 @@ internal class CancelGossipRequestWorker(context: Context, } override suspend fun doSafeWork(params: Params): Result { - val localId = LocalEcho.createLocalEchoId() + // params.txnId should be provided in all cases now. But Params can be deserialized by + // the WorkManager from data serialized in a previous version of the application, so without the txnId field. + // So if not present, we create a txnId + val txnId = params.txnId ?: createUniqueTxnId() val contentMap = MXUsersDevicesMap<Any>() val toDeviceContent = ShareRequestCancellation( requestingDeviceId = credentials.deviceId, @@ -92,7 +99,7 @@ internal class CancelGossipRequestWorker(context: Context, SendToDeviceTask.Params( eventType = EventType.ROOM_KEY_REQUEST, contentMap = contentMap, - transactionId = localId + transactionId = txnId ) ) cryptoStore.updateOutgoingGossipingRequestState(params.requestId, OutgoingGossipingRequestState.CANCELLED) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/IncomingGossipingRequestManager.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/IncomingGossipingRequestManager.kt index 4a0a274f..e8640d50 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/IncomingGossipingRequestManager.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/IncomingGossipingRequestManager.kt @@ -35,6 +35,7 @@ import org.matrix.android.sdk.internal.crypto.model.rest.GossipingDefaultContent import org.matrix.android.sdk.internal.crypto.model.rest.GossipingToDeviceObject import org.matrix.android.sdk.internal.crypto.model.rest.RoomKeyRequestBody import org.matrix.android.sdk.internal.crypto.store.IMXCryptoStore +import org.matrix.android.sdk.internal.crypto.tasks.createUniqueTxnId import org.matrix.android.sdk.internal.di.SessionId import org.matrix.android.sdk.internal.session.SessionScope import org.matrix.android.sdk.internal.util.MatrixCoroutineDispatchers @@ -356,7 +357,8 @@ internal class IncomingGossipingRequestManager @Inject constructor( secretValue = secretValue, requestUserId = request.userId, requestDeviceId = request.deviceId, - requestId = request.requestId + requestId = request.requestId, + txnId = createUniqueTxnId() ) cryptoStore.updateGossipingRequestState(request, GossipingRequestState.ACCEPTING) @@ -376,13 +378,13 @@ internal class IncomingGossipingRequestManager @Inject constructor( } request.share = { secretValue -> - val params = SendGossipWorker.Params( sessionId = userId, secretValue = secretValue, requestUserId = request.userId, requestDeviceId = request.deviceId, - requestId = request.requestId + requestId = request.requestId, + txnId = createUniqueTxnId() ) cryptoStore.updateGossipingRequestState(request, GossipingRequestState.ACCEPTING) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/OutgoingGossipingRequestManager.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/OutgoingGossipingRequestManager.kt index c86f2be0..ccdb5ab1 100755 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/OutgoingGossipingRequestManager.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/OutgoingGossipingRequestManager.kt @@ -16,7 +16,6 @@ package org.matrix.android.sdk.internal.crypto -import org.matrix.android.sdk.api.session.events.model.LocalEcho import org.matrix.android.sdk.internal.crypto.model.rest.RoomKeyRequestBody import org.matrix.android.sdk.internal.crypto.store.IMXCryptoStore import org.matrix.android.sdk.internal.di.SessionId @@ -26,6 +25,8 @@ import org.matrix.android.sdk.internal.worker.WorkerParamsFactory import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.delay import kotlinx.coroutines.launch +import org.matrix.android.sdk.internal.crypto.tasks.createUniqueTxnId +import org.matrix.android.sdk.internal.crypto.util.RequestIdHelper import timber.log.Timber import javax.inject.Inject @@ -131,7 +132,8 @@ internal class OutgoingGossipingRequestManager @Inject constructor( val params = SendGossipRequestWorker.Params( sessionId = sessionId, keyShareRequest = request as? OutgoingRoomKeyRequest, - secretShareRequest = request as? OutgoingSecretRequest + secretShareRequest = request as? OutgoingSecretRequest, + txnId = createUniqueTxnId() ) cryptoStore.updateOutgoingGossipingRequestState(request.requestId, OutgoingGossipingRequestState.SENDING) val workRequest = gossipingWorkManager.createWork<SendGossipRequestWorker>(WorkerParamsFactory.toData(params), true) @@ -154,7 +156,8 @@ internal class OutgoingGossipingRequestManager @Inject constructor( if (resend) { val reSendParams = SendGossipRequestWorker.Params( sessionId = sessionId, - keyShareRequest = request.copy(requestId = LocalEcho.createLocalEchoId()) + keyShareRequest = request.copy(requestId = RequestIdHelper.createUniqueRequestId()), + txnId = createUniqueTxnId() ) val reSendWorkRequest = gossipingWorkManager.createWork<SendGossipRequestWorker>(WorkerParamsFactory.toData(reSendParams), true) gossipingWorkManager.postWork(reSendWorkRequest) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/SendGossipRequestWorker.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/SendGossipRequestWorker.kt index e8d567b9..2e26720a 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/SendGossipRequestWorker.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/SendGossipRequestWorker.kt @@ -23,7 +23,6 @@ import org.matrix.android.sdk.api.auth.data.Credentials import org.matrix.android.sdk.api.failure.shouldBeRetried 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.LocalEcho import org.matrix.android.sdk.api.session.events.model.toContent import org.matrix.android.sdk.internal.crypto.model.MXUsersDevicesMap import org.matrix.android.sdk.internal.crypto.model.rest.GossipingToDeviceObject @@ -31,6 +30,7 @@ import org.matrix.android.sdk.internal.crypto.model.rest.RoomKeyShareRequest import org.matrix.android.sdk.internal.crypto.model.rest.SecretShareRequest import org.matrix.android.sdk.internal.crypto.store.IMXCryptoStore import org.matrix.android.sdk.internal.crypto.tasks.SendToDeviceTask +import org.matrix.android.sdk.internal.crypto.tasks.createUniqueTxnId import org.matrix.android.sdk.internal.session.SessionComponent import org.matrix.android.sdk.internal.worker.SessionSafeCoroutineWorker import org.matrix.android.sdk.internal.worker.SessionWorkerParams @@ -46,6 +46,9 @@ internal class SendGossipRequestWorker(context: Context, override val sessionId: String, val keyShareRequest: OutgoingRoomKeyRequest? = null, val secretShareRequest: OutgoingSecretRequest? = null, + // The txnId for the sendToDevice request. Nullable for compatibility reasons, but MUST always be provided + // to use the same value if this worker is retried. + val txnId: String? = null, override val lastFailureMessage: String? = null ) : SessionWorkerParams @@ -58,7 +61,10 @@ internal class SendGossipRequestWorker(context: Context, } override suspend fun doSafeWork(params: Params): Result { - val localId = LocalEcho.createLocalEchoId() + // params.txnId should be provided in all cases now. But Params can be deserialized by + // the WorkManager from data serialized in a previous version of the application, so without the txnId field. + // So if not present, we create a txnId + val txnId = params.txnId ?: createUniqueTxnId() val contentMap = MXUsersDevicesMap<Any>() val eventType: String val requestId: String @@ -122,7 +128,7 @@ internal class SendGossipRequestWorker(context: Context, SendToDeviceTask.Params( eventType = eventType, contentMap = contentMap, - transactionId = localId + transactionId = txnId ) ) cryptoStore.updateOutgoingGossipingRequestState(requestId, OutgoingGossipingRequestState.SENT) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/SendGossipWorker.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/SendGossipWorker.kt index 8c680570..c5c6d26f 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/SendGossipWorker.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/SendGossipWorker.kt @@ -23,7 +23,6 @@ import org.matrix.android.sdk.api.auth.data.Credentials import org.matrix.android.sdk.api.failure.shouldBeRetried 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.LocalEcho import org.matrix.android.sdk.api.session.events.model.toContent import org.matrix.android.sdk.internal.crypto.actions.EnsureOlmSessionsForDevicesAction import org.matrix.android.sdk.internal.crypto.actions.MessageEncrypter @@ -31,6 +30,7 @@ import org.matrix.android.sdk.internal.crypto.model.MXUsersDevicesMap import org.matrix.android.sdk.internal.crypto.model.event.SecretSendEventContent import org.matrix.android.sdk.internal.crypto.store.IMXCryptoStore import org.matrix.android.sdk.internal.crypto.tasks.SendToDeviceTask +import org.matrix.android.sdk.internal.crypto.tasks.createUniqueTxnId import org.matrix.android.sdk.internal.session.SessionComponent import org.matrix.android.sdk.internal.worker.SessionSafeCoroutineWorker import org.matrix.android.sdk.internal.worker.SessionWorkerParams @@ -48,6 +48,9 @@ internal class SendGossipWorker(context: Context, val requestUserId: String?, val requestDeviceId: String?, val requestId: String?, + // The txnId for the sendToDevice request. Nullable for compatibility reasons, but MUST always be provided + // to use the same value if this worker is retried. + val txnId: String? = null, override val lastFailureMessage: String? = null ) : SessionWorkerParams @@ -62,7 +65,10 @@ internal class SendGossipWorker(context: Context, } override suspend fun doSafeWork(params: Params): Result { - val localId = LocalEcho.createLocalEchoId() + // params.txnId should be provided in all cases now. But Params can be deserialized by + // the WorkManager from data serialized in a previous version of the application, so without the txnId field. + // So if not present, we create a txnId + val txnId = params.txnId ?: createUniqueTxnId() val eventType: String = EventType.SEND_SECRET val toDeviceContent = SecretSendEventContent( @@ -127,7 +133,7 @@ internal class SendGossipWorker(context: Context, SendToDeviceTask.Params( eventType = EventType.ENCRYPTED, contentMap = sendToDeviceMap, - transactionId = localId + transactionId = txnId ) ) cryptoStore.updateGossipingRequestState( 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 d9979988..3c8353e8 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 @@ -27,7 +27,6 @@ import io.realm.Sort import io.realm.kotlin.where import org.matrix.android.sdk.api.session.crypto.crosssigning.MXCrossSigningInfo import org.matrix.android.sdk.api.session.events.model.Event -import org.matrix.android.sdk.api.session.events.model.LocalEcho import org.matrix.android.sdk.api.session.room.send.SendState import org.matrix.android.sdk.api.util.Optional import org.matrix.android.sdk.api.util.toOptional @@ -88,6 +87,7 @@ import org.matrix.android.sdk.internal.crypto.store.db.query.delete import org.matrix.android.sdk.internal.crypto.store.db.query.get import org.matrix.android.sdk.internal.crypto.store.db.query.getById import org.matrix.android.sdk.internal.crypto.store.db.query.getOrCreate +import org.matrix.android.sdk.internal.crypto.util.RequestIdHelper import org.matrix.android.sdk.internal.database.mapper.ContentMapper import org.matrix.android.sdk.internal.database.tools.RealmDebugTools import org.matrix.android.sdk.internal.di.CryptoDatabase @@ -1121,7 +1121,7 @@ internal class RealmCryptoStore @Inject constructor( if (existing == null) { request = realm.createObject(OutgoingGossipingRequestEntity::class.java).apply { - this.requestId = LocalEcho.createLocalEchoId() + this.requestId = RequestIdHelper.createUniqueRequestId() this.setRecipients(recipients) this.requestState = OutgoingGossipingRequestState.UNSENT this.type = GossipRequestType.KEY @@ -1151,7 +1151,7 @@ internal class RealmCryptoStore @Inject constructor( this.type = GossipRequestType.SECRET setRecipients(recipients) this.requestState = OutgoingGossipingRequestState.UNSENT - this.requestId = LocalEcho.createLocalEchoId() + this.requestId = RequestIdHelper.createUniqueRequestId() this.requestedInfoStr = secretName }.toOutgoingGossipingRequest() as? OutgoingSecretRequest } else { 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 41a5118b..c6af9b0c 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 @@ -22,8 +22,8 @@ import org.matrix.android.sdk.internal.crypto.model.rest.SendToDeviceBody 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 java.util.UUID import javax.inject.Inject -import kotlin.random.Random internal interface SendToDeviceTask : Task<SendToDeviceTask.Params, Unit> { data class Params( @@ -31,7 +31,7 @@ internal interface SendToDeviceTask : Task<SendToDeviceTask.Params, Unit> { val eventType: String, // the content to send. Map from user_id to device_id to content dictionary. val contentMap: MXUsersDevicesMap<Any>, - // the transactionId + // the transactionId. If not provided, a transactionId will be created by the task val transactionId: String? = null ) } @@ -46,16 +46,23 @@ internal class DefaultSendToDeviceTask @Inject constructor( messages = params.contentMap.map ) + // If params.transactionId is not provided, we create a unique txnId. + // It's important to do that outside the requestBlock parameter of executeRequest() + // to use the same value if the request is retried + val txnId = params.transactionId ?: createUniqueTxnId() + return executeRequest( globalErrorReceiver, canRetry = true, maxRetriesCount = 3 ) { cryptoApi.sendToDevice( - params.eventType, - params.transactionId ?: Random.nextInt(Integer.MAX_VALUE).toString(), - sendToDeviceBody + eventType = params.eventType, + transactionId = txnId, + body = sendToDeviceBody ) } } } + +internal fun createUniqueTxnId() = UUID.randomUUID().toString() diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/util/RequestIdHelper.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/util/RequestIdHelper.kt new file mode 100644 index 00000000..3106d582 --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/util/RequestIdHelper.kt @@ -0,0 +1,23 @@ +/* + * 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.crypto.util + +import java.util.UUID + +internal object RequestIdHelper { + fun createUniqueRequestId() = UUID.randomUUID().toString() +} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/EventInsertLiveObserver.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/EventInsertLiveObserver.kt index 88aa432f..758c7aa5 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/EventInsertLiveObserver.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/EventInsertLiveObserver.kt @@ -17,6 +17,9 @@ package org.matrix.android.sdk.internal.database import com.zhuinden.monarchy.Monarchy +import io.realm.RealmConfiguration +import io.realm.RealmResults +import kotlinx.coroutines.launch import org.matrix.android.sdk.internal.database.mapper.asDomain import org.matrix.android.sdk.internal.database.model.EventEntity import org.matrix.android.sdk.internal.database.model.EventInsertEntity @@ -24,20 +27,15 @@ import org.matrix.android.sdk.internal.database.model.EventInsertEntityFields import org.matrix.android.sdk.internal.database.query.where import org.matrix.android.sdk.internal.di.SessionDatabase import org.matrix.android.sdk.internal.session.EventInsertLiveProcessor -import io.realm.RealmConfiguration -import io.realm.RealmResults -import kotlinx.coroutines.launch -import org.matrix.android.sdk.internal.crypto.EventDecryptor import timber.log.Timber import javax.inject.Inject internal class EventInsertLiveObserver @Inject constructor(@SessionDatabase realmConfiguration: RealmConfiguration, - private val processors: Set<@JvmSuppressWildcards EventInsertLiveProcessor>, - private val eventDecryptor: EventDecryptor) + private val processors: Set<@JvmSuppressWildcards EventInsertLiveProcessor>) : RealmLiveEntityObserver<EventInsertEntity>(realmConfiguration) { - override val query = Monarchy.Query<EventInsertEntity> { - it.where(EventInsertEntity::class.java) + override val query = Monarchy.Query { + it.where(EventInsertEntity::class.java).equalTo(EventInsertEntityFields.CAN_BE_PROCESSED, true) } override fun onChange(results: RealmResults<EventInsertEntity>) { @@ -86,23 +84,6 @@ internal class EventInsertLiveObserver @Inject constructor(@SessionDatabase real } } -// private fun decryptIfNeeded(event: Event) { -// if (event.isEncrypted() && event.mxDecryptionResult == null) { -// try { -// val result = eventDecryptor.decryptEvent(event, event.roomId ?: "") -// event.mxDecryptionResult = OlmDecryptionResult( -// payload = result.clearEvent, -// senderKey = result.senderCurve25519Key, -// keysClaimed = result.claimedEd25519Key?.let { k -> mapOf("ed25519" to k) }, -// forwardingCurve25519KeyChain = result.forwardingCurve25519KeyChain -// ) -// } catch (e: MXCryptoError) { -// Timber.v("Failed to decrypt event") -// // TODO -> we should keep track of this and retry, or some processing will never be handled -// } -// } -// } - private fun shouldProcess(eventInsertEntity: EventInsertEntity): Boolean { return processors.any { it.shouldProcess(eventInsertEntity.eventId, eventInsertEntity.eventType, eventInsertEntity.insertType) 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 28ae4d8b..aa96ca5e 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 @@ -29,6 +29,7 @@ import org.matrix.android.sdk.internal.database.model.CurrentStateEventEntityFie import org.matrix.android.sdk.internal.database.model.EditAggregatedSummaryEntityFields import org.matrix.android.sdk.internal.database.model.EditionOfEventFields import org.matrix.android.sdk.internal.database.model.EventEntityFields +import org.matrix.android.sdk.internal.database.model.EventInsertEntityFields import org.matrix.android.sdk.internal.database.model.HomeServerCapabilitiesEntityFields import org.matrix.android.sdk.internal.database.model.PendingThreePidEntityFields import org.matrix.android.sdk.internal.database.model.PreviewUrlCacheEntityFields @@ -46,7 +47,7 @@ import timber.log.Timber internal object RealmSessionStoreMigration : RealmMigration { - const val SESSION_STORE_SCHEMA_VERSION = 16L + const val SESSION_STORE_SCHEMA_VERSION = 17L override fun migrate(realm: DynamicRealm, oldVersion: Long, newVersion: Long) { Timber.v("Migrating Realm Session from $oldVersion to $newVersion") @@ -67,6 +68,7 @@ internal object RealmSessionStoreMigration : RealmMigration { if (oldVersion <= 13) migrateTo14(realm) if (oldVersion <= 14) migrateTo15(realm) if (oldVersion <= 15) migrateTo16(realm) + if (oldVersion <= 16) migrateTo17(realm) } private fun migrateTo1(realm: DynamicRealm) { @@ -330,4 +332,10 @@ internal object RealmSessionStoreMigration : RealmMigration { obj.setLong(HomeServerCapabilitiesEntityFields.LAST_UPDATED_TIMESTAMP, 0) } } + + private fun migrateTo17(realm: DynamicRealm) { + Timber.d("Step 16 -> 17") + realm.schema.get("EventInsertEntity") + ?.addField(EventInsertEntityFields.CAN_BE_PROCESSED, Boolean::class.java) + } } 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 c32c0196..0cf431c3 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 @@ -88,7 +88,7 @@ internal class RoomSummaryMapper @Inject constructor(private val timelineEventMa avatarUrl = it.childSummaryEntity?.avatarUrl, activeMemberCount = it.childSummaryEntity?.joinedMembersCount, order = it.order, - autoJoin = it.autoJoin ?: false, +// autoJoin = it.autoJoin ?: false, viaServers = it.viaServers.toList(), parentRoomId = roomSummaryEntity.roomId, suggested = it.suggested, diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/EventEntity.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/EventEntity.kt index c9edbcd8..4dc8712a 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/EventEntity.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/EventEntity.kt @@ -22,6 +22,7 @@ import org.matrix.android.sdk.internal.crypto.algorithms.olm.OlmDecryptionResult import org.matrix.android.sdk.internal.di.MoshiProvider import io.realm.RealmObject import io.realm.annotations.Index +import org.matrix.android.sdk.internal.extensions.assertIsManaged internal open class EventEntity(@Index var eventId: String = "", @Index var roomId: String = "", @@ -56,15 +57,22 @@ internal open class EventEntity(@Index var eventId: String = "", companion object fun setDecryptionResult(result: MXEventDecryptionResult) { + assertIsManaged() val decryptionResult = OlmDecryptionResult( payload = result.clearEvent, senderKey = result.senderCurve25519Key, keysClaimed = result.claimedEd25519Key?.let { mapOf("ed25519" to it) }, forwardingCurve25519KeyChain = result.forwardingCurve25519KeyChain ) - val adapter = MoshiProvider.providesMoshi().adapter<OlmDecryptionResult>(OlmDecryptionResult::class.java) + val adapter = MoshiProvider.providesMoshi().adapter(OlmDecryptionResult::class.java) decryptionResultJson = adapter.toJson(decryptionResult) decryptionErrorCode = null decryptionErrorReason = null + + // If we have an EventInsertEntity for the eventId we make sures it can be processed now. + realm.where(EventInsertEntity::class.java) + .equalTo(EventInsertEntityFields.EVENT_ID, eventId) + .findFirst() + ?.canBeProcessed = true } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/EventInsertEntity.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/EventInsertEntity.kt index f4426207..5cfd306d 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/EventInsertEntity.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/EventInsertEntity.kt @@ -23,7 +23,12 @@ import io.realm.RealmObject * in EventEntity table. */ internal open class EventInsertEntity(var eventId: String = "", - var eventType: String = "" + var eventType: String = "", + /** + * This flag will be used to filter EventInsertEntity in EventInsertLiveObserver. + * Currently it's set to false when the event content is encrypted. + */ + var canBeProcessed: Boolean = true ) : RealmObject() { private var insertTypeStr: String = EventInsertType.INCREMENTAL_SYNC.name diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/query/EventEntityQueries.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/query/EventEntityQueries.kt index 0bf62a19..57e24cf8 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/query/EventEntityQueries.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/query/EventEntityQueries.kt @@ -24,6 +24,7 @@ import io.realm.Realm import io.realm.RealmList import io.realm.RealmQuery import io.realm.kotlin.where +import org.matrix.android.sdk.api.session.events.model.EventType internal fun EventEntity.copyToRealmOrIgnore(realm: Realm, insertType: EventInsertType): EventEntity { val eventEntity = realm.where<EventEntity>() @@ -31,7 +32,8 @@ internal fun EventEntity.copyToRealmOrIgnore(realm: Realm, insertType: EventInse .equalTo(EventEntityFields.ROOM_ID, roomId) .findFirst() return if (eventEntity == null) { - val insertEntity = EventInsertEntity(eventId = eventId, eventType = type).apply { + val canBeProcessed = type != EventType.ENCRYPTED || decryptionResultJson != null + val insertEntity = EventInsertEntity(eventId = eventId, eventType = type, canBeProcessed = canBeProcessed).apply { this.insertType = insertType } realm.insert(insertEntity) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/network/NetworkConstants.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/network/NetworkConstants.kt index 99c12255..361a306d 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/network/NetworkConstants.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/network/NetworkConstants.kt @@ -30,6 +30,7 @@ internal object NetworkConstants { // Identity server const val URI_IDENTITY_PREFIX_PATH = "_matrix/identity/v2" const val URI_IDENTITY_PATH_V2 = "$URI_IDENTITY_PREFIX_PATH/" + const val URI_IDENTITY_PATH_V1 = "_matrix/identity/api/v1/" // Push Gateway const val URI_PUSH_GATEWAY_PREFIX_PATH = "_matrix/push/v1/" 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 21160636..8a031025 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 @@ -19,13 +19,13 @@ package org.matrix.android.sdk.internal.network import com.squareup.moshi.JsonEncodingException +import kotlinx.coroutines.suspendCancellableCoroutine +import okhttp3.ResponseBody +import org.matrix.android.sdk.api.extensions.orFalse import org.matrix.android.sdk.api.failure.Failure import org.matrix.android.sdk.api.failure.GlobalError import org.matrix.android.sdk.api.failure.MatrixError import org.matrix.android.sdk.internal.di.MoshiProvider -import kotlinx.coroutines.suspendCancellableCoroutine -import okhttp3.ResponseBody -import org.matrix.android.sdk.api.extensions.orFalse import retrofit2.HttpException import retrofit2.Response import timber.log.Timber @@ -86,13 +86,18 @@ private fun toFailure(errorBody: ResponseBody?, httpCode: Int, globalErrorReceiv val matrixError = matrixErrorAdapter.fromJson(errorBodyStr) if (matrixError != null) { - if (matrixError.code == MatrixError.M_CONSENT_NOT_GIVEN && !matrixError.consentUri.isNullOrBlank()) { - // Also send this error to the globalErrorReceiver, for a global management - globalErrorReceiver?.handleGlobalError(GlobalError.ConsentNotGivenError(matrixError.consentUri)) - } else if (httpCode == HttpURLConnection.HTTP_UNAUTHORIZED /* 401 */ - && matrixError.code == MatrixError.M_UNKNOWN_TOKEN) { - // Also send this error to the globalErrorReceiver, for a global management - globalErrorReceiver?.handleGlobalError(GlobalError.InvalidToken(matrixError.isSoftLogout.orFalse())) + // Also send following errors to the globalErrorReceiver, for a global management + when { + matrixError.code == MatrixError.M_CONSENT_NOT_GIVEN && !matrixError.consentUri.isNullOrBlank() -> { + globalErrorReceiver?.handleGlobalError(GlobalError.ConsentNotGivenError(matrixError.consentUri)) + } + httpCode == HttpURLConnection.HTTP_UNAUTHORIZED /* 401 */ + && matrixError.code == MatrixError.M_UNKNOWN_TOKEN -> { + globalErrorReceiver?.handleGlobalError(GlobalError.InvalidToken(matrixError.isSoftLogout.orFalse())) + } + matrixError.code == MatrixError.ORG_MATRIX_EXPIRED_ACCOUNT -> { + globalErrorReceiver?.handleGlobalError(GlobalError.ExpiredAccount) + } } return Failure.ServerError(matrixError, httpCode) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/query/QueryRoomOrderProcessor.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/query/QueryRoomOrderProcessor.kt index 7a06c212..7294ce4a 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/query/QueryRoomOrderProcessor.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/query/QueryRoomOrderProcessor.kt @@ -24,13 +24,21 @@ import org.matrix.android.sdk.internal.database.model.RoomSummaryEntityFields internal fun RealmQuery<RoomSummaryEntity>.process(sortOrder: RoomSortOrder): RealmQuery<RoomSummaryEntity> { when (sortOrder) { - RoomSortOrder.NAME -> { + RoomSortOrder.NAME -> { sort(RoomSummaryEntityFields.DISPLAY_NAME, Sort.ASCENDING) } - RoomSortOrder.ACTIVITY -> { + RoomSortOrder.ACTIVITY -> { sort(RoomSummaryEntityFields.LAST_ACTIVITY_TIME, Sort.DESCENDING) } - RoomSortOrder.NONE -> { + RoomSortOrder.PRIORITY_AND_ACTIVITY -> { + sort( + arrayOf( + RoomSummaryEntityFields.IS_FAVOURITE, + RoomSummaryEntityFields.IS_LOW_PRIORITY, + RoomSummaryEntityFields.LAST_ACTIVITY_TIME), + arrayOf(Sort.DESCENDING, Sort.ASCENDING, Sort.DESCENDING)) + } + RoomSortOrder.NONE -> { } } return this diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/homeserver/GetCapabilitiesResult.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/homeserver/GetCapabilitiesResult.kt index 1fe4f9d9..b36d05b6 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/homeserver/GetCapabilitiesResult.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/homeserver/GetCapabilitiesResult.kt @@ -84,7 +84,7 @@ internal data class RoomVersions( * } * } */ - @Json(name = "room_capabilities") + @Json(name = "org.matrix.msc3244.room_capabilities") val roomCapabilities: JsonDict? = null ) 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 48870b86..fdb6caf5 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 @@ -52,6 +52,7 @@ import kotlinx.coroutines.withContext import okhttp3.OkHttpClient import org.matrix.android.sdk.api.extensions.orFalse import org.matrix.android.sdk.api.session.Session +import org.matrix.android.sdk.internal.session.identity.model.SignInvitationResult import timber.log.Timber import javax.inject.Inject import javax.net.ssl.HttpsURLConnection @@ -79,6 +80,7 @@ internal class DefaultIdentityService @Inject constructor( private val identityApiProvider: IdentityApiProvider, private val accountDataDataSource: UserAccountDataDataSource, private val homeServerCapabilitiesService: HomeServerCapabilitiesService, + private val sign3pidInvitationTask: DefaultSign3pidInvitationTask, private val sessionParams: SessionParams ) : IdentityService, SessionLifecycleObserver { @@ -290,6 +292,14 @@ internal class DefaultIdentityService @Inject constructor( return token.token } + override suspend fun sign3pidInvitation(identiyServer: String, token: String, secret: String): SignInvitationResult { + return sign3pidInvitationTask.execute(Sign3pidInvitationTask.Params( + url = identiyServer, + token = token, + privateKey = secret + )) + } + override fun addListener(listener: IdentityServiceListener) { listeners.add(listener) } 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 e9e4d17e..99bd7404 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,10 +26,12 @@ 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 org.matrix.android.sdk.internal.session.identity.model.SignInvitationResult import retrofit2.http.Body import retrofit2.http.GET import retrofit2.http.POST import retrofit2.http.Path +import retrofit2.http.Query /** * Ref: https://matrix.org/docs/spec/identity_service/latest @@ -95,4 +97,16 @@ internal interface IdentityAPI { @POST(NetworkConstants.URI_IDENTITY_PATH_V2 + "validate/{medium}/submitToken") suspend fun submitToken(@Path("medium") medium: String, @Body body: IdentityRequestOwnershipParams): SuccessResult + + /** + * https://matrix.org/docs/spec/identity_service/r0.3.0#post-matrix-identity-v2-sign-ed25519 + * + * Have to rely on V1 for now + */ + @POST(NetworkConstants.URI_IDENTITY_PATH_V1 + "sign-ed25519") + suspend fun signInvitationDetails( + @Query("token") token: String, + @Query("private_key") privateKey: String, + @Query("mxid") mxid: String + ): SignInvitationResult } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/identity/Sign3pidInvitationTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/identity/Sign3pidInvitationTask.kt new file mode 100644 index 00000000..d491af33 --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/identity/Sign3pidInvitationTask.kt @@ -0,0 +1,49 @@ +/* + * Copyright 2021 The Matrix.org Foundation C.I.C. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.matrix.android.sdk.internal.session.identity + +import dagger.Lazy +import okhttp3.OkHttpClient +import org.matrix.android.sdk.internal.di.Unauthenticated +import org.matrix.android.sdk.internal.di.UserId +import org.matrix.android.sdk.internal.network.RetrofitFactory +import org.matrix.android.sdk.internal.session.identity.model.SignInvitationResult +import org.matrix.android.sdk.internal.task.Task +import javax.inject.Inject + +internal interface Sign3pidInvitationTask : Task<Sign3pidInvitationTask.Params, SignInvitationResult> { + data class Params( + val token: String, + val url: String, + val privateKey: String + ) +} + +internal class DefaultSign3pidInvitationTask @Inject constructor( + @Unauthenticated + private val okHttpClient: Lazy<OkHttpClient>, + private val retrofitFactory: RetrofitFactory, + @UserId private val userId: String +) : Sign3pidInvitationTask { + + override suspend fun execute(params: Sign3pidInvitationTask.Params): SignInvitationResult { + val identityAPI = retrofitFactory + .create(okHttpClient, "https://${params.url}") + .create(IdentityAPI::class.java) + return identityAPI.signInvitationDetails(params.token, params.privateKey, userId) + } +} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/identity/model/SignInvitationBody.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/identity/model/SignInvitationBody.kt new file mode 100644 index 00000000..69982572 --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/identity/model/SignInvitationBody.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.session.identity.model + +import com.squareup.moshi.JsonClass + +@JsonClass(generateAdapter = true) +internal data class SignInvitationBody( + /**The Matrix user ID of the user accepting the invitation.*/ + val mxid: String, + /**The token from the call to store- invite..*/ + val token: String, + /** The private key, encoded as Unpadded base64. */ + val private_key: String +) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/identity/model/SignInvitationResult.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/identity/model/SignInvitationResult.kt new file mode 100644 index 00000000..27a3f320 --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/identity/model/SignInvitationResult.kt @@ -0,0 +1,31 @@ +/* + * Copyright 2021 The Matrix.org Foundation C.I.C. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.matrix.android.sdk.internal.session.identity.model + +import com.squareup.moshi.JsonClass + +@JsonClass(generateAdapter = true) +data class SignInvitationResult( + /** The Matrix user ID of the user accepting the invitation.*/ + val mxid: String, + /** The Matrix user ID of the user who sent the invitation.*/ + val sender: String, + /**The token from the call to store- invite..*/ + val signatures: Map<String, *>, + /** The token for the invitation */ + val token: String +) 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 4e8abcf7..65974151 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 @@ -15,6 +15,8 @@ */ package org.matrix.android.sdk.internal.session.notification +import androidx.lifecycle.LiveData +import androidx.lifecycle.Transformations import com.zhuinden.monarchy.Monarchy import org.matrix.android.sdk.api.pushrules.Action import org.matrix.android.sdk.api.pushrules.PushRuleService @@ -26,6 +28,7 @@ import org.matrix.android.sdk.api.pushrules.rest.PushRule import org.matrix.android.sdk.api.pushrules.rest.RuleSet import org.matrix.android.sdk.api.session.events.model.Event import org.matrix.android.sdk.internal.database.mapper.PushRulesMapper +import org.matrix.android.sdk.internal.database.model.PushRuleEntity import org.matrix.android.sdk.internal.database.model.PushRulesEntity import org.matrix.android.sdk.internal.database.query.where import org.matrix.android.sdk.internal.di.SessionDatabase @@ -117,8 +120,8 @@ internal class DefaultPushRuleService @Inject constructor( updatePushRuleActionsTask.execute(UpdatePushRuleActionsTask.Params(kind, ruleId, enable, actions)) } - override suspend fun removePushRule(kind: RuleKind, pushRule: PushRule) { - removePushRuleTask.execute(RemovePushRuleTask.Params(kind, pushRule)) + override suspend fun removePushRule(kind: RuleKind, ruleId: String) { + removePushRuleTask.execute(RemovePushRuleTask.Params(kind, ruleId)) } override fun removePushRuleListener(listener: PushRuleService.PushRuleListener) { @@ -211,4 +214,19 @@ internal class DefaultPushRuleService @Inject constructor( } } } + + override fun getKeywords(): LiveData<Set<String>> { + // Keywords are all content rules that don't start with '.' + val liveData = monarchy.findAllMappedWithChanges( + { realm -> + PushRulesEntity.where(realm, RuleScope.GLOBAL, RuleSetKey.CONTENT) + }, + { result -> + result.pushRules.map(PushRuleEntity::ruleId).filter { !it.startsWith(".") } + } + ) + return Transformations.map(liveData) { results -> + results.firstOrNull().orEmpty().toSet() + } + } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/permalinks/ViaParameterFinder.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/permalinks/ViaParameterFinder.kt index 21f55bbc..dff82cb4 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/permalinks/ViaParameterFinder.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/permalinks/ViaParameterFinder.kt @@ -78,6 +78,10 @@ internal class ViaParameterFinder @Inject constructor( .toSet() } + // not used much for now but as per MSC1772 + // the via parameter of m.space.child must contain a via key which gives a list of candidate servers that can be used to join the room. + // It is possible for the list of candidate servers and the list of authorised servers to diverge. + // It may not be possible for a user to join a room if there's no overlap between these fun computeViaParamsForRestricted(roomId: String, max: Int): List<String> { val userThatCanInvite = roomGetterProvider.get().getRoom(roomId) ?.getRoomMembers(roomMemberQueryParams { memberships = listOf(Membership.JOIN) }) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/EnabledBody.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/EnabledBody.kt new file mode 100644 index 00000000..5d620ee7 --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/EnabledBody.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.internal.session.pushers + +import com.squareup.moshi.Json +import com.squareup.moshi.JsonClass + +@JsonClass(generateAdapter = true) +internal data class EnabledBody( + @Json(name = "enabled") + val enabled: Boolean +) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/GetPushersResponse.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/GetPushersResponse.kt index d8f0ce12..4fed4302 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/GetPushersResponse.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/GetPushersResponse.kt @@ -19,7 +19,7 @@ import com.squareup.moshi.Json import com.squareup.moshi.JsonClass @JsonClass(generateAdapter = true) -internal class GetPushersResponse( +internal data class GetPushersResponse( @Json(name = "pushers") val pushers: List<JsonPusher>? = null ) 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 daf9397c..994b4860 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 @@ -41,7 +41,7 @@ internal interface PushRulesApi { @PUT(NetworkConstants.URI_API_PREFIX_PATH_R0 + "pushrules/global/{kind}/{ruleId}/enabled") suspend fun updateEnableRuleStatus(@Path("kind") kind: String, @Path("ruleId") ruleId: String, - @Body enable: Boolean?) + @Body enabledBody: EnabledBody) /** * Update the ruleID action 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 23d0515f..bae89360 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 @@ -16,7 +16,6 @@ package org.matrix.android.sdk.internal.session.pushers import org.matrix.android.sdk.api.pushrules.RuleKind -import org.matrix.android.sdk.api.pushrules.rest.PushRule import org.matrix.android.sdk.internal.network.GlobalErrorReceiver import org.matrix.android.sdk.internal.network.executeRequest import org.matrix.android.sdk.internal.task.Task @@ -25,7 +24,7 @@ import javax.inject.Inject internal interface RemovePushRuleTask : Task<RemovePushRuleTask.Params, Unit> { data class Params( val kind: RuleKind, - val pushRule: PushRule + val ruleId: String ) } @@ -36,7 +35,7 @@ internal class DefaultRemovePushRuleTask @Inject constructor( override suspend fun execute(params: RemovePushRuleTask.Params) { return executeRequest(globalErrorReceiver) { - pushRulesApi.deleteRule(params.kind.value, params.pushRule.ruleId) + pushRulesApi.deleteRule(params.kind.value, params.ruleId) } } } 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 b8dbabd0..33589dc5 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 @@ -39,7 +39,11 @@ internal class DefaultUpdatePushRuleActionsTask @Inject constructor( override suspend fun execute(params: UpdatePushRuleActionsTask.Params) { executeRequest(globalErrorReceiver) { - pushRulesApi.updateEnableRuleStatus(params.kind.value, params.ruleId, enable = params.enable) + pushRulesApi.updateEnableRuleStatus( + params.kind.value, + params.ruleId, + EnabledBody(params.enable) + ) } if (params.actions != null) { val body = mapOf("actions" to params.actions.toJson()) 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 9d7a46be..3fe16146 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,11 @@ internal class DefaultUpdatePushRuleEnableStatusTask @Inject constructor( override suspend fun execute(params: UpdatePushRuleEnableStatusTask.Params) { return executeRequest(globalErrorReceiver) { - pushRulesApi.updateEnableRuleStatus(params.kind.value, params.pushRule.ruleId, params.enabled) + pushRulesApi.updateEnableRuleStatus( + params.kind.value, + params.pushRule.ruleId, + EnabledBody(params.enabled) + ) } } } 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 632ea4c4..f69949cb 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 @@ -38,6 +38,7 @@ import org.matrix.android.sdk.api.util.toOptional import org.matrix.android.sdk.internal.database.mapper.asDomain import org.matrix.android.sdk.internal.database.model.RoomMemberSummaryEntityFields import org.matrix.android.sdk.internal.di.SessionDatabase +import org.matrix.android.sdk.internal.session.identity.model.SignInvitationResult import org.matrix.android.sdk.internal.session.room.alias.DeleteRoomAliasTask import org.matrix.android.sdk.internal.session.room.alias.GetRoomIdByAliasTask import org.matrix.android.sdk.internal.session.room.alias.RoomAliasDescription @@ -122,6 +123,12 @@ internal class DefaultRoomService @Inject constructor( joinRoomTask.execute(JoinRoomTask.Params(roomIdOrAlias, reason, viaServers)) } + override suspend fun joinRoom(roomId: String, + reason: String?, + thirdPartySigned: SignInvitationResult) { + joinRoomTask.execute(JoinRoomTask.Params(roomId, reason, thirdPartySigned = thirdPartySigned)) + } + override suspend fun markAllAsRead(roomIds: List<String>) { markAllRoomsReadTask.execute(MarkAllRoomsReadTask.Params(roomIds)) } 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 18ece606..535fa9df 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 @@ -159,7 +159,8 @@ internal interface RoomAPI { @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) + @Path("eventId") eventId: String, + @Body body: JsonDict = emptyMap()) /** * Invite a user to the given room. @@ -253,7 +254,7 @@ internal interface RoomAPI { @POST(NetworkConstants.URI_API_PREFIX_PATH_R0 + "join/{roomIdOrAlias}") suspend fun join(@Path("roomIdOrAlias") roomIdOrAlias: String, @Query("server_name") viaServers: List<String>, - @Body params: Map<String, String?>): JoinRoomResponse + @Body params: JsonDict): JoinRoomResponse /** * Leave the given room. 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 c04c899e..79497070 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 @@ -28,6 +28,8 @@ import org.matrix.android.sdk.api.session.space.SpaceService import org.matrix.android.sdk.internal.session.DefaultFileService import org.matrix.android.sdk.internal.session.SessionScope import org.matrix.android.sdk.internal.session.directory.DirectoryAPI +import org.matrix.android.sdk.internal.session.identity.DefaultSign3pidInvitationTask +import org.matrix.android.sdk.internal.session.identity.Sign3pidInvitationTask import org.matrix.android.sdk.internal.session.room.accountdata.DefaultUpdateRoomAccountDataTask import org.matrix.android.sdk.internal.session.room.accountdata.UpdateRoomAccountDataTask import org.matrix.android.sdk.internal.session.room.alias.AddRoomAliasTask @@ -248,4 +250,7 @@ internal abstract class RoomModule { @Binds abstract fun bindRoomVersionUpgradeTask(task: DefaultRoomVersionUpgradeTask): RoomVersionUpgradeTask + + @Binds + abstract fun bindSign3pidInvitationTask(task: DefaultSign3pidInvitationTask): Sign3pidInvitationTask } 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 de6a71e5..518f0a0a 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 @@ -123,7 +123,7 @@ internal class DefaultCreateRoomTask @Inject constructor( this.isDirect = true } } - val directChats = directChatsHelper.getLocalUserAccount() + val directChats = directChatsHelper.getLocalDirectMessages() updateUserAccountDataTask.execute(UpdateUserAccountDataTask.DirectChatParams(directMessages = directChats)) } 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 2ecacf33..7528f80c 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 @@ -20,22 +20,30 @@ 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.room.model.RoomMemberContent +import org.matrix.android.sdk.internal.di.UserId import org.matrix.android.sdk.internal.session.events.getFixedRoomMemberContent +import org.matrix.android.sdk.internal.session.sync.SyncResponsePostTreatmentAggregator import org.matrix.android.sdk.internal.session.user.UserEntityFactory import javax.inject.Inject -internal class RoomMemberEventHandler @Inject constructor() { +internal class RoomMemberEventHandler @Inject constructor( + @UserId private val myUserId: String +) { - fun handle(realm: Realm, roomId: String, event: Event): Boolean { + fun handle(realm: Realm, roomId: String, event: Event, aggregator: SyncResponsePostTreatmentAggregator? = null): Boolean { if (event.type != EventType.STATE_ROOM_MEMBER) { return false } val userId = event.stateKey ?: return false val roomMember = event.getFixedRoomMemberContent() - return handle(realm, roomId, userId, roomMember) + return handle(realm, roomId, userId, roomMember, aggregator) } - fun handle(realm: Realm, roomId: String, userId: String, roomMember: RoomMemberContent?): Boolean { + fun handle(realm: Realm, + roomId: String, + userId: String, + roomMember: RoomMemberContent?, + aggregator: SyncResponsePostTreatmentAggregator? = null): Boolean { if (roomMember == null) { return false } @@ -45,6 +53,14 @@ internal class RoomMemberEventHandler @Inject constructor() { val userEntity = UserEntityFactory.create(userId, roomMember) realm.insertOrUpdate(userEntity) } + + // check whether this new room member event may be used to update the directs dictionary in account data + // this is required to handle correctly invite by email in DM + val mxId = roomMember.thirdPartyInvite?.signed?.mxid + if (mxId != null && mxId != myUserId) { + aggregator?.directChatsToCheck?.put(roomId, mxId) + } + return true } } 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 562b2568..209a904f 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 @@ -19,6 +19,7 @@ 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.events.model.toContent 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 @@ -29,6 +30,7 @@ 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.identity.model.SignInvitationResult import org.matrix.android.sdk.internal.session.room.RoomAPI import org.matrix.android.sdk.internal.session.room.membership.RoomChangeMembershipStateDataSource import org.matrix.android.sdk.internal.session.room.read.SetReadMarkersTask @@ -40,7 +42,8 @@ internal interface JoinRoomTask : Task<JoinRoomTask.Params, Unit> { data class Params( val roomIdOrAlias: String, val reason: String?, - val viaServers: List<String> = emptyList() + val viaServers: List<String> = emptyList(), + val thirdPartySigned : SignInvitationResult? = null ) } @@ -59,12 +62,16 @@ internal class DefaultJoinRoomTask @Inject constructor( return } roomChangeMembershipStateDataSource.updateState(params.roomIdOrAlias, ChangeMembershipState.Joining) + val extraParams = mutableMapOf<String, Any>().apply { + params.reason?.let { this["reason"] = it } + params.thirdPartySigned?.let { this["third_party_signed"] = it.toContent() } + } val joinRoomResponse = try { executeRequest(globalErrorReceiver) { roomAPI.join( roomIdOrAlias = params.roomIdOrAlias, viaServers = params.viaServers.take(3), - params = mapOf("reason" to params.reason) + params = extraParams ) } } catch (failure: Throwable) { diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/notification/SetRoomNotificationStateTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/notification/SetRoomNotificationStateTask.kt index de049d75..9cea1fe4 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/notification/SetRoomNotificationStateTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/notification/SetRoomNotificationStateTask.kt @@ -45,7 +45,7 @@ internal class DefaultSetRoomNotificationStateTask @Inject constructor(@SessionD PushRuleEntity.where(it, scope = RuleScope.GLOBAL, ruleId = params.roomId).findFirst()?.toRoomPushRule() } if (currentRoomPushRule != null) { - removePushRuleTask.execute(RemovePushRuleTask.Params(currentRoomPushRule.kind, currentRoomPushRule.rule)) + removePushRuleTask.execute(RemovePushRuleTask.Params(currentRoomPushRule.kind, currentRoomPushRule.rule.ruleId)) } val newRoomPushRule = params.roomNotificationState.toRoomPushRule(params.roomId) if (newRoomPushRule != null) { diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/relationship/RoomChildRelationInfo.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/relationship/RoomChildRelationInfo.kt index 2efea7f1..5bad334a 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/relationship/RoomChildRelationInfo.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/relationship/RoomChildRelationInfo.kt @@ -43,7 +43,7 @@ internal class RoomChildRelationInfo( data class SpaceChildInfo( val roomId: String, val order: String?, - val autoJoin: Boolean, +// val autoJoin: Boolean, val viaServers: List<String> ) @@ -71,7 +71,7 @@ internal class RoomChildRelationInfo( SpaceChildInfo( roomId = it.stateKey, order = scc.validOrder(), - autoJoin = scc.autoJoin ?: false, +// autoJoin = scc.autoJoin ?: false, viaServers = via ) } 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 a64b9039..6dbb71e0 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 @@ -185,7 +185,7 @@ internal class DefaultSendService @AssistedInject constructor( name = messageContent.body, queryUri = Uri.parse(messageContent.url), type = ContentAttachmentData.Type.AUDIO, - waveform = messageContent.audioWaveformInfo?.waveform + waveform = messageContent.audioWaveformInfo?.waveform?.filterNotNull() ) localEchoRepository.updateSendState(localEcho.eventId, roomId, SendState.UNSENT) internalSendMedia(listOf(localEcho.root), attachmentData, true) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/LocalEchoRepository.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/LocalEchoRepository.kt index e98e5646..13095fbd 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/LocalEchoRepository.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/LocalEchoRepository.kt @@ -77,7 +77,7 @@ internal class LocalEchoRepository @Inject constructor(@SessionDatabase private val timelineEvent = timelineEventMapper.map(timelineEventEntity) timelineInput.onLocalEchoCreated(roomId = roomId, timelineEvent = timelineEvent) taskExecutor.executorScope.asyncTransaction(monarchy) { realm -> - val eventInsertEntity = EventInsertEntity(event.eventId, event.type).apply { + val eventInsertEntity = EventInsertEntity(event.eventId, event.type, canBeProcessed = true).apply { this.insertType = EventInsertType.LOCAL_ECHO } realm.insert(eventInsertEntity) 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 7eed22f6..4ec27976 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 @@ -182,7 +182,7 @@ internal class DefaultStateService @AssistedInject constructor(@Assisted private override suspend fun setJoinRuleRestricted(allowList: List<String>) { // we need to compute correct via parameters and check if PL are correct val allowEntries = allowList.map { spaceId -> - RoomJoinRulesAllowEntry(spaceId, viaParameterFinder.computeViaParamsForRestricted(spaceId, 3)) + RoomJoinRulesAllowEntry.restrictedToRoom(spaceId) } updateJoinRule(RoomJoinRules.RESTRICTED, null, allowEntries) } 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 842c9d3a..89a35339 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 @@ -220,7 +220,7 @@ internal class RoomSummaryUpdater @Inject constructor( this.childRoomId = child.roomId this.childSummaryEntity = RoomSummaryEntity.where(realm, child.roomId).findFirst() this.order = child.order - this.autoJoin = child.autoJoin +// this.autoJoin = child.autoJoin this.viaServers.addAll(child.viaServers) } ) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/TimelineEventDecryptor.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/TimelineEventDecryptor.kt index 3517f26c..721dae0b 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/TimelineEventDecryptor.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/TimelineEventDecryptor.kt @@ -106,7 +106,8 @@ internal class TimelineEventDecryptor @Inject constructor( val result = cryptoService.decryptEvent(request.event, timelineId) Timber.v("Successfully decrypted event ${event.eventId}") realm.executeTransaction { - EventEntity.where(it, eventId = event.eventId ?: "") + val eventId = event.eventId ?: "" + EventEntity.where(it, eventId = eventId) .findFirst() ?.setDecryptionResult(result) } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/space/DefaultSpace.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/space/DefaultSpace.kt index 233eef45..8a6bbc18 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/space/DefaultSpace.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/space/DefaultSpace.kt @@ -21,6 +21,7 @@ import org.matrix.android.sdk.api.session.events.model.EventType import org.matrix.android.sdk.api.session.events.model.toContent import org.matrix.android.sdk.api.session.events.model.toModel import org.matrix.android.sdk.api.session.room.Room +import org.matrix.android.sdk.api.session.room.model.RoomJoinRules import org.matrix.android.sdk.api.session.room.model.RoomSummary import org.matrix.android.sdk.api.session.space.Space import org.matrix.android.sdk.api.session.space.model.SpaceChildContent @@ -50,16 +51,24 @@ internal class DefaultSpace( override suspend fun addChildren(roomId: String, viaServers: List<String>?, order: String?, - autoJoin: Boolean, +// autoJoin: Boolean, suggested: Boolean?) { // Find best via + val bestVia = viaServers + ?: (spaceSummaryDataSource.getRoomSummary(roomId) + ?.takeIf { it.joinRules == RoomJoinRules.RESTRICTED } + ?.let { + // for restricted room, best to take via from users that can invite in the + // child room + viaParameterFinder.computeViaParamsForRestricted(roomId, 3) + } + ?: viaParameterFinder.computeViaParams(roomId, 3)) room.sendStateEvent( eventType = EventType.STATE_SPACE_CHILD, stateKey = roomId, body = SpaceChildContent( - via = viaServers ?: viaParameterFinder.computeViaParams(roomId, 3), - autoJoin = autoJoin, + via = bestVia, order = order, suggested = suggested ).toContent() @@ -80,7 +89,7 @@ internal class DefaultSpace( body = SpaceChildContent( order = null, via = null, - autoJoin = null, +// autoJoin = null, suggested = null ).toContent() ) @@ -105,35 +114,35 @@ internal class DefaultSpace( body = SpaceChildContent( order = order, via = existing.via, - autoJoin = existing.autoJoin, +// autoJoin = existing.autoJoin, suggested = existing.suggested ).toContent() ) } - override suspend fun setChildrenAutoJoin(roomId: String, autoJoin: Boolean) { - val existing = room.getStateEvents(setOf(EventType.STATE_SPACE_CHILD), QueryStringValue.Equals(roomId)) - .firstOrNull() - ?.content.toModel<SpaceChildContent>() - ?: throw IllegalArgumentException("$roomId is not a child of this space") - - if (existing.autoJoin == autoJoin) { - // nothing to do? - return - } - - // edit state event and set via to null - room.sendStateEvent( - eventType = EventType.STATE_SPACE_CHILD, - stateKey = roomId, - body = SpaceChildContent( - order = existing.order, - via = existing.via, - autoJoin = autoJoin, - suggested = existing.suggested - ).toContent() - ) - } +// override suspend fun setChildrenAutoJoin(roomId: String, autoJoin: Boolean) { +// val existing = room.getStateEvents(setOf(EventType.STATE_SPACE_CHILD), QueryStringValue.Equals(roomId)) +// .firstOrNull() +// ?.content.toModel<SpaceChildContent>() +// ?: throw IllegalArgumentException("$roomId is not a child of this space") +// +// if (existing.autoJoin == autoJoin) { +// // nothing to do? +// return +// } +// +// // edit state event and set via to null +// room.sendStateEvent( +// eventType = EventType.STATE_SPACE_CHILD, +// stateKey = roomId, +// body = SpaceChildContent( +// order = existing.order, +// via = existing.via, +// autoJoin = autoJoin, +// suggested = existing.suggested +// ).toContent() +// ) +// } override suspend fun setChildrenSuggested(roomId: String, suggested: Boolean) { val existing = room.getStateEvents(setOf(EventType.STATE_SPACE_CHILD), QueryStringValue.Equals(roomId)) @@ -152,7 +161,7 @@ internal class DefaultSpace( body = SpaceChildContent( order = existing.order, via = existing.via, - autoJoin = existing.autoJoin, +// autoJoin = existing.autoJoin, suggested = suggested ).toContent() ) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/space/DefaultSpaceService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/space/DefaultSpaceService.kt index 0c5c0416..7be4cdcd 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/space/DefaultSpaceService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/space/DefaultSpaceService.kt @@ -19,6 +19,7 @@ package org.matrix.android.sdk.internal.session.space import android.net.Uri import androidx.lifecycle.LiveData import org.matrix.android.sdk.api.query.QueryStringValue +import org.matrix.android.sdk.api.session.events.model.Event import org.matrix.android.sdk.api.session.events.model.EventType import org.matrix.android.sdk.api.session.events.model.toContent import org.matrix.android.sdk.api.session.events.model.toModel @@ -27,6 +28,7 @@ import org.matrix.android.sdk.api.session.room.model.Membership import org.matrix.android.sdk.api.session.room.model.PowerLevelsContent import org.matrix.android.sdk.api.session.room.model.RoomDirectoryVisibility 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.RoomSummary import org.matrix.android.sdk.api.session.room.model.SpaceChildInfo import org.matrix.android.sdk.api.session.room.model.create.CreateRoomPreset @@ -34,6 +36,7 @@ import org.matrix.android.sdk.api.session.room.powerlevels.PowerLevelsHelper import org.matrix.android.sdk.api.session.space.CreateSpaceParams import org.matrix.android.sdk.api.session.space.JoinSpaceResult import org.matrix.android.sdk.api.session.space.Space +import org.matrix.android.sdk.api.session.space.SpaceHierarchyData import org.matrix.android.sdk.api.session.space.SpaceService import org.matrix.android.sdk.api.session.space.SpaceSummaryQueryParams import org.matrix.android.sdk.api.session.space.model.SpaceChildContent @@ -108,53 +111,65 @@ internal class DefaultSpaceService @Inject constructor( override suspend fun querySpaceChildren(spaceId: String, suggestedOnly: Boolean?, - autoJoinedOnly: Boolean?): Pair<RoomSummary, List<SpaceChildInfo>> { - return resolveSpaceInfoTask.execute(ResolveSpaceInfoTask.Params.withId(spaceId, suggestedOnly, autoJoinedOnly)).let { response -> + limit: Int?, + from: String?, + knownStateList: List<Event>?): SpaceHierarchyData { + return resolveSpaceInfoTask.execute( + ResolveSpaceInfoTask.Params( + spaceId = spaceId, limit = limit, maxDepth = 1, from = from, suggestedOnly = suggestedOnly + ) + ).let { response -> val spaceDesc = response.rooms?.firstOrNull { it.roomId == spaceId } - Pair( - first = RoomSummary( - roomId = spaceDesc?.roomId ?: spaceId, - roomType = spaceDesc?.roomType, - name = spaceDesc?.name ?: "", - displayName = spaceDesc?.name ?: "", - topic = spaceDesc?.topic ?: "", - joinedMembersCount = spaceDesc?.numJoinedMembers, - avatarUrl = spaceDesc?.avatarUrl ?: "", - encryptionEventTs = null, - typingUsers = emptyList(), - isEncrypted = false, - flattenParentIds = emptyList() - ), - second = response.rooms - ?.filter { it.roomId != spaceId } - ?.flatMap { childSummary -> - response.events - ?.filter { it.stateKey == childSummary.roomId && it.type == EventType.STATE_SPACE_CHILD } - ?.mapNotNull { childStateEv -> - // create a child entry for everytime this room is the child of a space - // beware that a room could appear then twice in this list - childStateEv.content.toModel<SpaceChildContent>()?.let { childStateEvContent -> - SpaceChildInfo( - childRoomId = childSummary.roomId, - isKnown = true, - roomType = childSummary.roomType, - name = childSummary.name, - topic = childSummary.topic, - avatarUrl = childSummary.avatarUrl, - order = childStateEvContent.order, - autoJoin = childStateEvContent.autoJoin ?: false, - viaServers = childStateEvContent.via.orEmpty(), - activeMemberCount = childSummary.numJoinedMembers, - parentRoomId = childStateEv.roomId, - suggested = childStateEvContent.suggested, - canonicalAlias = childSummary.canonicalAlias, - aliases = childSummary.aliases, - worldReadable = childSummary.worldReadable - ) - } - }.orEmpty() - } - .orEmpty() + val root = RoomSummary( + roomId = spaceDesc?.roomId ?: spaceId, + roomType = spaceDesc?.roomType, + name = spaceDesc?.name ?: "", + displayName = spaceDesc?.name ?: "", + topic = spaceDesc?.topic ?: "", + joinedMembersCount = spaceDesc?.numJoinedMembers, + avatarUrl = spaceDesc?.avatarUrl ?: "", + encryptionEventTs = null, + typingUsers = emptyList(), + isEncrypted = false, + flattenParentIds = emptyList(), + canonicalAlias = spaceDesc?.canonicalAlias, + joinRules = RoomJoinRules.PUBLIC.takeIf { spaceDesc?.worldReadable == true } + ) + val children = response.rooms + ?.filter { it.roomId != spaceId } + ?.flatMap { childSummary -> + (spaceDesc?.childrenState ?: knownStateList) + ?.filter { it.stateKey == childSummary.roomId && it.type == EventType.STATE_SPACE_CHILD } + ?.mapNotNull { childStateEv -> + // create a child entry for everytime this room is the child of a space + // beware that a room could appear then twice in this list + childStateEv.content.toModel<SpaceChildContent>()?.let { childStateEvContent -> + SpaceChildInfo( + childRoomId = childSummary.roomId, + isKnown = true, + roomType = childSummary.roomType, + name = childSummary.name, + topic = childSummary.topic, + avatarUrl = childSummary.avatarUrl, + order = childStateEvContent.order, +// autoJoin = childStateEvContent.autoJoin ?: false, + viaServers = childStateEvContent.via.orEmpty(), + activeMemberCount = childSummary.numJoinedMembers, + parentRoomId = childStateEv.roomId, + suggested = childStateEvContent.suggested, + canonicalAlias = childSummary.canonicalAlias, + aliases = childSummary.aliases, + worldReadable = childSummary.worldReadable + ) + } + }.orEmpty() + } + .orEmpty() + SpaceHierarchyData( + rootSummary = root, + children = children, + childrenState = spaceDesc?.childrenState.orEmpty(), + nextToken = response.nextBatch ) } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/space/JoinSpaceTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/space/JoinSpaceTask.kt index e9d5ba51..7eeaed4f 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/space/JoinSpaceTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/space/JoinSpaceTask.kt @@ -19,7 +19,6 @@ package org.matrix.android.sdk.internal.session.space import io.realm.RealmConfiguration import kotlinx.coroutines.TimeoutCancellationException import org.matrix.android.sdk.api.session.room.model.Membership -import org.matrix.android.sdk.api.session.room.model.RoomType import org.matrix.android.sdk.api.session.space.JoinSpaceResult import org.matrix.android.sdk.internal.database.awaitNotEmptyResult import org.matrix.android.sdk.internal.database.model.RoomSummaryEntity @@ -84,39 +83,39 @@ internal class DefaultJoinSpaceTask @Inject constructor( // after that i should have the children (? do I need to paginate to get state) val summary = roomSummaryDataSource.getSpaceSummary(params.roomIdOrAlias) Timber.v("## Space: Found space summary Name:[${summary?.name}] children: ${summary?.spaceChildren?.size}") - summary?.spaceChildren?.forEach { +// summary?.spaceChildren?.forEach { // val childRoomSummary = it.roomSummary ?: return@forEach - Timber.v("## Space: Processing child :[${it.childRoomId}] autoJoin:${it.autoJoin}") - if (it.autoJoin) { - // I should try to join as well - if (it.roomType == RoomType.SPACE) { - // recursively join auto-joined child of this space? - when (val subspaceJoinResult = execute(JoinSpaceTask.Params(it.childRoomId, null, it.viaServers))) { - JoinSpaceResult.Success -> { - // nop - } - is JoinSpaceResult.Fail -> { - errors[it.childRoomId] = subspaceJoinResult.error - } - is JoinSpaceResult.PartialSuccess -> { - errors.putAll(subspaceJoinResult.failedRooms) - } - } - } else { - try { - Timber.v("## Space: Joining room child ${it.childRoomId}") - joinRoomTask.execute(JoinRoomTask.Params( - roomIdOrAlias = it.childRoomId, - reason = "Auto-join parent space", - viaServers = it.viaServers - )) - } catch (failure: Throwable) { - errors[it.childRoomId] = failure - Timber.e("## Space: Failed to join room child ${it.childRoomId}") - } - } - } - } +// Timber.v("## Space: Processing child :[${it.childRoomId}] suggested:${it.suggested}") +// if (it.autoJoin) { +// // I should try to join as well +// if (it.roomType == RoomType.SPACE) { +// // recursively join auto-joined child of this space? +// when (val subspaceJoinResult = execute(JoinSpaceTask.Params(it.childRoomId, null, it.viaServers))) { +// JoinSpaceResult.Success -> { +// // nop +// } +// is JoinSpaceResult.Fail -> { +// errors[it.childRoomId] = subspaceJoinResult.error +// } +// is JoinSpaceResult.PartialSuccess -> { +// errors.putAll(subspaceJoinResult.failedRooms) +// } +// } +// } else { +// try { +// Timber.v("## Space: Joining room child ${it.childRoomId}") +// joinRoomTask.execute(JoinRoomTask.Params( +// roomIdOrAlias = it.childRoomId, +// reason = "Auto-join parent space", +// viaServers = it.viaServers +// )) +// } catch (failure: Throwable) { +// errors[it.childRoomId] = failure +// Timber.e("## Space: Failed to join room child ${it.childRoomId}") +// } +// } +// } +// } return if (errors.isEmpty()) { JoinSpaceResult.Success diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/space/ResolveSpaceInfoTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/space/ResolveSpaceInfoTask.kt index d2be49b7..2a396d6e 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/space/ResolveSpaceInfoTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/space/ResolveSpaceInfoTask.kt @@ -24,24 +24,12 @@ import javax.inject.Inject internal interface ResolveSpaceInfoTask : Task<ResolveSpaceInfoTask.Params, SpacesResponse> { data class Params( val spaceId: String, - val maxRoomPerSpace: Int?, - val limit: Int, - val batchToken: String?, - val suggestedOnly: Boolean?, - val autoJoinOnly: Boolean? - ) { - companion object { - fun withId(spaceId: String, suggestedOnly: Boolean?, autoJoinOnly: Boolean?) = - Params( - spaceId = spaceId, - maxRoomPerSpace = 10, - limit = 20, - batchToken = null, - suggestedOnly = suggestedOnly, - autoJoinOnly = autoJoinOnly - ) - } - } + val limit: Int?, + val maxDepth: Int?, + val from: String?, + val suggestedOnly: Boolean? +// val autoJoinOnly: Boolean? + ) } internal class DefaultResolveSpaceInfoTask @Inject constructor( @@ -49,15 +37,13 @@ internal class DefaultResolveSpaceInfoTask @Inject constructor( private val globalErrorReceiver: GlobalErrorReceiver ) : ResolveSpaceInfoTask { override suspend fun execute(params: ResolveSpaceInfoTask.Params): SpacesResponse { - val body = SpaceSummaryParams( - maxRoomPerSpace = params.maxRoomPerSpace, - limit = params.limit, - batch = params.batchToken ?: "", - autoJoinedOnly = params.autoJoinOnly, - suggestedOnly = params.suggestedOnly - ) return executeRequest(globalErrorReceiver) { - spaceApi.getSpaces(params.spaceId, body) + spaceApi.getSpaceHierarchy( + spaceId = params.spaceId, + suggestedOnly = params.suggestedOnly, + limit = params.limit, + maxDepth = params.maxDepth, + from = params.from) } } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/space/SpaceApi.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/space/SpaceApi.kt index 0fcc95fd..edd10bc2 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/space/SpaceApi.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/space/SpaceApi.kt @@ -17,27 +17,25 @@ package org.matrix.android.sdk.internal.session.space import org.matrix.android.sdk.internal.network.NetworkConstants -import retrofit2.http.Body -import retrofit2.http.POST +import retrofit2.http.GET import retrofit2.http.Path +import retrofit2.http.Query internal interface SpaceApi { /** - * - * POST /_matrix/client/r0/rooms/{roomID}/spaces - * { - * "max_rooms_per_space": 5, // The maximum number of rooms/subspaces to return for a given space, if negative unbounded. default: -1. - * "auto_join_only": true, // If true, only return m.space.child events with auto_join:true, default: false, which returns all events. - * "limit": 100, // The maximum number of rooms/subspaces to return, server can override this, default: 100. - * "batch": "opaque_string" // A token to use if this is a subsequent HTTP hit, default: "". - * } - * - * Ref: - * - MSC 2946 https://github.com/matrix-org/matrix-doc/blob/kegan/spaces-summary/proposals/2946-spaces-summary.md - * - https://hackmd.io/fNYh4tjUT5mQfR1uuRzWDA + * @param suggestedOnly Optional. If true, return only child events and rooms where the m.space.child event has suggested: true. + * @param limit: Optional: a client-defined limit to the maximum number of rooms to return per page. Must be a non-negative integer. + * @param maxDepth: Optional: The maximum depth in the tree (from the root room) to return. + * The deepest depth returned will not include children events. Defaults to no-limit. Must be a non-negative integer. + * @param from: Optional. Pagination token given to retrieve the next set of rooms. + * Note that if a pagination token is provided, then the parameters given for suggested_only and max_depth must be the same. */ - @POST(NetworkConstants.URI_API_PREFIX_PATH_UNSTABLE + "org.matrix.msc2946/rooms/{roomId}/spaces") - suspend fun getSpaces(@Path("roomId") spaceId: String, - @Body params: SpaceSummaryParams): SpacesResponse + @GET(NetworkConstants.URI_API_PREFIX_PATH_UNSTABLE + "org.matrix.msc2946/rooms/{roomId}/hierarchy") + suspend fun getSpaceHierarchy( + @Path("roomId") spaceId: String, + @Query("suggested_only") suggestedOnly: Boolean?, + @Query("limit") limit: Int?, + @Query("max_depth") maxDepth: Int?, + @Query("from") from: String?): SpacesResponse } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/space/SpaceChildSummaryResponse.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/space/SpaceChildSummaryResponse.kt index 5021ff63..e0f273d0 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/space/SpaceChildSummaryResponse.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/space/SpaceChildSummaryResponse.kt @@ -18,14 +18,17 @@ package org.matrix.android.sdk.internal.session.space import com.squareup.moshi.Json import com.squareup.moshi.JsonClass +import org.matrix.android.sdk.api.session.events.model.Event +/** + * The fields are the same as those returned by /publicRooms (see spec), with the addition of: + * room_type: the value of the m.type field from the room's m.room.create event, if any. + * children_state: The m.space.child events of the room. + * For each event, only the following fields are included: type, state_key, content, room_id, sender, + * with the addition of: origin_server_ts: This is required for sorting of rooms as specified below. + */ @JsonClass(generateAdapter = true) internal data class SpaceChildSummaryResponse( - /** - * The total number of state events which point to or from this room (inbound/outbound edges). - * This includes all m.space.child events in the room, in addition to m.room.parent events which point to this room as a parent. - */ - @Json(name = "num_refs") val numRefs: Int? = null, /** * The room type, which is m.space for subspaces. @@ -33,6 +36,11 @@ internal data class SpaceChildSummaryResponse( */ @Json(name = "room_type") val roomType: String? = null, + /** The m.space.child events of the room. For each event, only the following fields are included: + * type, state_key, content, room_id, sender, with the addition of origin_server_ts. + */ + @Json(name = "children_state") val childrenState: List<Event>? = null, + /** * Aliases of the room. May be empty. */ diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/space/SpaceSummaryParams.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/space/SpaceSummaryParams.kt deleted file mode 100644 index 013db1c2..00000000 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/space/SpaceSummaryParams.kt +++ /dev/null @@ -1,34 +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.space - -import com.squareup.moshi.Json -import com.squareup.moshi.JsonClass - -@JsonClass(generateAdapter = true) -internal data class SpaceSummaryParams( - /** The maximum number of rooms/subspaces to return for a given space, if negative unbounded. default: -1 */ - @Json(name = "max_rooms_per_space") val maxRoomPerSpace: Int?, - /** The maximum number of rooms/subspaces to return, server can override this, default: 100 */ - @Json(name = "limit") val limit: Int?, - /** A token to use if this is a subsequent HTTP hit, default: "". */ - @Json(name = "batch") val batch: String = "", - /** whether we should only return children with the "suggested" flag set. */ - @Json(name = "suggested_only") val suggestedOnly: Boolean?, - /** whether we should only return children with the "suggested" flag set. */ - @Json(name = "auto_join_only") val autoJoinedOnly: Boolean? -) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/space/SpacesResponse.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/space/SpacesResponse.kt index 20d63c88..7d22dce9 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/space/SpacesResponse.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/space/SpacesResponse.kt @@ -18,14 +18,11 @@ package org.matrix.android.sdk.internal.session.space import com.squareup.moshi.Json import com.squareup.moshi.JsonClass -import org.matrix.android.sdk.api.session.events.model.Event @JsonClass(generateAdapter = true) internal data class SpacesResponse( /** Its presence indicates that there are more results to return. */ @Json(name = "next_batch") val nextBatch: String? = null, /** Rooms information like name/avatar/type ... */ - @Json(name = "rooms") val rooms: List<SpaceChildSummaryResponse>? = null, - /** These are the edges of the graph. The objects in the array are complete (or stripped?) m.room.parent or m.space.child events. */ - @Json(name = "events") val events: List<Event>? = null + @Json(name = "rooms") val rooms: List<SpaceChildSummaryResponse>? = null ) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/space/peeking/PeekSpaceTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/space/peeking/PeekSpaceTask.kt index f6b156a6..5cbaaa45 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/space/peeking/PeekSpaceTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/space/peeking/PeekSpaceTask.kt @@ -103,7 +103,7 @@ internal class DefaultPeekSpaceTask @Inject constructor( // can't peek :/ spaceChildResults.add( SpaceChildPeekResult( - childId, childPeek, entry.second?.autoJoin, entry.second?.order + childId, childPeek, entry.second?.order ) ) // continue to next child @@ -116,7 +116,7 @@ internal class DefaultPeekSpaceTask @Inject constructor( SpaceSubChildPeekResult( childId, childPeek, - entry.second?.autoJoin, +// entry.second?.autoJoin, entry.second?.order, peekChildren(childStateEvents, depth + 1, maxDepth) ) @@ -127,7 +127,7 @@ internal class DefaultPeekSpaceTask @Inject constructor( Timber.v("## SPACE_PEEK: room child $entry") spaceChildResults.add( SpaceChildPeekResult( - childId, childPeek, entry.second?.autoJoin, entry.second?.order + childId, childPeek, entry.second?.order ) ) } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/space/peeking/SpacePeekResult.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/space/peeking/SpacePeekResult.kt index 1df62e94..44d879f0 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/space/peeking/SpacePeekResult.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/space/peeking/SpacePeekResult.kt @@ -28,21 +28,21 @@ data class SpacePeekSummary( interface ISpaceChild { val id: String val roomPeekResult: PeekResult - val default: Boolean? +// val default: Boolean? val order: String? } data class SpaceChildPeekResult( override val id: String, override val roomPeekResult: PeekResult, - override val default: Boolean? = null, +// override val default: Boolean? = null, override val order: String? = null ) : ISpaceChild data class SpaceSubChildPeekResult( override val id: String, override val roomPeekResult: PeekResult, - override val default: Boolean?, +// override val default: Boolean?, override val order: String?, val children: List<ISpaceChild> ) : ISpaceChild 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 c3586bce..830e666c 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 @@ -221,7 +221,7 @@ internal class RoomSyncHandler @Inject constructor(private val readReceiptHandle } // Give info to crypto module cryptoService.onStateEvent(roomId, event) - roomMemberEventHandler.handle(realm, roomId, event) + roomMemberEventHandler.handle(realm, roomId, event, aggregator) } } if (roomSync.timeline?.events?.isNotEmpty() == true) { @@ -233,7 +233,8 @@ internal class RoomSyncHandler @Inject constructor(private val readReceiptHandle roomSync.timeline.prevToken, roomSync.timeline.limited, insertType, - syncLocalTimestampMillis + syncLocalTimestampMillis, + aggregator ) roomEntity.addIfNecessary(chunkEntity) } @@ -337,7 +338,8 @@ internal class RoomSyncHandler @Inject constructor(private val readReceiptHandle prevToken: String? = null, isLimited: Boolean = true, insertType: EventInsertType, - syncLocalTimestampMillis: Long): ChunkEntity { + syncLocalTimestampMillis: Long, + aggregator: SyncResponsePostTreatmentAggregator): ChunkEntity { val lastChunk = ChunkEntity.findLastForwardChunkOfRoom(realm, roomEntity.roomId) val chunkEntity = if (!isLimited && lastChunk != null) { lastChunk @@ -371,7 +373,7 @@ internal class RoomSyncHandler @Inject constructor(private val readReceiptHandle if (event.type == EventType.STATE_ROOM_MEMBER) { val fixedContent = event.getFixedRoomMemberContent() roomMemberContentsByUser[event.stateKey] = fixedContent - roomMemberEventHandler.handle(realm, roomEntity.roomId, event.stateKey, fixedContent) + roomMemberEventHandler.handle(realm, roomEntity.roomId, event.stateKey, fixedContent, aggregator) } } roomMemberContentsByUser.getOrPut(event.senderId) { 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 index ea10a32f..9bb2bfc9 100644 --- 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 @@ -19,4 +19,6 @@ package org.matrix.android.sdk.internal.session.sync internal class SyncResponsePostTreatmentAggregator { // List of RoomId val ephemeralFilesToDelete = mutableListOf<String>() + // Map of roomId to directUserId + val directChatsToCheck = mutableMapOf<String, 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 index 12b77c70..db1100d7 100644 --- 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 @@ -16,13 +16,20 @@ package org.matrix.android.sdk.internal.session.sync +import org.matrix.android.sdk.api.MatrixPatterns +import org.matrix.android.sdk.internal.session.sync.model.accountdata.toMutable +import org.matrix.android.sdk.internal.session.user.accountdata.DirectChatsHelper +import org.matrix.android.sdk.internal.session.user.accountdata.UpdateUserAccountDataTask import javax.inject.Inject internal class SyncResponsePostTreatmentAggregatorHandler @Inject constructor( - private val ephemeralTemporaryStore: RoomSyncEphemeralTemporaryStore + private val directChatsHelper: DirectChatsHelper, + private val ephemeralTemporaryStore: RoomSyncEphemeralTemporaryStore, + private val updateUserAccountDataTask: UpdateUserAccountDataTask ) { - fun handle(synResHaResponsePostTreatmentAggregator: SyncResponsePostTreatmentAggregator) { + suspend fun handle(synResHaResponsePostTreatmentAggregator: SyncResponsePostTreatmentAggregator) { cleanupEphemeralFiles(synResHaResponsePostTreatmentAggregator.ephemeralFilesToDelete) + updateDirectUserIds(synResHaResponsePostTreatmentAggregator.directChatsToCheck) } private fun cleanupEphemeralFiles(ephemeralFilesToDelete: List<String>) { @@ -30,4 +37,33 @@ internal class SyncResponsePostTreatmentAggregatorHandler @Inject constructor( ephemeralTemporaryStore.delete(it) } } + + private suspend fun updateDirectUserIds(directUserIdsToUpdate: Map<String, String>) { + val directChats = directChatsHelper.getLocalDirectMessages().toMutable() + var hasUpdate = false + directUserIdsToUpdate.forEach { (roomId, candidateUserId) -> + // consider room is a DM if referenced in the DM dictionary + val currentDirectUserId = directChats.firstNotNullOfOrNull { (userId, roomIds) -> userId.takeIf { roomId in roomIds } } + // update directUserId with the given candidateUserId if it mismatches the current one + if (currentDirectUserId != null && !MatrixPatterns.isUserId(currentDirectUserId)) { + // link roomId with the matrix id + directChats + .getOrPut(candidateUserId) { arrayListOf() } + .apply { + if (!contains(roomId)) { + hasUpdate = true + add(roomId) + } + } + + // remove roomId from currentDirectUserId entry + hasUpdate = hasUpdate or(directChats[currentDirectUserId]?.remove(roomId) == true) + // remove currentDirectUserId entry if there is no attached room anymore + hasUpdate = hasUpdate or(directChats.takeIf { it[currentDirectUserId].isNullOrEmpty() }?.remove(currentDirectUserId) != null) + } + } + if (hasUpdate) { + updateUserAccountDataTask.execute(UpdateUserAccountDataTask.DirectChatParams(directMessages = directChats)) + } + } } 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 b8d987d5..110e7781 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 @@ -53,6 +53,7 @@ import org.matrix.android.sdk.internal.session.sync.model.accountdata.Breadcrumb import org.matrix.android.sdk.internal.session.sync.model.accountdata.DirectMessagesContent import org.matrix.android.sdk.internal.session.sync.model.accountdata.IgnoredUsersContent import org.matrix.android.sdk.internal.session.sync.model.accountdata.UserAccountDataSync +import org.matrix.android.sdk.internal.session.sync.model.accountdata.toMutable import org.matrix.android.sdk.internal.session.user.accountdata.DirectChatsHelper import org.matrix.android.sdk.internal.session.user.accountdata.UpdateUserAccountDataTask import timber.log.Timber @@ -83,7 +84,7 @@ internal class UserAccountDataSyncHandler @Inject constructor( // If we get some direct chat invites, we synchronize the user account data including those. suspend fun synchronizeWithServerIfNeeded(invites: Map<String, InvitedRoomSync>) { if (invites.isNullOrEmpty()) return - val directChats = directChatsHelper.getLocalUserAccount() + val directChats = directChatsHelper.getLocalDirectMessages().toMutable() var hasUpdate = false monarchy.doWithRealm { realm -> invites.forEach { (roomId, _) -> 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 41173dea..7c73f1fe 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 @@ -20,3 +20,12 @@ package org.matrix.android.sdk.internal.session.sync.model.accountdata * Keys are userIds, values are list of roomIds */ internal typealias DirectMessagesContent = Map<String, List<String>> + +/** + * Returns a new [MutableMap] with all elements of this collection. + */ +internal fun DirectMessagesContent.toMutable(): MutableMap<String, MutableList<String>> { + return map { it.key to it.value.toMutableList() } + .toMap() + .toMutableMap() +} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/user/accountdata/DirectChatsHelper.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/user/accountdata/DirectChatsHelper.kt index a9e50897..37030576 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/user/accountdata/DirectChatsHelper.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/user/accountdata/DirectChatsHelper.kt @@ -21,6 +21,7 @@ import org.matrix.android.sdk.internal.database.query.getDirectRooms import org.matrix.android.sdk.internal.di.SessionDatabase import io.realm.Realm import io.realm.RealmConfiguration +import org.matrix.android.sdk.internal.session.sync.model.accountdata.DirectMessagesContent import javax.inject.Inject internal class DirectChatsHelper @Inject constructor(@SessionDatabase @@ -29,7 +30,7 @@ internal class DirectChatsHelper @Inject constructor(@SessionDatabase /** * @return a map of userId <-> list of roomId */ - fun getLocalUserAccount(filterRoomId: String? = null): MutableMap<String, MutableList<String>> { + fun getLocalDirectMessages(filterRoomId: String? = null): DirectMessagesContent { return Realm.getInstance(realmConfiguration).use { realm -> // Makes sure we have the latest realm updates, this is important as we sent this information to the server. realm.refresh() 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 5912dc7b..dfe4b6b8 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 @@ -52,7 +52,7 @@ internal class DefaultWidgetService @Inject constructor(private val widgetManage return widgetManager.getWidgetComputedUrl(widget, isLightTheme) } - override fun getRoomWidgetsLive( +override fun getRoomWidgetsLive( roomId: String, widgetId: QueryStringValue, widgetTypes: Set<String>?, diff --git a/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/task/CoroutineSequencersTest.kt b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/task/CoroutineSequencersTest.kt index 74b6c03d..3572a1a5 100644 --- a/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/task/CoroutineSequencersTest.kt +++ b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/task/CoroutineSequencersTest.kt @@ -32,6 +32,7 @@ class CoroutineSequencersTest: MatrixTest { private val dispatcher = Executors.newSingleThreadExecutor().asCoroutineDispatcher() @Test + @Suppress("EXPERIMENTAL_API_USAGE") fun sequencer_should_run_sequential() { val sequencer = SemaphoreCoroutineSequencer() val results = ArrayList<String>() @@ -60,6 +61,7 @@ class CoroutineSequencersTest: MatrixTest { } @Test + @Suppress("EXPERIMENTAL_API_USAGE") fun sequencer_should_run_parallel() { val sequencer1 = SemaphoreCoroutineSequencer() val sequencer2 = SemaphoreCoroutineSequencer() @@ -86,6 +88,7 @@ class CoroutineSequencersTest: MatrixTest { } @Test + @Suppress("EXPERIMENTAL_API_USAGE") fun sequencer_should_jump_to_next_when_current_job_canceled() { val sequencer = SemaphoreCoroutineSequencer() val results = ArrayList<String>() -- GitLab