From ea8f9bdec679684fc1489da9a71940ee3a11677b Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Mon, 19 Jul 2021 15:36:06 +0200 Subject: [PATCH 01/17] Upgrade gradle wrapper, kotlin version and build tools --- build.gradle | 4 ++-- gradle/wrapper/gradle-wrapper.properties | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/build.gradle b/build.gradle index fd8064ca63..5752df90a4 100644 --- a/build.gradle +++ b/build.gradle @@ -2,7 +2,7 @@ buildscript { // Ref: https://kotlinlang.org/releases.html - ext.kotlin_version = '1.5.10' + ext.kotlin_version = '1.5.21' ext.kotlin_coroutines_version = "1.5.0" repositories { google() @@ -12,7 +12,7 @@ buildscript { } } dependencies { - classpath 'com.android.tools.build:gradle:4.2.1' + classpath 'com.android.tools.build:gradle:4.2.2' classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" // NOTE: Do not place your application dependencies here; they belong diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index e1e2fd2c75..17ba19021b 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionSha256Sum=13bf8d3cf8eeeb5770d19741a59bde9bd966dd78d17f1bbad787a05ef19d1c2d -distributionUrl=https\://services.gradle.org/distributions/gradle-7.0.2-all.zip +distributionSha256Sum=9bb8bc05f562f2d42bdf1ba8db62f6b6fa1c3bf6c392228802cc7cb0578fe7e0 +distributionUrl=https\://services.gradle.org/distributions/gradle-7.1.1-all.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists From d314df23cbf002595612d67572a4f6e87be49cba Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Mon, 19 Jul 2021 15:39:22 +0200 Subject: [PATCH 02/17] Import 1.1.13 from Element --- matrix-sdk-android/build.gradle | 41 +-- .../org/matrix/android/sdk/SpaceOrderTest.kt | 260 ++++++++++++++++++ .../org/matrix/android/sdk/StringOrderTest.kt | 110 ++++++++ .../android/sdk/common/CommonTestHelper.kt | 2 +- .../crypto/keysbackup/KeysBackupTest.kt | 2 +- .../sdk/internal/crypto/ssss/QuadSTests.kt | 24 +- .../matrix/android/sdk/api/MatrixPatterns.kt | 37 ++- .../auth/data/HomeServerConnectionConfig.kt | 38 +-- .../sdk/api/auth/data/SessionParams.kt | 11 +- .../registration/RegistrationFlowResponse.kt | 2 +- .../sdk/api/auth/wellknown/WellknownResult.kt | 5 - .../sdk/api/failure/MatrixIdFailure.kt | 21 ++ .../matrix/android/sdk/api/raw/RawService.kt | 4 +- .../matrix/android/sdk/api/session/Session.kt | 6 +- .../accountdata/SessionAccountDataService.kt | 66 +++++ ...ntDataEvent.kt => UserAccountDataEvent.kt} | 4 +- .../sdk/api/session/call/CallListener.kt | 6 + .../sdk/api/session/crypto/CryptoService.kt | 7 +- .../crypto/keysbackup/KeysBackupService.kt | 2 +- .../crypto/keysbackup/KeysBackupState.kt | 2 +- .../sdk/api/session/events/model/Event.kt | 2 +- .../sdk/api/session/events/model/EventType.kt | 2 + .../homeserver/HomeServerCapabilities.kt | 8 +- .../session/homeserver/RoomVersionModel.kt | 32 +++ .../api/session/identity/IdentityService.kt | 2 +- .../session/room/AliasAvailabilityResult.kt | 24 ++ .../android/sdk/api/session/room/Room.kt | 6 +- .../api/session/room/RoomDirectoryService.kt | 2 + .../sdk/api/session/room/RoomService.kt | 6 + .../room/accountdata/RoomAccountDataEvent.kt | 29 ++ .../accountdata/RoomAccountDataService.kt} | 14 +- .../room/accountdata/RoomAccountDataTypes.kt | 1 + .../model/call/CallAssertedIdentityContent.kt | 57 ++++ .../room/model/message/MessageAudioContent.kt | 2 +- .../room/model/message/MessageFileContent.kt | 3 +- .../room/model/message/MessageImageContent.kt | 3 +- .../model/message/MessageStickerContent.kt | 2 +- .../room/model/message/MessageVideoContent.kt | 2 +- .../room/version/RoomVersionService.kt | 45 +++ .../android/sdk/api/session/space/Space.kt | 3 + .../sdk/api/session/space/SpaceOrderUtils.kt | 105 +++++++ .../sdk/api/session/space/SpaceService.kt | 6 +- .../session/space/model/SpaceOrderContent.kt | 37 +++ .../space/model/TopLevelSpaceComparator.kt | 44 +++ .../android/sdk/api/util/StringOrderUtils.kt | 87 ++++++ .../android/sdk/internal/SessionManager.kt | 2 +- .../android/sdk/internal/auth/AuthAPI.kt | 10 +- .../auth/DefaultAuthenticationService.kt | 135 ++++----- .../auth/IsValidClientServerApiTask.kt | 2 +- .../sdk/internal/auth/SessionCreator.kt | 6 +- .../{RiotConfig.kt => WebClientConfig.kt} | 10 +- .../internal/auth/db/AuthRealmMigration.kt | 39 ++- .../internal/auth/login/DirectLoginTask.kt | 2 +- .../sdk/internal/crypto/CryptoModule.kt | 3 +- .../internal/crypto/DefaultCryptoService.kt | 63 ++--- .../algorithms/megolm/MXMegolmDecryption.kt | 2 +- .../attachments/MXEncryptedAttachments.kt | 13 +- .../sdk/internal/crypto/model/MXDeviceInfo.kt | 2 +- .../crypto/model/rest/EncryptedFileInfo.kt | 6 - .../DefaultSharedSecretStorageService.kt | 24 +- .../store/db/RealmCryptoStoreMigration.kt | 24 +- .../tasks/InitializeCrossSigningTask.kt | 2 +- .../database/RealmSessionStoreMigration.kt | 38 ++- .../SessionRealmConfigurationFactory.kt | 3 +- .../database/mapper/AccountDataMapper.kt | 12 +- .../mapper/HomeServerCapabilitiesMapper.kt | 32 ++- .../model/HomeServerCapabilitiesEntity.kt | 1 + .../database/model/UserAccountDataEntity.kt | 2 +- .../internal/federation/FederationModule.kt | 3 +- .../legacy/DefaultLegacySessionImporter.kt | 9 +- .../sdk/internal/legacy/riot/Credentials.java | 2 +- .../riot/HomeServerConnectionConfig.java | 22 +- .../internal/legacy/riot/LoginStorage.java | 8 +- .../sdk/internal/network/UserAgentHolder.kt | 2 +- .../sdk/internal/network/ssl/CertUtil.kt | 2 +- .../sdk/internal/raw/DefaultRawService.kt | 5 +- .../sdk/internal/session/DefaultSession.kt | 9 +- .../sdk/internal/session/SessionModule.kt | 8 +- .../session/call/CallEventProcessor.kt | 12 +- .../session/call/CallListenersDispatcher.kt | 5 + .../session/call/CallSignalingHandler.kt | 34 ++- .../session/cleanup/CleanupSession.kt | 10 +- .../content/DefaultContentUrlResolver.kt | 2 +- .../session/content/UploadContentWorker.kt | 4 +- .../session/events/DefaultEventService.kt | 2 +- .../DefaultHomeServerCapabilitiesService.kt | 14 +- .../homeserver/GetCapabilitiesResult.kt | 25 +- .../GetHomeServerCapabilitiesTask.kt | 11 +- .../HomeServerCapabilitiesDataSource.kt | 38 +++ .../identity/DefaultIdentityService.kt | 4 +- .../session/identity/IdentityAuthAPI.kt | 2 +- .../session/identity/IdentityModule.kt | 3 +- .../db/RealmIdentityStoreMigration.kt | 7 +- .../integrationmanager/IntegrationManager.kt | 8 +- .../session/permalinks/ViaParameterFinder.kt | 5 +- .../internal/session/profile/ProfileAPI.kt | 2 +- .../sdk/internal/session/room/DefaultRoom.kt | 11 +- .../room/DefaultRoomDirectoryService.kt | 15 +- .../session/room/DefaultRoomService.kt | 4 + .../sdk/internal/session/room/RoomAPI.kt | 11 + .../sdk/internal/session/room/RoomFactory.kt | 7 +- .../sdk/internal/session/room/RoomModule.kt | 5 + .../internal/session/room/RoomUpgradeBody.kt | 26 ++ .../session/room/RoomUpgradeResponse.kt | 26 ++ ...ce.kt => DefaultRoomAccountDataService.kt} | 22 +- .../accountdata/RoomAccountDataDataSource.kt | 59 ++-- .../alias/RoomAliasAvailabilityChecker.kt | 7 +- .../room/create/CreateRoomBodyBuilder.kt | 2 +- .../RoomChangeMembershipStateDataSource.kt | 3 +- .../room/membership/joining/JoinRoomTask.kt | 4 + .../session/room/send/DefaultSendService.kt | 6 +- .../queue/EventSenderProcessorCoroutine.kt | 2 +- .../send/queue/EventSenderProcessorThread.kt | 2 +- .../queue/HomeServerAvailabilityChecker.kt | 4 +- .../room/summary/RoomSummaryUpdater.kt | 1 - .../session/room/timeline/DefaultTimeline.kt | 5 +- .../room/typing/DefaultTypingService.kt | 82 +++--- .../session/room/typing/SendTypingTask.kt | 9 +- .../room/version/DefaultRoomVersionService.kt | 84 ++++++ .../room/version/RoomVersionUpgradeTask.kt | 66 +++++ .../internal/session/space/DefaultSpace.kt | 6 + .../session/space/DefaultSpaceService.kt | 3 +- .../sdk/internal/session/sync/SyncTask.kt | 2 +- .../sync/UserAccountDataSyncHandler.kt | 10 +- .../model/accountdata/UserAccountDataSync.kt | 4 +- .../parsing/RoomSyncAccountDataHandler.kt | 1 - ...kt => DefaultSessionAccountDataService.kt} | 39 ++- .../user/accountdata/DirectChatsHelper.kt | 4 +- .../accountdata/UserAccountDataDataSource.kt | 10 +- .../internal/session/widgets/WidgetManager.kt | 12 +- .../widgets/helper/UserAccountWidgets.kt | 4 +- .../internal/wellknown/GetWellknownTask.kt | 19 +- .../internal/worker/WorkerParamsFactory.kt | 2 +- .../androidsdk/crypto/data/MXDeviceInfo.java | 4 +- .../src/main/res/values-fr/strings_sas.xml | 4 +- .../src/main/res/values-hu/strings_sas.xml | 68 +++++ .../src/main/res/values-tzm/strings_sas.xml | 30 ++ 137 files changed, 2018 insertions(+), 532 deletions(-) create mode 100644 matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/SpaceOrderTest.kt create mode 100644 matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/StringOrderTest.kt create mode 100644 matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/failure/MatrixIdFailure.kt create mode 100644 matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/accountdata/SessionAccountDataService.kt rename matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/accountdata/{AccountDataEvent.kt => UserAccountDataEvent.kt} (91%) create mode 100644 matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/homeserver/RoomVersionModel.kt create mode 100644 matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/AliasAvailabilityResult.kt create mode 100644 matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/accountdata/RoomAccountDataEvent.kt rename matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/{accountdata/AccountDataService.kt => room/accountdata/RoomAccountDataService.kt} (81%) create mode 100644 matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/call/CallAssertedIdentityContent.kt create mode 100644 matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/version/RoomVersionService.kt create mode 100644 matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/space/SpaceOrderUtils.kt create mode 100644 matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/space/model/SpaceOrderContent.kt create mode 100644 matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/space/model/TopLevelSpaceComparator.kt create mode 100644 matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/util/StringOrderUtils.kt rename matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/data/{RiotConfig.kt => WebClientConfig.kt} (84%) create mode 100644 matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/homeserver/HomeServerCapabilitiesDataSource.kt create mode 100644 matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/RoomUpgradeBody.kt create mode 100644 matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/RoomUpgradeResponse.kt rename matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/accountdata/{RoomAccountDataService.kt => DefaultRoomAccountDataService.kt} (72%) create mode 100644 matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/version/DefaultRoomVersionService.kt create mode 100644 matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/version/RoomVersionUpgradeTask.kt rename matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/user/accountdata/{UserAccountDataService.kt => DefaultSessionAccountDataService.kt} (55%) create mode 100644 matrix-sdk-android/src/main/res/values-hu/strings_sas.xml create mode 100644 matrix-sdk-android/src/main/res/values-tzm/strings_sas.xml diff --git a/matrix-sdk-android/build.gradle b/matrix-sdk-android/build.gradle index fbd7f57ccd..391e408768 100644 --- a/matrix-sdk-android/build.gradle +++ b/matrix-sdk-android/build.gradle @@ -3,14 +3,13 @@ apply plugin: 'kotlin-android' apply plugin: 'kotlin-kapt' apply plugin: 'kotlin-parcelize' apply plugin: 'realm-android' -apply plugin: 'maven-publish' buildscript { repositories { mavenCentral() } dependencies { - classpath "io.realm:realm-gradle-plugin:10.5.0" + classpath "io.realm:realm-gradle-plugin:10.6.1" } } @@ -22,7 +21,7 @@ android { minSdkVersion 21 targetSdkVersion 30 versionCode 1 - versionName "1.1.9" + versionName "1.1.13" // Multidex is useful for tests multiDexEnabled true testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" @@ -113,7 +112,7 @@ dependencies { def lifecycle_version = '2.2.0' def arch_version = '2.1.0' def markwon_version = '3.1.0' - def daggerVersion = '2.36' + def daggerVersion = '2.37' def work_version = '2.5.0' def retrofit_version = '2.9.0' @@ -122,7 +121,7 @@ dependencies { implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$kotlin_coroutines_version" implementation "androidx.appcompat:appcompat:1.3.0" - implementation "androidx.core:core-ktx:1.5.0" + implementation "androidx.core:core-ktx:1.6.0" implementation "androidx.lifecycle:lifecycle-extensions:$lifecycle_version" implementation "androidx.lifecycle:lifecycle-common-java8:$lifecycle_version" @@ -170,40 +169,30 @@ dependencies { implementation 'com.otaliastudios:transcoder:0.10.3' // Phone number https://github.com/google/libphonenumber - implementation 'com.googlecode.libphonenumber:libphonenumber:8.12.24' + implementation 'com.googlecode.libphonenumber:libphonenumber:8.12.27' testImplementation 'junit:junit:4.13.2' testImplementation 'org.robolectric:robolectric:4.5.1' //testImplementation 'org.robolectric:shadows-support-v4:3.0' // Note: version sticks to 1.9.2 due to https://github.com/mockk/mockk/issues/281 - testImplementation 'io.mockk:mockk:1.11.0' - testImplementation 'org.amshove.kluent:kluent-android:1.65' + testImplementation 'io.mockk:mockk:1.12.0' + testImplementation 'org.amshove.kluent:kluent-android:1.68' testImplementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$kotlin_coroutines_version" // Plant Timber tree for test testImplementation 'net.lachlanmckee:timber-junit-rule:1.0.1' kaptAndroidTest "com.google.dagger:dagger-compiler:$daggerVersion" - androidTestImplementation 'androidx.test:core:1.3.0' - androidTestImplementation 'androidx.test:runner:1.3.0' - androidTestImplementation 'androidx.test:rules:1.3.0' - androidTestImplementation 'androidx.test.ext:junit:1.1.2' - androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0' - androidTestImplementation 'org.amshove.kluent:kluent-android:1.65' - androidTestImplementation 'io.mockk:mockk-android:1.11.0' + androidTestImplementation 'androidx.test:core:1.4.0' + androidTestImplementation 'androidx.test:runner:1.4.0' + androidTestImplementation 'androidx.test:rules:1.4.0' + androidTestImplementation 'androidx.test.ext:junit:1.1.3' + androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0' + androidTestImplementation 'org.amshove.kluent:kluent-android:1.68' + androidTestImplementation 'io.mockk:mockk-android:1.12.0' androidTestImplementation "androidx.arch.core:core-testing:$arch_version" androidTestImplementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$kotlin_coroutines_version" // Plant Timber tree for test androidTestImplementation 'net.lachlanmckee:timber-junit-rule:1.0.1' - androidTestUtil 'androidx.test:orchestrator:1.3.0' -} - -project.afterEvaluate { - publishing { - publications { - release(MavenPublication) { - from components.release - } - } - } + androidTestUtil 'androidx.test:orchestrator:1.4.0' } diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/SpaceOrderTest.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/SpaceOrderTest.kt new file mode 100644 index 0000000000..3270dfb757 --- /dev/null +++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/SpaceOrderTest.kt @@ -0,0 +1,260 @@ +/* + * Copyright 2021 The Matrix.org Foundation C.I.C. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.matrix.android.sdk + +import org.amshove.kluent.internal.assertEquals +import org.junit.Assert +import org.junit.Test +import org.matrix.android.sdk.api.session.space.SpaceOrderUtils + +class SpaceOrderTest { + + @Test + fun testOrderBetweenNodesWithOrder() { + val orderedSpaces = listOf( + "roomId1" to "a", + "roomId2" to "m", + "roomId3" to "z" + ).assertSpaceOrdered() + + val orderCommand = SpaceOrderUtils.orderCommandsForMove(orderedSpaces, "roomId1", 1) + + Assert.assertTrue("Only one order should be changed", orderCommand.size == 1) + Assert.assertTrue("Moved space order should change", orderCommand.first().spaceId == "roomId1") + + Assert.assertTrue("m" < orderCommand[0].order) + Assert.assertTrue(orderCommand[0].order < "z") + } + + @Test + fun testMoveLastBetweenNodesWithOrder() { + val orderedSpaces = listOf( + "roomId1" to "a", + "roomId2" to "m", + "roomId3" to "z" + ).assertSpaceOrdered() + + val orderCommand = SpaceOrderUtils.orderCommandsForMove(orderedSpaces, "roomId1", 2) + + Assert.assertTrue("Only one order should be changed", orderCommand.size == 1) + Assert.assertTrue("Moved space order should change", orderCommand.first().spaceId == "roomId1") + + Assert.assertTrue("z" < orderCommand[0].order) + } + + @Test + fun testMoveUpNoOrder() { + val orderedSpaces = listOf( + "roomId1" to null, + "roomId2" to null, + "roomId3" to null + ).assertSpaceOrdered() + + val orderCommand = SpaceOrderUtils.orderCommandsForMove(orderedSpaces, "roomId1", 1) + + Assert.assertTrue("2 orders change should be needed", orderCommand.size == 2) + + val reOrdered = reOrderWithCommands(orderedSpaces, orderCommand) + + Assert.assertEquals("roomId2", reOrdered[0].first) + Assert.assertEquals("roomId1", reOrdered[1].first) + Assert.assertEquals("roomId3", reOrdered[2].first) + } + + @Test + fun testMoveUpNotEnoughSpace() { + val orderedSpaces = listOf( + "roomId1" to "a", + "roomId2" to "j", + "roomId3" to "k" + ).assertSpaceOrdered() + + val orderCommand = SpaceOrderUtils.orderCommandsForMove(orderedSpaces, "roomId1", 1) + + Assert.assertTrue("more orders change should be needed", orderCommand.size > 1) + + val reOrdered = reOrderWithCommands(orderedSpaces, orderCommand) + + Assert.assertEquals("roomId2", reOrdered[0].first) + Assert.assertEquals("roomId1", reOrdered[1].first) + Assert.assertEquals("roomId3", reOrdered[2].first) + } + + @Test + fun testMoveEndNoOrder() { + val orderedSpaces = listOf( + "roomId1" to null, + "roomId2" to null, + "roomId3" to null + ).assertSpaceOrdered() + + val orderCommand = SpaceOrderUtils.orderCommandsForMove(orderedSpaces, "roomId1", 2) + + // Actually 2 could be enough... as it's last it can stays with null + Assert.assertEquals("3 orders change should be needed", 3, orderCommand.size) + + val reOrdered = reOrderWithCommands(orderedSpaces, orderCommand) + + Assert.assertEquals("roomId2", reOrdered[0].first) + Assert.assertEquals("roomId3", reOrdered[1].first) + Assert.assertEquals("roomId1", reOrdered[2].first) + } + + @Test + fun testMoveUpBiggerOrder() { + val orderedSpaces = listOf( + "roomId1" to "aaaa", + "roomId2" to "ffff", + "roomId3" to "pppp", + "roomId4" to null, + "roomId5" to null, + "roomId6" to null + ).assertSpaceOrdered() + + val orderCommand = SpaceOrderUtils.orderCommandsForMove(orderedSpaces, "roomId2", 3) + + // Actually 2 could be enough... as it's last it can stays with null + Assert.assertEquals("3 orders change should be needed", 3, orderCommand.size) + + val reOrdered = reOrderWithCommands(orderedSpaces, orderCommand) + + Assert.assertEquals("roomId1", reOrdered[0].first) + Assert.assertEquals("roomId3", reOrdered[1].first) + Assert.assertEquals("roomId4", reOrdered[2].first) + Assert.assertEquals("roomId5", reOrdered[3].first) + Assert.assertEquals("roomId2", reOrdered[4].first) + Assert.assertEquals("roomId6", reOrdered[5].first) + } + + @Test + fun testMoveDownBetweenNodesWithOrder() { + val orderedSpaces = listOf( + "roomId1" to "a", + "roomId2" to "m", + "roomId3" to "z" + ).assertSpaceOrdered() + + val orderCommand = SpaceOrderUtils.orderCommandsForMove(orderedSpaces, "roomId3", -1) + + Assert.assertTrue("Only one order should be changed", orderCommand.size == 1) + Assert.assertTrue("Moved space order should change", orderCommand.first().spaceId == "roomId3") + + val reOrdered = reOrderWithCommands(orderedSpaces, orderCommand) + + Assert.assertEquals("roomId1", reOrdered[0].first) + Assert.assertEquals("roomId3", reOrdered[1].first) + Assert.assertEquals("roomId2", reOrdered[2].first) + } + + @Test + fun testMoveDownNoOrder() { + val orderedSpaces = listOf( + "roomId1" to null, + "roomId2" to null, + "roomId3" to null + ).assertSpaceOrdered() + + val orderCommand = SpaceOrderUtils.orderCommandsForMove(orderedSpaces, "roomId3", -1) + + Assert.assertTrue("2 orders change should be needed", orderCommand.size == 2) + + val reOrdered = reOrderWithCommands(orderedSpaces, orderCommand) + + Assert.assertEquals("roomId1", reOrdered[0].first) + Assert.assertEquals("roomId3", reOrdered[1].first) + Assert.assertEquals("roomId2", reOrdered[2].first) + } + + @Test + fun testMoveDownBiggerOrder() { + val orderedSpaces = listOf( + "roomId1" to "aaaa", + "roomId2" to "ffff", + "roomId3" to "pppp", + "roomId4" to null, + "roomId5" to null, + "roomId6" to null + ).assertSpaceOrdered() + + val orderCommand = SpaceOrderUtils.orderCommandsForMove(orderedSpaces, "roomId5", -4) + + Assert.assertEquals("1 order change should be needed", 1, orderCommand.size) + + val reOrdered = reOrderWithCommands(orderedSpaces, orderCommand) + + Assert.assertEquals("roomId5", reOrdered[0].first) + Assert.assertEquals("roomId1", reOrdered[1].first) + Assert.assertEquals("roomId2", reOrdered[2].first) + Assert.assertEquals("roomId3", reOrdered[3].first) + Assert.assertEquals("roomId4", reOrdered[4].first) + Assert.assertEquals("roomId6", reOrdered[5].first) + } + + @Test + fun testMultipleMoveOrder() { + val orderedSpaces = listOf( + "roomId1" to null, + "roomId2" to null, + "roomId3" to null, + "roomId4" to null, + "roomId5" to null, + "roomId6" to null + ).assertSpaceOrdered() + + // move 5 to Top + val fiveToTop = SpaceOrderUtils.orderCommandsForMove(orderedSpaces, "roomId5", -4) + + val fiveTopReOrder = reOrderWithCommands(orderedSpaces, fiveToTop) + + // now move 4 to second + val orderCommand = SpaceOrderUtils.orderCommandsForMove(fiveTopReOrder, "roomId4", -3) + + val reOrdered = reOrderWithCommands(fiveTopReOrder, orderCommand) + // second order should cost 1 re-order + Assert.assertEquals(1, orderCommand.size) + + Assert.assertEquals("roomId5", reOrdered[0].first) + Assert.assertEquals("roomId4", reOrdered[1].first) + Assert.assertEquals("roomId1", reOrdered[2].first) + Assert.assertEquals("roomId2", reOrdered[3].first) + Assert.assertEquals("roomId3", reOrdered[4].first) + Assert.assertEquals("roomId6", reOrdered[5].first) + } + + @Test + fun testComparator() { + listOf( + "roomId2" to "a", + "roomId1" to "b", + "roomId3" to null, + "roomId4" to null + ).assertSpaceOrdered() + } + + private fun reOrderWithCommands(orderedSpaces: List>, orderCommand: List) = + orderedSpaces.map { orderInfo -> + orderInfo.first to (orderCommand.find { it.spaceId == orderInfo.first }?.order ?: orderInfo.second) + } + .sortedWith(testSpaceComparator) + + private fun List>.assertSpaceOrdered(): List> { + assertEquals(this, this.sortedWith(testSpaceComparator)) + return this + } + + private val testSpaceComparator = compareBy, String?>(nullsLast()) { it.second }.thenBy { it.first } +} diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/StringOrderTest.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/StringOrderTest.kt new file mode 100644 index 0000000000..a625362c04 --- /dev/null +++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/StringOrderTest.kt @@ -0,0 +1,110 @@ +/* + * Copyright 2021 The Matrix.org Foundation C.I.C. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.matrix.android.sdk + +import org.amshove.kluent.internal.assertEquals +import org.junit.Assert +import org.junit.Test +import org.matrix.android.sdk.api.MatrixPatterns +import org.matrix.android.sdk.api.util.StringOrderUtils + +class StringOrderTest { + + @Test + fun testbasing() { + assertEquals("a", StringOrderUtils.baseToString(StringOrderUtils.stringToBase("a", StringOrderUtils.DEFAULT_ALPHABET), StringOrderUtils.DEFAULT_ALPHABET)) + assertEquals("element", StringOrderUtils.baseToString(StringOrderUtils.stringToBase("element", StringOrderUtils.DEFAULT_ALPHABET), StringOrderUtils.DEFAULT_ALPHABET)) + assertEquals("matrix", StringOrderUtils.baseToString(StringOrderUtils.stringToBase("matrix", StringOrderUtils.DEFAULT_ALPHABET), StringOrderUtils.DEFAULT_ALPHABET)) + } + + @Test + fun testValid() { + println(StringOrderUtils.DEFAULT_ALPHABET.joinToString(",")) + + assert(MatrixPatterns.isValidOrderString("a")) + assert(MatrixPatterns.isValidOrderString(" ")) + assert(MatrixPatterns.isValidOrderString("abc")) + assert(!MatrixPatterns.isValidOrderString("abcĂȘ")) + assert(!MatrixPatterns.isValidOrderString("")) + assert(MatrixPatterns.isValidOrderString("!")) + assert(MatrixPatterns.isValidOrderString("!\"#\$%&'()*+,012")) + assert(!MatrixPatterns.isValidOrderString(Char(' '.code - 1).toString())) + + assert(!MatrixPatterns.isValidOrderString( + buildString { + for (i in 0..49) { + append(StringOrderUtils.DEFAULT_ALPHABET.random()) + } + } + )) + + assert(MatrixPatterns.isValidOrderString( + buildString { + for (i in 0..48) { + append(StringOrderUtils.DEFAULT_ALPHABET.random()) + } + } + )) + } + + @Test + fun testAverage() { + assertAverage("${StringOrderUtils.DEFAULT_ALPHABET.first()}", "m") + assertAverage("aa", "aab") + assertAverage("matrix", "element") + assertAverage("mmm", "mmmmm") + assertAverage("aab", "aa") + assertAverage("", "aa") + assertAverage("a", "z") + assertAverage("ground", "sky") + } + + @Test + fun testMidPoints() { + val orders = StringOrderUtils.midPoints("element", "matrix", 4) + assertEquals(4, orders!!.size) + assert("element" < orders[0]) + assert(orders[0] < orders[1]) + assert(orders[1] < orders[2]) + assert(orders[2] < orders[3]) + assert(orders[3] < "matrix") + + println("element < ${orders.joinToString(" < ") { "[$it]" }} < matrix") + + val orders2 = StringOrderUtils.midPoints("a", "d", 4) + assertEquals(null, orders2) + } + + @Test + fun testRenumberNeeded() { + assertEquals(null, StringOrderUtils.average("a", "a")) + assertEquals(null, StringOrderUtils.average("", "")) + assertEquals(null, StringOrderUtils.average("a", "b")) + assertEquals(null, StringOrderUtils.average("b", "a")) + assertEquals(null, StringOrderUtils.average("mmmm", "mmmm")) + assertEquals(null, StringOrderUtils.average("a${Char(0)}", "a")) + } + + private fun assertAverage(first: String, second: String) { + val left = first.takeIf { first < second } ?: second + val right = first.takeIf { first > second } ?: second + val av1 = StringOrderUtils.average(left, right)!! + println("[$left] < [$av1] < [$right]") + Assert.assertTrue(left < av1) + Assert.assertTrue(av1 < right) + } +} diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/common/CommonTestHelper.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/common/CommonTestHelper.kt index 287a4233d9..6e07223ac7 100644 --- a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/common/CommonTestHelper.kt +++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/common/CommonTestHelper.kt @@ -78,7 +78,7 @@ class CommonTestHelper(context: Context) { } /** - * Create a Home server configuration, with Http connection allowed for test + * Create a homeserver configuration, with Http connection allowed for test */ fun createHomeServerConfig(): HomeServerConnectionConfig { return HomeServerConnectionConfig.Builder() diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/keysbackup/KeysBackupTest.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/keysbackup/KeysBackupTest.kt index eb8b8b9730..89d297c592 100644 --- a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/keysbackup/KeysBackupTest.kt +++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/keysbackup/KeysBackupTest.kt @@ -816,7 +816,7 @@ class KeysBackupTest : InstrumentedTest { // - Do an e2e backup to the homeserver mKeysBackupTestHelper.prepareAndCreateKeysBackupData(keysBackup) - // Get key backup version from the home server + // Get key backup version from the homeserver val keysVersionResult = mTestHelper.doSync { keysBackup.getCurrentVersion(it) } diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/ssss/QuadSTests.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/ssss/QuadSTests.kt index 25c22bca57..d14de30c90 100644 --- a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/ssss/QuadSTests.kt +++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/ssss/QuadSTests.kt @@ -33,7 +33,7 @@ import org.matrix.android.sdk.common.TestConstants import org.matrix.android.sdk.internal.crypto.SSSS_ALGORITHM_AES_HMAC_SHA2 import org.matrix.android.sdk.internal.crypto.crosssigning.toBase64NoPadding import org.matrix.android.sdk.internal.crypto.secrets.DefaultSharedSecretStorageService -import org.matrix.android.sdk.api.session.accountdata.AccountDataEvent +import org.matrix.android.sdk.api.session.accountdata.UserAccountDataEvent import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.launch @@ -73,12 +73,12 @@ class QuadSTests : InstrumentedTest { // Assert Account data is updated val accountDataLock = CountDownLatch(1) - var accountData: AccountDataEvent? = null + var accountData: UserAccountDataEvent? = null val liveAccountData = runBlocking(Dispatchers.Main) { - aliceSession.userAccountDataService().getLiveAccountDataEvent("${DefaultSharedSecretStorageService.KEY_ID_BASE}.$TEST_KEY_ID") + aliceSession.accountDataService().getLiveUserAccountDataEvent("${DefaultSharedSecretStorageService.KEY_ID_BASE}.$TEST_KEY_ID") } - val accountDataObserver = Observer?> { t -> + val accountDataObserver = Observer?> { t -> if (t?.getOrNull()?.type == "${DefaultSharedSecretStorageService.KEY_ID_BASE}.$TEST_KEY_ID") { accountData = t.getOrNull() accountDataLock.countDown() @@ -100,13 +100,13 @@ class QuadSTests : InstrumentedTest { quadS.setDefaultKey(TEST_KEY_ID) } - var defaultKeyAccountData: AccountDataEvent? = null + var defaultKeyAccountData: UserAccountDataEvent? = null val defaultDataLock = CountDownLatch(1) val liveDefAccountData = runBlocking(Dispatchers.Main) { - aliceSession.userAccountDataService().getLiveAccountDataEvent(DefaultSharedSecretStorageService.DEFAULT_KEY_ID) + aliceSession.accountDataService().getLiveUserAccountDataEvent(DefaultSharedSecretStorageService.DEFAULT_KEY_ID) } - val accountDefDataObserver = Observer?> { t -> + val accountDefDataObserver = Observer?> { t -> if (t?.getOrNull()?.type == DefaultSharedSecretStorageService.DEFAULT_KEY_ID) { defaultKeyAccountData = t.getOrNull()!! defaultDataLock.countDown() @@ -206,7 +206,7 @@ class QuadSTests : InstrumentedTest { ) } - val accountDataEvent = aliceSession.userAccountDataService().getAccountDataEvent("my.secret") + val accountDataEvent = aliceSession.accountDataService().getUserAccountDataEvent("my.secret") val encryptedContent = accountDataEvent?.content?.get("encrypted") as? Map<*, *> assertEquals("Content should contains two encryptions", 2, encryptedContent?.keys?.size ?: 0) @@ -275,14 +275,14 @@ class QuadSTests : InstrumentedTest { mTestHelper.signOutAndClose(aliceSession) } - private fun assertAccountData(session: Session, type: String): AccountDataEvent { + private fun assertAccountData(session: Session, type: String): UserAccountDataEvent { val accountDataLock = CountDownLatch(1) - var accountData: AccountDataEvent? = null + var accountData: UserAccountDataEvent? = null val liveAccountData = runBlocking(Dispatchers.Main) { - session.userAccountDataService().getLiveAccountDataEvent(type) + session.accountDataService().getLiveUserAccountDataEvent(type) } - val accountDataObserver = Observer?> { t -> + val accountDataObserver = Observer?> { t -> if (t?.getOrNull()?.type == type) { accountData = t.getOrNull() accountDataLock.countDown() diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/MatrixPatterns.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/MatrixPatterns.kt index d8532c77c8..9a5e40ffeb 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/MatrixPatterns.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/MatrixPatterns.kt @@ -16,8 +16,12 @@ package org.matrix.android.sdk.api +import org.matrix.android.sdk.BuildConfig +import timber.log.Timber + /** * This class contains pattern to match the different Matrix ids + * Ref: https://matrix.org/docs/spec/appendices#identifier-grammar */ object MatrixPatterns { @@ -25,7 +29,7 @@ object MatrixPatterns { private const val DOMAIN_REGEX = ":[A-Z0-9.-]+(:[0-9]{2,5})?" // regex pattern to find matrix user ids in a string. - // See https://matrix.org/speculator/spec/HEAD/appendices.html#historical-user-ids + // See https://matrix.org/docs/spec/appendices#historical-user-ids private const val MATRIX_USER_IDENTIFIER_REGEX = "@[A-Z0-9\\x21-\\x39\\x3B-\\x7F]+$DOMAIN_REGEX" val PATTERN_CONTAIN_MATRIX_USER_IDENTIFIER = MATRIX_USER_IDENTIFIER_REGEX.toRegex(RegexOption.IGNORE_CASE) @@ -71,6 +75,9 @@ object MatrixPatterns { private const val LINK_TO_APP_ROOM_ALIAS_REGEXP = APP_BASE_REGEX + MATRIX_ROOM_ALIAS_REGEX + SEP_REGEX + MATRIX_EVENT_IDENTIFIER_REGEX private val PATTERN_CONTAIN_APP_LINK_PERMALINK_ROOM_ALIAS = LINK_TO_APP_ROOM_ALIAS_REGEXP.toRegex(RegexOption.IGNORE_CASE) + // ascii characters in the range \x20 (space) to \x7E (~) + val ORDER_STRING_REGEX = "[ -~]+".toRegex() + // list of patterns to find some matrix item. val MATRIX_PATTERNS = listOf( PATTERN_CONTAIN_MATRIX_TO_PERMALINK_ROOM_ID, @@ -146,4 +153,32 @@ object MatrixPatterns { fun extractServerNameFromId(matrixId: String?): String? { return matrixId?.substringAfter(":", missingDelimiterValue = "")?.takeIf { it.isNotEmpty() } } + + /** + * Orders which are not strings, or do not consist solely of ascii characters in the range \x20 (space) to \x7E (~), + * or consist of more than 50 characters, are forbidden and the field should be ignored if received. + */ + fun isValidOrderString(order: String?): Boolean { + return order != null && order.length < 50 && order matches ORDER_STRING_REGEX + } + + fun candidateAliasFromRoomName(name: String): String { + return Regex("\\s").replace(name.lowercase(), "_").let { + "[^a-z0-9._%#@=+-]".toRegex().replace(it, "") + } + } + + /** + * Return the domain form a userId + * Examples: + * - "@alice:domain.org".getDomain() will return "domain.org" + * - "@bob:domain.org:3455".getDomain() will return "domain.org:3455" + */ + fun String.getDomain(): String { + if (BuildConfig.DEBUG && !isUserId(this)) { + // They are some invalid userId localpart in the wild, but the domain part should be there anyway + Timber.w("Not a valid user ID: $this") + } + return substringAfter(":") + } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/data/HomeServerConnectionConfig.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/data/HomeServerConnectionConfig.kt index e1c5171bfc..215f0a0351 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/data/HomeServerConnectionConfig.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/data/HomeServerConnectionConfig.kt @@ -18,11 +18,11 @@ package org.matrix.android.sdk.api.auth.data import android.net.Uri import com.squareup.moshi.JsonClass +import okhttp3.CipherSuite +import okhttp3.TlsVersion import org.matrix.android.sdk.api.auth.data.HomeServerConnectionConfig.Builder import org.matrix.android.sdk.internal.network.ssl.Fingerprint import org.matrix.android.sdk.internal.util.ensureTrailingSlash -import okhttp3.CipherSuite -import okhttp3.TlsVersion /** * This data class holds how to connect to a specific Homeserver. @@ -31,7 +31,12 @@ import okhttp3.TlsVersion */ @JsonClass(generateAdapter = true) data class HomeServerConnectionConfig( + // This is the homeserver URL entered by the user val homeServerUri: Uri, + // This is the homeserver base URL for the client-server API. Default to homeServerUri, + // but can be updated with data from .Well-Known before login, and/or with the data + // included in the login response + val homeServerUriBase: Uri = homeServerUri, val identityServerUri: Uri? = null, val antiVirusServerUri: Uri? = null, val allowedFingerprints: List = emptyList(), @@ -47,7 +52,6 @@ data class HomeServerConnectionConfig( * This builder should be use to create a [HomeServerConnectionConfig] instance. */ class Builder { - private lateinit var homeServerUri: Uri private var identityServerUri: Uri? = null private var antiVirusServerUri: Uri? = null @@ -69,14 +73,14 @@ data class HomeServerConnectionConfig( */ fun withHomeServerUri(hsUri: Uri): Builder { if (hsUri.scheme != "http" && hsUri.scheme != "https") { - throw RuntimeException("Invalid home server URI: $hsUri") + throw RuntimeException("Invalid homeserver URI: $hsUri") } // ensure trailing / val hsString = hsUri.toString().ensureTrailingSlash() homeServerUri = try { Uri.parse(hsString) } catch (e: Exception) { - throw RuntimeException("Invalid home server URI: $hsUri") + throw RuntimeException("Invalid homeserver URI: $hsUri") } return this } @@ -134,7 +138,7 @@ data class HomeServerConnectionConfig( } /** - * Add an accepted TLS version for TLS connections with the home server. + * Add an accepted TLS version for TLS connections with the homeserver. * * @param tlsVersion the tls version to add to the set of TLS versions accepted. * @return this builder @@ -156,7 +160,7 @@ data class HomeServerConnectionConfig( } /** - * Add a TLS cipher suite to the list of accepted TLS connections with the home server. + * Add a TLS cipher suite to the list of accepted TLS connections with the homeserver. * * @param tlsCipherSuite the tls cipher suite to add. * @return this builder @@ -234,16 +238,16 @@ data class HomeServerConnectionConfig( */ fun build(): HomeServerConnectionConfig { return HomeServerConnectionConfig( - homeServerUri, - identityServerUri, - antiVirusServerUri, - allowedFingerprints, - shouldPin, - tlsVersions, - tlsCipherSuites, - shouldAcceptTlsExtensions, - allowHttpExtension, - forceUsageTlsVersions + homeServerUri = homeServerUri, + identityServerUri = identityServerUri, + antiVirusServerUri = antiVirusServerUri, + allowedFingerprints = allowedFingerprints, + shouldPin = shouldPin, + tlsVersions = tlsVersions, + tlsCipherSuites = tlsCipherSuites, + shouldAcceptTlsExtensions = shouldAcceptTlsExtensions, + allowHttpExtension = allowHttpExtension, + forceUsageTlsVersions = forceUsageTlsVersions ) } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/data/SessionParams.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/data/SessionParams.kt index b2a57c7f5c..b490ac877e 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/data/SessionParams.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/data/SessionParams.kt @@ -51,13 +51,18 @@ data class SessionParams( val deviceId = credentials.deviceId /** - * The current homeserver Url. It can be different that the homeserver url entered - * during login phase, because a redirection may have occurred + * The homeserver Url entered by the user during the login phase. */ val homeServerUrl = homeServerConnectionConfig.homeServerUri.toString() /** - * The current homeserver host + * The current homeserver Url for client-server API. It can be different that the homeserver url entered + * during login phase, because a redirection may have occurred + */ + val homeServerUrlBase = homeServerConnectionConfig.homeServerUriBase.toString() + + /** + * The current homeserver host, using what has been entered by the user during login phase */ val homeServerHost = homeServerConnectionConfig.homeServerUri.host diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/registration/RegistrationFlowResponse.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/registration/RegistrationFlowResponse.kt index 2b1c1c09b3..ac740ddab7 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/registration/RegistrationFlowResponse.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/registration/RegistrationFlowResponse.kt @@ -38,7 +38,7 @@ data class RegistrationFlowResponse( val completedStages: List? = null, /** - * The session identifier that the client must pass back to the home server, if one is provided, + * The session identifier that the client must pass back to the homeserver, if one is provided, * in subsequent attempts to authenticate in the same API call. */ @Json(name = "session") diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/wellknown/WellknownResult.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/wellknown/WellknownResult.kt index c68a9e9699..56257db79c 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/wellknown/WellknownResult.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/wellknown/WellknownResult.kt @@ -22,11 +22,6 @@ import org.matrix.android.sdk.api.auth.data.WellKnown * Ref: https://matrix.org/docs/spec/client_server/latest#well-known-uri */ sealed class WellknownResult { - /** - * The provided matrixId is no valid. Unable to extract a domain name. - */ - object InvalidMatrixId : WellknownResult() - /** * Retrieve the specific piece of information from the user in a way which fits within the existing client user experience, * if the client is inclined to do so. Failure can take place instead if no good user experience for this is possible at this point. diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/failure/MatrixIdFailure.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/failure/MatrixIdFailure.kt new file mode 100644 index 0000000000..8f7bca803d --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/failure/MatrixIdFailure.kt @@ -0,0 +1,21 @@ +/* + * Copyright (c) 2021 The Matrix.org Foundation C.I.C. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.matrix.android.sdk.api.failure + +sealed class MatrixIdFailure : Failure.FeatureFailure() { + object InvalidMatrixId : MatrixIdFailure() +} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/raw/RawService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/raw/RawService.kt index f1722b2189..3366d040f7 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/raw/RawService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/raw/RawService.kt @@ -29,8 +29,10 @@ interface RawService { /** * Specific case for the well-known file. Cache validity is 8 hours + * @param domain the domain to get the .well-known file, for instance "matrix.org". + * The URL will be "https://{domain}/.well-known/matrix/client" */ - suspend fun getWellknown(userId: String): String + suspend fun getWellknown(domain: String): String /** * Clear all the cache data diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/Session.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/Session.kt index e888e5d2de..2f981ffbbe 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/Session.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/Session.kt @@ -24,7 +24,7 @@ import org.matrix.android.sdk.api.failure.GlobalError import org.matrix.android.sdk.api.federation.FederationService import org.matrix.android.sdk.api.pushrules.PushRuleService import org.matrix.android.sdk.api.session.account.AccountService -import org.matrix.android.sdk.api.session.accountdata.AccountDataService +import org.matrix.android.sdk.api.session.accountdata.SessionAccountDataService import org.matrix.android.sdk.api.session.cache.CacheService import org.matrix.android.sdk.api.session.call.CallSignalingService import org.matrix.android.sdk.api.session.content.ContentUploadStateTracker @@ -239,9 +239,9 @@ interface Session : fun openIdService(): OpenIdService /** - * Returns the user account data service associated with the session + * Returns the account data service associated with the session */ - fun userAccountDataService(): AccountDataService + fun accountDataService(): SessionAccountDataService /** * Add a listener to the session. diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/accountdata/SessionAccountDataService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/accountdata/SessionAccountDataService.kt new file mode 100644 index 0000000000..2ffb9112d1 --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/accountdata/SessionAccountDataService.kt @@ -0,0 +1,66 @@ +/* + * Copyright 2020 The Matrix.org Foundation C.I.C. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.matrix.android.sdk.api.session.accountdata + +import androidx.lifecycle.LiveData +import org.matrix.android.sdk.api.session.events.model.Content +import org.matrix.android.sdk.api.session.room.accountdata.RoomAccountDataEvent +import org.matrix.android.sdk.api.util.Optional + +/** + * This service is attached globally to the session. + */ +interface SessionAccountDataService { + /** + * Retrieve the account data with the provided type or null if not found + */ + fun getUserAccountDataEvent(type: String): UserAccountDataEvent? + + /** + * Observe the account data with the provided type + */ + fun getLiveUserAccountDataEvent(type: String): LiveData> + + /** + * Retrieve the account data with the provided types. The return list can have a different size that + * the size of the types set, because some AccountData may not exist. + * If an empty set is provided, all the AccountData are retrieved + */ + fun getUserAccountDataEvents(types: Set): List + + /** + * Observe the account data with the provided types. If an empty set is provided, all the AccountData are observed + */ + fun getLiveUserAccountDataEvents(types: Set): LiveData> + + /** + * Retrieve the room account data with the provided types. The return list can have a different size that + * the size of the types set, because some AccountData may not exist. + * If an empty set is provided, all the room AccountData are retrieved + */ + fun getRoomAccountDataEvents(types: Set): List + + /** + * Observe the room account data with the provided types. If an empty set is provided, AccountData of every room are observed + */ + fun getLiveRoomAccountDataEvents(types: Set): LiveData> + + /** + * Update the account data with the provided type and the provided account data content + */ + suspend fun updateUserAccountData(type: String, content: Content) +} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/accountdata/AccountDataEvent.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/accountdata/UserAccountDataEvent.kt similarity index 91% rename from matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/accountdata/AccountDataEvent.kt rename to matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/accountdata/UserAccountDataEvent.kt index e5cbd07aaf..77381a28c4 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/accountdata/AccountDataEvent.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/accountdata/UserAccountDataEvent.kt @@ -22,10 +22,10 @@ import org.matrix.android.sdk.api.session.events.model.Content /** * This is a simplified Event with just a type and a content. - * Currently used types are defined in [UserAccountDataTypes]. + * Currently used types are defined in [UserAccountDataTypes] */ @JsonClass(generateAdapter = true) -data class AccountDataEvent( +data class UserAccountDataEvent( @Json(name = "type") val type: String, @Json(name = "content") val content: Content ) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/call/CallListener.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/call/CallListener.kt index 303add747f..d17be59cd4 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/call/CallListener.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/call/CallListener.kt @@ -17,6 +17,7 @@ package org.matrix.android.sdk.api.session.call import org.matrix.android.sdk.api.session.room.model.call.CallAnswerContent +import org.matrix.android.sdk.api.session.room.model.call.CallAssertedIdentityContent import org.matrix.android.sdk.api.session.room.model.call.CallCandidatesContent import org.matrix.android.sdk.api.session.room.model.call.CallHangupContent import org.matrix.android.sdk.api.session.room.model.call.CallInviteContent @@ -61,4 +62,9 @@ interface CallListener { * Called when the call has been managed by an other session */ fun onCallManagedByOtherSession(callId: String) + + /** + * Called when an asserted identity event is received + */ + fun onCallAssertedIdentityReceived(callAssertedIdentityContent: CallAssertedIdentityContent) } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/CryptoService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/CryptoService.kt index 1b7a5243e2..e3f00a24b6 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/CryptoService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/CryptoService.kt @@ -42,7 +42,6 @@ import org.matrix.android.sdk.internal.crypto.model.event.RoomKeyWithHeldContent import org.matrix.android.sdk.internal.crypto.model.rest.DeviceInfo import org.matrix.android.sdk.internal.crypto.model.rest.DevicesListResponse import org.matrix.android.sdk.internal.crypto.model.rest.RoomKeyRequestBody -import kotlin.jvm.Throws interface CryptoService { @@ -82,9 +81,11 @@ interface CryptoService { fun getDeviceTrackingStatus(userId: String): Int - fun importRoomKeys(roomKeysAsArray: ByteArray, password: String, progressListener: ProgressListener?, callback: MatrixCallback) + suspend fun importRoomKeys(roomKeysAsArray: ByteArray, + password: String, + progressListener: ProgressListener?): ImportRoomKeysResult - fun exportRoomKeys(password: String, callback: MatrixCallback) + suspend fun exportRoomKeys(password: String): ByteArray fun setRoomBlacklistUnverifiedDevices(roomId: String) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/keysbackup/KeysBackupService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/keysbackup/KeysBackupService.kt index 465d00168f..4464427b90 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/keysbackup/KeysBackupService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/keysbackup/KeysBackupService.kt @@ -28,7 +28,7 @@ import org.matrix.android.sdk.internal.crypto.store.SavedKeyBackupKeyInfo interface KeysBackupService { /** - * Retrieve the current version of the backup from the home server + * Retrieve the current version of the backup from the homeserver * * It can be different than keysBackupVersion. * @param callback onSuccess(null) will be called if there is no backup on the server diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/keysbackup/KeysBackupState.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/keysbackup/KeysBackupState.kt index 7d0f04ebcf..a4cc133398 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/keysbackup/KeysBackupState.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/keysbackup/KeysBackupState.kt @@ -54,7 +54,7 @@ enum class KeysBackupState { // Need to check the current backup version on the homeserver Unknown, - // Checking if backup is enabled on home server + // Checking if backup is enabled on homeserver CheckingBackUpOnHomeserver, // Backup has been stopped because a new backup version has been detected on the homeserver diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/events/model/Event.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/events/model/Event.kt index 6400dd6444..3d82846e7e 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/events/model/Event.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/events/model/Event.kt @@ -104,7 +104,7 @@ data class Event( /** * The `age` value transcoded in a timestamp based on the device clock when the SDK received - * the event from the home server. + * the event from the homeserver. * Unlike `age`, this value is static. */ @Transient diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/events/model/EventType.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/events/model/EventType.kt index 229a53fa9d..9c3fdd57da 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/events/model/EventType.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/events/model/EventType.kt @@ -76,6 +76,8 @@ object EventType { const val CALL_NEGOTIATE = "m.call.negotiate" const val CALL_REJECT = "m.call.reject" const val CALL_HANGUP = "m.call.hangup" + const val CALL_ASSERTED_IDENTITY = "m.call.asserted_identity" + const val CALL_ASSERTED_IDENTITY_PREFIX = "org.matrix.call.asserted_identity" // This type is not processed by the client, just sent to the server const val CALL_REPLACES = "m.call.replaces" diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/homeserver/HomeServerCapabilities.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/homeserver/HomeServerCapabilities.kt index da99ab8d54..10c2db4535 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/homeserver/HomeServerCapabilities.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/homeserver/HomeServerCapabilities.kt @@ -32,7 +32,13 @@ data class HomeServerCapabilities( /** * Default identity server url, provided in Wellknown */ - val defaultIdentityServerUrl: String? = null + val defaultIdentityServerUrl: String? = null, + /** + * Room versions supported by the server + * This capability describes the default and available room versions a server supports, and at what level of stability. + * Clients should make use of this capability to determine if users need to be encouraged to upgrade their rooms. + */ + val roomVersions: RoomVersionCapabilities? = null ) { companion object { const val MAX_UPLOAD_FILE_SIZE_UNKNOWN = -1L diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/homeserver/RoomVersionModel.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/homeserver/RoomVersionModel.kt new file mode 100644 index 0000000000..7798b4cc63 --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/homeserver/RoomVersionModel.kt @@ -0,0 +1,32 @@ +/* + * Copyright 2021 The Matrix.org Foundation C.I.C. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.matrix.android.sdk.api.session.homeserver + +data class RoomVersionCapabilities( + val defaultRoomVersion: String, + val supportedVersion: List +) + +data class RoomVersionInfo( + val version: String, + val status: RoomVersionStatus +) + +enum class RoomVersionStatus { + STABLE, + UNSTABLE +} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/identity/IdentityService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/identity/IdentityService.kt index 8f8967e8fb..ae546b6cec 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/identity/IdentityService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/identity/IdentityService.kt @@ -35,7 +35,7 @@ interface IdentityService { /** * Check if the identity server is valid * See https://matrix.org/docs/spec/identity_service/latest#status-check - * RiotX SDK only supports identity server API v2 + * Matrix Android SDK2 only supports identity server API v2 */ suspend fun isValidIdentityServer(url: String) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/AliasAvailabilityResult.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/AliasAvailabilityResult.kt new file mode 100644 index 0000000000..6f607569c0 --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/AliasAvailabilityResult.kt @@ -0,0 +1,24 @@ +/* + * Copyright 2021 The Matrix.org Foundation C.I.C. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.matrix.android.sdk.api.session.room + +import org.matrix.android.sdk.api.session.room.alias.RoomAliasError + +sealed class AliasAvailabilityResult { + object Available: AliasAvailabilityResult() + data class NotAvailable(val roomAliasError: RoomAliasError) : AliasAvailabilityResult() +} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/Room.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/Room.kt index 41bcbf8ff6..ebe96b6382 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/Room.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/Room.kt @@ -17,7 +17,7 @@ package org.matrix.android.sdk.api.session.room import androidx.lifecycle.LiveData -import org.matrix.android.sdk.api.session.accountdata.AccountDataService +import org.matrix.android.sdk.api.session.room.accountdata.RoomAccountDataService import org.matrix.android.sdk.api.session.room.alias.AliasService import org.matrix.android.sdk.api.session.room.call.RoomCallService import org.matrix.android.sdk.api.session.room.crypto.RoomCryptoService @@ -34,6 +34,7 @@ import org.matrix.android.sdk.api.session.room.tags.TagsService import org.matrix.android.sdk.api.session.room.timeline.TimelineService import org.matrix.android.sdk.api.session.room.typing.TypingService import org.matrix.android.sdk.api.session.room.uploads.UploadsService +import org.matrix.android.sdk.api.session.room.version.RoomVersionService import org.matrix.android.sdk.api.session.search.SearchResult import org.matrix.android.sdk.api.session.space.Space import org.matrix.android.sdk.api.util.Optional @@ -57,7 +58,8 @@ interface Room : RelationService, RoomCryptoService, RoomPushRuleService, - AccountDataService { + RoomAccountDataService, + RoomVersionService { /** * The roomId of this room diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/RoomDirectoryService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/RoomDirectoryService.kt index 176de8e408..f3e3913bc1 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/RoomDirectoryService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/RoomDirectoryService.kt @@ -40,4 +40,6 @@ interface RoomDirectoryService { * Set the visibility of a room in the directory */ suspend fun setRoomDirectoryVisibility(roomId: String, roomDirectoryVisibility: RoomDirectoryVisibility) + + suspend fun checkAliasAvailability(aliasLocalPart: String?) : AliasAvailabilityResult } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/RoomService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/RoomService.kt index 871c5378a6..b7377df1b3 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/RoomService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/RoomService.kt @@ -125,6 +125,12 @@ interface RoomService { */ suspend fun deleteRoomAlias(roomAlias: String) + /** + * Return the current local changes membership for the given room. + * see [getChangeMembershipsLive] for more details. + */ + fun getChangeMemberships(roomIdOrAlias: String): ChangeMembershipState + /** * Return a live data of all local changes membership that happened since the session has been opened. * It allows you to track this in your client to known what is currently being processed by the SDK. diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/accountdata/RoomAccountDataEvent.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/accountdata/RoomAccountDataEvent.kt new file mode 100644 index 0000000000..eb676ab5e7 --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/accountdata/RoomAccountDataEvent.kt @@ -0,0 +1,29 @@ +/* + * Copyright 2020 The Matrix.org Foundation C.I.C. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.matrix.android.sdk.api.session.room.accountdata + +import org.matrix.android.sdk.api.session.events.model.Content + +/** + * This is a simplified Event with just a roomId, a type and a content. + * Currently used types are defined in [RoomAccountDataTypes]. + */ +data class RoomAccountDataEvent( + val roomId: String, + val type: String, + val content: Content +) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/accountdata/AccountDataService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/accountdata/RoomAccountDataService.kt similarity index 81% rename from matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/accountdata/AccountDataService.kt rename to matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/accountdata/RoomAccountDataService.kt index 77f3eb0cd9..190749c85c 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/accountdata/AccountDataService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/accountdata/RoomAccountDataService.kt @@ -14,37 +14,37 @@ * limitations under the License. */ -package org.matrix.android.sdk.api.session.accountdata +package org.matrix.android.sdk.api.session.room.accountdata import androidx.lifecycle.LiveData import org.matrix.android.sdk.api.session.events.model.Content import org.matrix.android.sdk.api.util.Optional /** - * This service can be attached globally to the session so it represents user data or attached to a single room. + * This service is attached to a single room. */ -interface AccountDataService { +interface RoomAccountDataService { /** * Retrieve the account data with the provided type or null if not found */ - fun getAccountDataEvent(type: String): AccountDataEvent? + fun getAccountDataEvent(type: String): RoomAccountDataEvent? /** * Observe the account data with the provided type */ - fun getLiveAccountDataEvent(type: String): LiveData> + fun getLiveAccountDataEvent(type: String): LiveData> /** * Retrieve the account data with the provided types. The return list can have a different size that * the size of the types set, because some AccountData may not exist. * If an empty set is provided, all the AccountData are retrieved */ - fun getAccountDataEvents(types: Set): List + fun getAccountDataEvents(types: Set): List /** * Observe the account data with the provided types. If an empty set is provided, all the AccountData are observed */ - fun getLiveAccountDataEvents(types: Set): LiveData> + fun getLiveAccountDataEvents(types: Set): LiveData> /** * Update the account data with the provided type and the provided account data content diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/accountdata/RoomAccountDataTypes.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/accountdata/RoomAccountDataTypes.kt index 0e80c307b4..96eb86c0d6 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/accountdata/RoomAccountDataTypes.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/accountdata/RoomAccountDataTypes.kt @@ -20,4 +20,5 @@ object RoomAccountDataTypes { const val EVENT_TYPE_VIRTUAL_ROOM = "im.vector.is_virtual_room" const val EVENT_TYPE_TAG = "m.tag" const val EVENT_TYPE_FULLY_READ = "m.fully_read" + const val EVENT_TYPE_SPACE_ORDER = "org.matrix.msc3230.space_order" // m.space_order } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/call/CallAssertedIdentityContent.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/call/CallAssertedIdentityContent.kt new file mode 100644 index 0000000000..4c5413425f --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/call/CallAssertedIdentityContent.kt @@ -0,0 +1,57 @@ +/* + * Copyright 2021 The Matrix.org Foundation C.I.C. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.matrix.android.sdk.api.session.room.model.call + +import com.squareup.moshi.Json +import com.squareup.moshi.JsonClass + +/** + * This event is sent by the callee when they wish to answer the call. + */ +@JsonClass(generateAdapter = true) +data class CallAssertedIdentityContent( + /** + * Required. The ID of the call this event relates to. + */ + @Json(name = "call_id") override val callId: String, + /** + * Required. ID to let user identify remote echo of their own events + */ + @Json(name = "party_id") override val partyId: String? = null, + /** + * Required. The version of the VoIP specification this messages adheres to. + */ + @Json(name = "version") override val version: String?, + + /** + * Optional. Used to inform the transferee who they're now speaking to. + */ + @Json(name = "asserted_identity") val assertedIdentity: AssertedIdentity? = null +) : CallSignalingContent { + + /** + * A user ID may be included if relevant, but unlike target_user, it is purely informational. + * The asserted identity may not represent a matrix user at all, + * in which case just a display_name may be given, or a perhaps a display_name and avatar_url. + */ + @JsonClass(generateAdapter = true) + data class AssertedIdentity( + @Json(name = "id") val id: String? = null, + @Json(name = "display_name") val displayName: String? = null, + @Json(name = "avatar_url") val avatarUrl: String? = null + ) +} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/MessageAudioContent.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/MessageAudioContent.kt index fcf3d73cbe..b4ba5c0a66 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/MessageAudioContent.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/MessageAudioContent.kt @@ -54,5 +54,5 @@ data class MessageAudioContent( ) : MessageWithAttachmentContent { override val mimeType: String? - get() = encryptedFileInfo?.mimetype ?: audioInfo?.mimeType + get() = audioInfo?.mimeType } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/MessageFileContent.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/MessageFileContent.kt index d93f115322..96877b4d9f 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/MessageFileContent.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/MessageFileContent.kt @@ -60,8 +60,7 @@ data class MessageFileContent( ) : MessageWithAttachmentContent { override val mimeType: String? - get() = encryptedFileInfo?.mimetype - ?: info?.mimeType + get() = info?.mimeType ?: MimeTypeMap.getFileExtensionFromUrl(filename ?: body)?.let { extension -> MimeTypeMap.getSingleton().getMimeTypeFromExtension(extension) } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/MessageImageContent.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/MessageImageContent.kt index 73e27b64e3..73fd1eab56 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/MessageImageContent.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/MessageImageContent.kt @@ -20,7 +20,6 @@ import com.squareup.moshi.Json import com.squareup.moshi.JsonClass import org.matrix.android.sdk.api.session.events.model.Content import org.matrix.android.sdk.api.session.room.model.relation.RelationDefaultContent -import org.matrix.android.sdk.api.util.MimeTypes import org.matrix.android.sdk.internal.crypto.model.rest.EncryptedFileInfo @JsonClass(generateAdapter = true) @@ -55,5 +54,5 @@ data class MessageImageContent( @Json(name = "file") override val encryptedFileInfo: EncryptedFileInfo? = null ) : MessageImageInfoContent { override val mimeType: String? - get() = encryptedFileInfo?.mimetype ?: info?.mimeType ?: MimeTypes.Images + get() = info?.mimeType } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/MessageStickerContent.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/MessageStickerContent.kt index 280316d4b5..8e1d4d3d75 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/MessageStickerContent.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/MessageStickerContent.kt @@ -55,5 +55,5 @@ data class MessageStickerContent( @Json(name = "file") override val encryptedFileInfo: EncryptedFileInfo? = null ) : MessageImageInfoContent { override val mimeType: String? - get() = encryptedFileInfo?.mimetype ?: info?.mimeType + get() = info?.mimeType } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/MessageVideoContent.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/MessageVideoContent.kt index b7581c9fbf..3f5d2dab2e 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/MessageVideoContent.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/MessageVideoContent.kt @@ -53,5 +53,5 @@ data class MessageVideoContent( @Json(name = "file") override val encryptedFileInfo: EncryptedFileInfo? = null ) : MessageWithAttachmentContent { override val mimeType: String? - get() = encryptedFileInfo?.mimetype ?: videoInfo?.mimeType + get() = videoInfo?.mimeType } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/version/RoomVersionService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/version/RoomVersionService.kt new file mode 100644 index 0000000000..ea67b55174 --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/version/RoomVersionService.kt @@ -0,0 +1,45 @@ +/* + * Copyright 2021 The Matrix.org Foundation C.I.C. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.matrix.android.sdk.api.session.room.version + +interface RoomVersionService { + /** + * Return the room version of this room + */ + fun getRoomVersion(): String + + /** + * Upgrade to the given room version + * @return the replacement room id + */ + suspend fun upgradeToVersion(version: String): String + + /** + * Get the recommended room version for the current homeserver + */ + fun getRecommendedVersion() : String + + /** + * Ask if the user has enough power level to upgrade the room + */ + fun userMayUpgradeRoom(userId: String): Boolean + + /** + * Return true if the current room version is declared unstable by the homeserver + */ + fun isUsingUnstableRoomVersion(): Boolean +} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/space/Space.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/space/Space.kt index db25762c2f..3bae6126e0 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/space/Space.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/space/Space.kt @@ -18,6 +18,7 @@ package org.matrix.android.sdk.api.session.space import org.matrix.android.sdk.api.session.room.Room import org.matrix.android.sdk.api.session.room.model.RoomSummary +import org.matrix.android.sdk.api.session.space.model.SpaceChildContent interface Space { @@ -38,6 +39,8 @@ interface Space { autoJoin: Boolean = false, suggested: Boolean? = false) + fun getChildInfo(roomId: String): SpaceChildContent? + suspend fun removeChildren(roomId: String) @Throws diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/space/SpaceOrderUtils.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/space/SpaceOrderUtils.kt new file mode 100644 index 0000000000..844a5adcb4 --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/space/SpaceOrderUtils.kt @@ -0,0 +1,105 @@ +/* + * Copyright 2021 The Matrix.org Foundation C.I.C. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.matrix.android.sdk.api.session.space + +import org.matrix.android.sdk.api.util.StringOrderUtils + +/** + * Adds some utilities to compute correct string orders when ordering spaces. + * After moving a space (e.g via DnD), client should limit the number of room account data update. + * For example if the space is moved between two other spaces with orders, just update the moved space order by computing + * a mid point between the surrounding orders. + * If the space is moved after a space with no order, all the previous spaces should be then ordered, + * and the computed orders should be chosen so that there is enough gaps in between them to facilitate future re-order. + * Re numbering (i.e change all spaces m.space.order account data) should be avoided as much as possible, + * as the updates might not be atomic for other clients and would makes spaces jump around. + */ +object SpaceOrderUtils { + + data class SpaceReOrderCommand( + val spaceId: String, + val order: String + ) + + /** + * Returns a minimal list of order change in order to re order the space list as per given move. + */ + fun orderCommandsForMove(orderedSpacesToOrderMap: List>, movedSpaceId: String, delta: Int): List { + val movedIndex = orderedSpacesToOrderMap.indexOfFirst { it.first == movedSpaceId } + if (movedIndex == -1) return emptyList() + if (delta == 0) return emptyList() + + val targetIndex = if (delta > 0) movedIndex + delta else (movedIndex + delta - 1) + + val nodesToReNumber = mutableListOf() + var lowerBondOrder: String? = null + var index = targetIndex + while (index >= 0 && lowerBondOrder == null) { + val node = orderedSpacesToOrderMap.getOrNull(index) + if (node != null /*null when adding at the end*/) { + val nodeOrder = node.second + if (node.first == movedSpaceId) break + if (nodeOrder == null) { + nodesToReNumber.add(0, node.first) + } else { + lowerBondOrder = nodeOrder + } + } + index-- + } + nodesToReNumber.add(movedSpaceId) + val afterSpace: Pair? = if (orderedSpacesToOrderMap.indices.contains(targetIndex + 1)) { + orderedSpacesToOrderMap[targetIndex + 1] + } else null + + val defaultMaxOrder = CharArray(4) { StringOrderUtils.DEFAULT_ALPHABET.last() } + .joinToString("") + + val defaultMinOrder = CharArray(4) { StringOrderUtils.DEFAULT_ALPHABET.first() } + .joinToString("") + + val afterOrder = afterSpace?.second ?: defaultMaxOrder + + val beforeOrder = lowerBondOrder ?: defaultMinOrder + + val newOrder = StringOrderUtils.midPoints(beforeOrder, afterOrder, nodesToReNumber.size) + + if (newOrder.isNullOrEmpty()) { + // re order all? + val expectedList = orderedSpacesToOrderMap.toMutableList() + expectedList.removeAt(movedIndex).let { + expectedList.add(movedIndex + delta, it) + } + + return StringOrderUtils.midPoints(defaultMinOrder, defaultMaxOrder, orderedSpacesToOrderMap.size)?.let { orders -> + expectedList.mapIndexed { index, pair -> + SpaceReOrderCommand( + pair.first, + orders[index] + ) + } + } ?: emptyList() + } else { + return nodesToReNumber.mapIndexed { i, s -> + SpaceReOrderCommand( + s, + newOrder[i] + ) + } + } + } +} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/space/SpaceService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/space/SpaceService.kt index fedf38fe06..e5288e4045 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/space/SpaceService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/space/SpaceService.kt @@ -36,7 +36,11 @@ interface SpaceService { /** * Just a shortcut for space creation for ease of use */ - suspend fun createSpace(name: String, topic: String?, avatarUri: Uri?, isPublic: Boolean): String + suspend fun createSpace(name: String, + topic: String?, + avatarUri: Uri?, + isPublic: Boolean, + roomAliasLocalPart: String? = null): String /** * Get a space from a roomId diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/space/model/SpaceOrderContent.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/space/model/SpaceOrderContent.kt new file mode 100644 index 0000000000..a8578347c8 --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/space/model/SpaceOrderContent.kt @@ -0,0 +1,37 @@ +/* + * Copyright 2021 The Matrix.org Foundation C.I.C. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.matrix.android.sdk.api.session.space.model + +import com.squareup.moshi.JsonClass +import org.matrix.android.sdk.api.MatrixPatterns + +/** + * { + * "type": "m.space_order", + * "content": { + * "order": "..." + * } + * } + */ +@JsonClass(generateAdapter = true) +data class SpaceOrderContent( + val order: String? = null +) { + fun safeOrder(): String? { + return order?.takeIf { MatrixPatterns.isValidOrderString(it) } + } +} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/space/model/TopLevelSpaceComparator.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/space/model/TopLevelSpaceComparator.kt new file mode 100644 index 0000000000..8af4f3a149 --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/space/model/TopLevelSpaceComparator.kt @@ -0,0 +1,44 @@ +/* + * Copyright 2021 The Matrix.org Foundation C.I.C. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.matrix.android.sdk.api.session.space.model + +import org.matrix.android.sdk.api.session.room.model.RoomSummary + +// Can't use regular compare by because Null is considered less than any value, and for space order it's the opposite +class TopLevelSpaceComparator(val orders: Map) : Comparator { + + override fun compare(left: RoomSummary?, right: RoomSummary?): Int { + val leftOrder = left?.roomId?.let { orders[it] } + val rightOrder = right?.roomId?.let { orders[it] } + return if (leftOrder != null && rightOrder != null) { + leftOrder.compareTo(rightOrder) + } else { + if (leftOrder == null) { + if (rightOrder == null) { + compareValues(left?.roomId, right?.roomId) + } else { + 1 + } + } else { + -1 + } + } +// .also { +// Timber.w("VAL: compare(${left?.displayName} | $leftOrder ,${right?.displayName} | $rightOrder) = $it") +// } + } +} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/util/StringOrderUtils.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/util/StringOrderUtils.kt new file mode 100644 index 0000000000..83c8585941 --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/util/StringOrderUtils.kt @@ -0,0 +1,87 @@ +/* + * Copyright 2021 The Matrix.org Foundation C.I.C. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.matrix.android.sdk.api.util + +import java.math.BigInteger + +object StringOrderUtils { + + val DEFAULT_ALPHABET = buildString { + for (i in 0x20..0x7E) { + append(Char(i)) + } + }.toCharArray() + + // /=Range(0x20, 0x7E) + + fun average(left: String, right: String, alphabet: CharArray = DEFAULT_ALPHABET): String? { + return midPoints(left, right, 1, alphabet)?.firstOrNull() + } + + fun midPoints(left: String, right: String, count: Int, alphabet: CharArray = DEFAULT_ALPHABET): List? { + if (left == right) return null // no space in between.. + if (left > right) return midPoints(right, left, count, alphabet) + val size = maxOf(left.length, right.length) + val leftPadded = pad(left, size, alphabet.first()) + val rightPadded = pad(right, size, alphabet.first()) + val b1 = stringToBase(leftPadded, alphabet) + val b2 = stringToBase(rightPadded, alphabet) + val step = (b2.minus(b1)).div(BigInteger("${count + 1}")) + val orders = mutableListOf() + var previous = left + for (i in 0 until count) { + val newOrder = baseToString(b1.add(step.multiply(BigInteger("${i + 1}"))), alphabet) + orders.add(newOrder) + // ensure there was enought precision + if (previous >= newOrder) return null + previous = newOrder + } + return orders.takeIf { orders.last() < right } + } + + private fun pad(string: String, size: Int, padding: Char): String { + val raw = string.toCharArray() + return CharArray(size).also { + for (index in it.indices) { + if (index < raw.size) { + it[index] = raw[index] + } else { + it[index] = padding + } + } + }.joinToString("") + } + + fun baseToString(x: BigInteger, alphabet: CharArray): String { + val len = alphabet.size.toBigInteger() + if (x < len) { + return alphabet[x.toInt()].toString() + } else { + return baseToString(x.div(len), alphabet) + alphabet[x.rem(len).toInt()].toString() + } + } + + fun stringToBase(x: String, alphabet: CharArray): BigInteger { + if (x.isEmpty()) throw IllegalArgumentException() + val len = alphabet.size.toBigInteger() + var result = BigInteger("0") + x.reversed().forEachIndexed { index, c -> + result += (alphabet.indexOf(c).toBigInteger() * len.pow(index)) + } + return result + } +} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/SessionManager.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/SessionManager.kt index 441232f57f..c746ad863a 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/SessionManager.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/SessionManager.kt @@ -51,7 +51,7 @@ internal class SessionManager @Inject constructor(private val matrixComponent: M } } - private fun getOrCreateSessionComponent(sessionParams: SessionParams): SessionComponent { + fun getOrCreateSessionComponent(sessionParams: SessionParams): SessionComponent { return sessionComponents.getOrPut(sessionParams.credentials.sessionId()) { DaggerSessionComponent .factory() diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/AuthAPI.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/AuthAPI.kt index 5a9fa9edf6..50d9e5a06c 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/AuthAPI.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/AuthAPI.kt @@ -21,8 +21,8 @@ import org.matrix.android.sdk.api.util.JsonDict import org.matrix.android.sdk.internal.auth.data.Availability import org.matrix.android.sdk.internal.auth.data.LoginFlowResponse import org.matrix.android.sdk.internal.auth.data.PasswordLoginParams -import org.matrix.android.sdk.internal.auth.data.RiotConfig import org.matrix.android.sdk.internal.auth.data.TokenLoginParams +import org.matrix.android.sdk.internal.auth.data.WebClientConfig import org.matrix.android.sdk.internal.auth.login.ResetPasswordMailConfirmed import org.matrix.android.sdk.internal.auth.registration.AddThreePidRegistrationParams import org.matrix.android.sdk.internal.auth.registration.AddThreePidRegistrationResponse @@ -44,16 +44,16 @@ import retrofit2.http.Url */ internal interface AuthAPI { /** - * Get a Riot config file, using the name including the domain + * Get a Web client config file, using the name including the domain */ @GET("config.{domain}.json") - suspend fun getRiotConfigDomain(@Path("domain") domain: String): RiotConfig + suspend fun getWebClientConfigDomain(@Path("domain") domain: String): WebClientConfig /** - * Get a Riot config file + * Get a Web client default config file */ @GET("config.json") - suspend fun getRiotConfig(): RiotConfig + suspend fun getWebClientConfig(): WebClientConfig /** * Get the version information of the homeserver diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/DefaultAuthenticationService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/DefaultAuthenticationService.kt index 20ce438d8e..e76dc28734 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/DefaultAuthenticationService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/DefaultAuthenticationService.kt @@ -19,6 +19,8 @@ package org.matrix.android.sdk.internal.auth import android.net.Uri import dagger.Lazy import okhttp3.OkHttpClient +import org.matrix.android.sdk.api.MatrixPatterns +import org.matrix.android.sdk.api.MatrixPatterns.getDomain import org.matrix.android.sdk.api.auth.AuthenticationService import org.matrix.android.sdk.api.auth.data.Credentials import org.matrix.android.sdk.api.auth.data.HomeServerConnectionConfig @@ -28,10 +30,11 @@ import org.matrix.android.sdk.api.auth.login.LoginWizard import org.matrix.android.sdk.api.auth.registration.RegistrationWizard import org.matrix.android.sdk.api.auth.wellknown.WellknownResult import org.matrix.android.sdk.api.failure.Failure +import org.matrix.android.sdk.api.failure.MatrixIdFailure import org.matrix.android.sdk.api.session.Session import org.matrix.android.sdk.api.util.appendParamToUrl import org.matrix.android.sdk.internal.SessionManager -import org.matrix.android.sdk.internal.auth.data.RiotConfig +import org.matrix.android.sdk.internal.auth.data.WebClientConfig import org.matrix.android.sdk.internal.auth.db.PendingSessionData import org.matrix.android.sdk.internal.auth.login.DefaultLoginWizard import org.matrix.android.sdk.internal.auth.login.DirectLoginTask @@ -122,7 +125,7 @@ internal class DefaultAuthenticationService @Inject constructor( private fun getHomeServerUrlBase(): String? { return pendingSessionData ?.homeServerConnectionConfig - ?.homeServerUri + ?.homeServerUriBase ?.toString() ?.trim { it == '/' } } @@ -143,9 +146,9 @@ internal class DefaultAuthenticationService @Inject constructor( return result.fold( { // The homeserver exists and up to date, keep the config - // Homeserver url may have been changed, if it was a Riot url + // Homeserver url may have been changed, if it was a Web client url val alteredHomeServerConnectionConfig = homeServerConnectionConfig.copy( - homeServerUri = Uri.parse(it.homeServerUrl) + homeServerUriBase = Uri.parse(it.homeServerUrl) ) pendingSessionData = PendingSessionData(alteredHomeServerConnectionConfig) @@ -154,7 +157,7 @@ internal class DefaultAuthenticationService @Inject constructor( }, { if (it is UnrecognizedCertificateException) { - throw Failure.UnrecognizedCertificateFailure(homeServerConnectionConfig.homeServerUri.toString(), it.fingerprint) + throw Failure.UnrecognizedCertificateFailure(homeServerConnectionConfig.homeServerUriBase.toString(), it.fingerprint) } else { throw it } @@ -165,46 +168,57 @@ internal class DefaultAuthenticationService @Inject constructor( private suspend fun getLoginFlowInternal(homeServerConnectionConfig: HomeServerConnectionConfig): LoginFlowResult { val authAPI = buildAuthAPI(homeServerConnectionConfig) - // First check the homeserver version - return runCatching { - executeRequest(null) { - authAPI.versions() - } - } - .map { versions -> - // Ok, it seems that the homeserver url is valid - getLoginFlowResult(authAPI, versions, homeServerConnectionConfig.homeServerUri.toString()) + // First check if there is a well-known file + return try { + getWellknownLoginFlowInternal(homeServerConnectionConfig) + } catch (failure: Throwable) { + if (failure is Failure.OtherServerError + && failure.httpCode == HttpsURLConnection.HTTP_NOT_FOUND /* 404 */) { + // 404, no well-known data, try direct access to the API + // First check the homeserver version + return runCatching { + executeRequest(null) { + authAPI.versions() + } } - .fold( - { - it - }, - { - if (it is Failure.OtherServerError - && it.httpCode == HttpsURLConnection.HTTP_NOT_FOUND /* 404 */) { - // It's maybe a Riot url? - getRiotDomainLoginFlowInternal(homeServerConnectionConfig) - } else { - throw it - } + .map { versions -> + // Ok, it seems that the homeserver url is valid + getLoginFlowResult(authAPI, versions, homeServerConnectionConfig.homeServerUriBase.toString()) } - ) + .fold( + { + it + }, + { + if (it is Failure.OtherServerError + && it.httpCode == HttpsURLConnection.HTTP_NOT_FOUND /* 404 */) { + // It's maybe a Web client url? + getWebClientDomainLoginFlowInternal(homeServerConnectionConfig) + } else { + throw it + } + } + ) + } else { + throw failure + } + } } - private suspend fun getRiotDomainLoginFlowInternal(homeServerConnectionConfig: HomeServerConnectionConfig): LoginFlowResult { + private suspend fun getWebClientDomainLoginFlowInternal(homeServerConnectionConfig: HomeServerConnectionConfig): LoginFlowResult { val authAPI = buildAuthAPI(homeServerConnectionConfig) val domain = homeServerConnectionConfig.homeServerUri.host - ?: return getRiotLoginFlowInternal(homeServerConnectionConfig) + ?: return getWebClientLoginFlowInternal(homeServerConnectionConfig) - // Ok, try to get the config.domain.json file of a RiotWeb client + // Ok, try to get the config.domain.json file of a Web client return runCatching { executeRequest(null) { - authAPI.getRiotConfigDomain(domain) + authAPI.getWebClientConfigDomain(domain) } } - .map { riotConfig -> - onRiotConfigRetrieved(homeServerConnectionConfig, riotConfig) + .map { webClientConfig -> + onWebClientConfigRetrieved(homeServerConnectionConfig, webClientConfig) } .fold( { @@ -214,7 +228,7 @@ internal class DefaultAuthenticationService @Inject constructor( if (it is Failure.OtherServerError && it.httpCode == HttpsURLConnection.HTTP_NOT_FOUND /* 404 */) { // Try with config.json - getRiotLoginFlowInternal(homeServerConnectionConfig) + getWebClientLoginFlowInternal(homeServerConnectionConfig) } else { throw it } @@ -222,40 +236,24 @@ internal class DefaultAuthenticationService @Inject constructor( ) } - private suspend fun getRiotLoginFlowInternal(homeServerConnectionConfig: HomeServerConnectionConfig): LoginFlowResult { + private suspend fun getWebClientLoginFlowInternal(homeServerConnectionConfig: HomeServerConnectionConfig): LoginFlowResult { val authAPI = buildAuthAPI(homeServerConnectionConfig) - // Ok, try to get the config.json file of a RiotWeb client - return runCatching { - executeRequest(null) { - authAPI.getRiotConfig() - } + // Ok, try to get the config.json file of a Web client + return executeRequest(null) { + authAPI.getWebClientConfig() } - .map { riotConfig -> - onRiotConfigRetrieved(homeServerConnectionConfig, riotConfig) + .let { webClientConfig -> + onWebClientConfigRetrieved(homeServerConnectionConfig, webClientConfig) } - .fold( - { - it - }, - { - if (it is Failure.OtherServerError - && it.httpCode == HttpsURLConnection.HTTP_NOT_FOUND /* 404 */) { - // Try with wellknown - getWellknownLoginFlowInternal(homeServerConnectionConfig) - } else { - throw it - } - } - ) } - private suspend fun onRiotConfigRetrieved(homeServerConnectionConfig: HomeServerConnectionConfig, riotConfig: RiotConfig): LoginFlowResult { - val defaultHomeServerUrl = riotConfig.getPreferredHomeServerUrl() + private suspend fun onWebClientConfigRetrieved(homeServerConnectionConfig: HomeServerConnectionConfig, webClientConfig: WebClientConfig): LoginFlowResult { + val defaultHomeServerUrl = webClientConfig.getPreferredHomeServerUrl() if (defaultHomeServerUrl?.isNotEmpty() == true) { // Ok, good sign, we got a default hs url val newHomeServerConnectionConfig = homeServerConnectionConfig.copy( - homeServerUri = Uri.parse(defaultHomeServerUrl) + homeServerUriBase = Uri.parse(defaultHomeServerUrl) ) val newAuthAPI = buildAuthAPI(newHomeServerConnectionConfig) @@ -275,15 +273,13 @@ internal class DefaultAuthenticationService @Inject constructor( val domain = homeServerConnectionConfig.homeServerUri.host ?: throw Failure.OtherServerError("", HttpsURLConnection.HTTP_NOT_FOUND /* 404 */) - // Create a fake userId, for the getWellknown task - val fakeUserId = "@alice:$domain" - val wellknownResult = getWellknownTask.execute(GetWellknownTask.Params(fakeUserId, homeServerConnectionConfig)) + val wellknownResult = getWellknownTask.execute(GetWellknownTask.Params(domain, homeServerConnectionConfig)) return when (wellknownResult) { is WellknownResult.Prompt -> { val newHomeServerConnectionConfig = homeServerConnectionConfig.copy( - homeServerUri = Uri.parse(wellknownResult.homeServerUrl), - identityServerUri = wellknownResult.identityServerUrl?.let { Uri.parse(it) } + homeServerUriBase = Uri.parse(wellknownResult.homeServerUrl), + identityServerUri = wellknownResult.identityServerUrl?.let { Uri.parse(it) } ?: homeServerConnectionConfig.identityServerUri ) val newAuthAPI = buildAuthAPI(newHomeServerConnectionConfig) @@ -379,7 +375,14 @@ internal class DefaultAuthenticationService @Inject constructor( override suspend fun getWellKnownData(matrixId: String, homeServerConnectionConfig: HomeServerConnectionConfig?): WellknownResult { - return getWellknownTask.execute(GetWellknownTask.Params(matrixId, homeServerConnectionConfig)) + if (!MatrixPatterns.isUserId(matrixId)) { + throw MatrixIdFailure.InvalidMatrixId + } + + return getWellknownTask.execute(GetWellknownTask.Params( + domain = matrixId.getDomain(), + homeServerConnectionConfig = homeServerConnectionConfig) + ) } override suspend fun directAuthentication(homeServerConnectionConfig: HomeServerConnectionConfig, @@ -390,7 +393,7 @@ internal class DefaultAuthenticationService @Inject constructor( } private fun buildAuthAPI(homeServerConnectionConfig: HomeServerConnectionConfig): AuthAPI { - val retrofit = retrofitFactory.create(buildClient(homeServerConnectionConfig), homeServerConnectionConfig.homeServerUri.toString()) + val retrofit = retrofitFactory.create(buildClient(homeServerConnectionConfig), homeServerConnectionConfig.homeServerUriBase.toString()) return retrofit.create(AuthAPI::class.java) } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/IsValidClientServerApiTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/IsValidClientServerApiTask.kt index 867cf46b8d..bc3d887000 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/IsValidClientServerApiTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/IsValidClientServerApiTask.kt @@ -42,7 +42,7 @@ internal class DefaultIsValidClientServerApiTask @Inject constructor( override suspend fun execute(params: IsValidClientServerApiTask.Params): Boolean { val client = buildClient(params.homeServerConnectionConfig) - val homeServerUrl = params.homeServerConnectionConfig.homeServerUri.toString() + val homeServerUrl = params.homeServerConnectionConfig.homeServerUriBase.toString() val authAPI = retrofitFactory.create(client, homeServerUrl) .create(AuthAPI::class.java) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/SessionCreator.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/SessionCreator.kt index 7c4a0c38ec..160fd2d556 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/SessionCreator.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/SessionCreator.kt @@ -38,7 +38,7 @@ internal class DefaultSessionCreator @Inject constructor( ) : SessionCreator { /** - * Credentials can affect the homeServerConnectionConfig, override home server url and/or + * Credentials can affect the homeServerConnectionConfig, override homeserver url and/or * identity server url if provided in the credentials */ override suspend fun createSession(credentials: Credentials, homeServerConnectionConfig: HomeServerConnectionConfig): Session { @@ -56,7 +56,7 @@ internal class DefaultSessionCreator @Inject constructor( tryOrNull { isValidClientServerApiTask.execute( IsValidClientServerApiTask.Params( - homeServerConnectionConfig.copy(homeServerUri = it) + homeServerConnectionConfig.copy(homeServerUriBase = it) ) ) .also { Timber.d("Overriding homeserver url: $it") } @@ -66,7 +66,7 @@ internal class DefaultSessionCreator @Inject constructor( val sessionParams = SessionParams( credentials = credentials, homeServerConnectionConfig = homeServerConnectionConfig.copy( - homeServerUri = overriddenUrl ?: homeServerConnectionConfig.homeServerUri, + homeServerUriBase = overriddenUrl ?: homeServerConnectionConfig.homeServerUriBase, identityServerUri = credentials.discoveryInformation?.identityServer?.baseURL // remove trailing "/" ?.trim { it == '/' } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/data/RiotConfig.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/data/WebClientConfig.kt similarity index 84% rename from matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/data/RiotConfig.kt rename to matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/data/WebClientConfig.kt index e61358a67b..65c3dc64a6 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/data/RiotConfig.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/data/WebClientConfig.kt @@ -20,7 +20,7 @@ import com.squareup.moshi.Json import com.squareup.moshi.JsonClass @JsonClass(generateAdapter = true) -internal data class RiotConfig( +internal data class WebClientConfig( /** * This is now deprecated, but still used first, rather than value from "default_server_config" */ @@ -28,7 +28,7 @@ internal data class RiotConfig( val defaultHomeServerUrl: String?, @Json(name = "default_server_config") - val defaultServerConfig: RiotConfigDefaultServerConfig? + val defaultServerConfig: WebClientConfigDefaultServerConfig? ) { fun getPreferredHomeServerUrl(): String? { return defaultHomeServerUrl @@ -38,13 +38,13 @@ internal data class RiotConfig( } @JsonClass(generateAdapter = true) -internal data class RiotConfigDefaultServerConfig( +internal data class WebClientConfigDefaultServerConfig( @Json(name = "m.homeserver") - val homeServer: RiotConfigBaseConfig? = null + val homeServer: WebClientConfigBaseConfig? = null ) @JsonClass(generateAdapter = true) -internal data class RiotConfigBaseConfig( +internal data class WebClientConfigBaseConfig( @Json(name = "base_url") val baseURL: String? = null ) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/db/AuthRealmMigration.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/db/AuthRealmMigration.kt index bb2667228b..c2104690b3 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/db/AuthRealmMigration.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/db/AuthRealmMigration.kt @@ -16,17 +16,19 @@ package org.matrix.android.sdk.internal.auth.db +import android.net.Uri +import io.realm.DynamicRealm +import io.realm.RealmMigration import org.matrix.android.sdk.api.auth.data.Credentials +import org.matrix.android.sdk.api.auth.data.HomeServerConnectionConfig import org.matrix.android.sdk.api.auth.data.sessionId import org.matrix.android.sdk.internal.di.MoshiProvider -import io.realm.DynamicRealm -import io.realm.RealmMigration import timber.log.Timber internal object AuthRealmMigration : RealmMigration { // Current schema version - const val SCHEMA_VERSION = 3L + const val SCHEMA_VERSION = 4L override fun migrate(realm: DynamicRealm, oldVersion: Long, newVersion: Long) { Timber.d("Migrating Auth Realm from $oldVersion to $newVersion") @@ -34,6 +36,7 @@ internal object AuthRealmMigration : RealmMigration { if (oldVersion <= 0) migrateTo1(realm) if (oldVersion <= 1) migrateTo2(realm) if (oldVersion <= 2) migrateTo3(realm) + if (oldVersion <= 3) migrateTo4(realm) } private fun migrateTo1(realm: DynamicRealm) { @@ -81,4 +84,34 @@ internal object AuthRealmMigration : RealmMigration { } ?.addPrimaryKey(SessionParamsEntityFields.SESSION_ID) } + + private fun migrateTo4(realm: DynamicRealm) { + Timber.d("Step 3 -> 4") + Timber.d("Update SessionParamsEntity to add HomeServerConnectionConfig.homeServerUriBase value") + + val adapter = MoshiProvider.providesMoshi() + .adapter(HomeServerConnectionConfig::class.java) + + realm.schema.get("SessionParamsEntity") + ?.transform { + val homeserverConnectionConfigJson = it.getString(SessionParamsEntityFields.HOME_SERVER_CONNECTION_CONFIG_JSON) + + val homeserverConnectionConfig = adapter + .fromJson(homeserverConnectionConfigJson) + + val homeserverUrl = homeserverConnectionConfig?.homeServerUri?.toString() + // Special case for matrix.org. Old session may use "https://matrix.org", newer one may use + // "https://matrix-client.matrix.org". So fix that here + val alteredHomeserverConnectionConfig = + if (homeserverUrl == "https://matrix.org" || homeserverUrl == "https://matrix-client.matrix.org") { + homeserverConnectionConfig.copy( + homeServerUri = Uri.parse("https://matrix.org"), + homeServerUriBase = Uri.parse("https://matrix-client.matrix.org") + ) + } else { + homeserverConnectionConfig + } + it.set(SessionParamsEntityFields.HOME_SERVER_CONNECTION_CONFIG_JSON, adapter.toJson(alteredHomeserverConnectionConfig)) + } + } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/login/DirectLoginTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/login/DirectLoginTask.kt index 77bbb8096f..3888633723 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/login/DirectLoginTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/login/DirectLoginTask.kt @@ -50,7 +50,7 @@ internal class DefaultDirectLoginTask @Inject constructor( override suspend fun execute(params: DirectLoginTask.Params): Session { val client = buildClient(params.homeServerConnectionConfig) - val homeServerUrl = params.homeServerConnectionConfig.homeServerUri.toString() + val homeServerUrl = params.homeServerConnectionConfig.homeServerUriBase.toString() val authAPI = retrofitFactory.create(client, homeServerUrl) .create(AuthAPI::class.java) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/CryptoModule.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/CryptoModule.kt index e114f86a99..84d4fef5af 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/CryptoModule.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/CryptoModule.kt @@ -112,7 +112,6 @@ internal abstract class CryptoModule { @SessionScope fun providesRealmConfiguration(@SessionFilesDirectory directory: File, @UserMd5 userMd5: String, - realmCryptoStoreMigration: RealmCryptoStoreMigration, realmKeysUtils: RealmKeysUtils): RealmConfiguration { return RealmConfiguration.Builder() .directory(directory) @@ -123,7 +122,7 @@ internal abstract class CryptoModule { .modules(RealmCryptoStoreModule()) .allowWritesOnUiThread(true) .schemaVersion(RealmCryptoStoreMigration.CRYPTO_STORE_SCHEMA_VERSION) - .migration(realmCryptoStoreMigration) + .migration(RealmCryptoStoreMigration) .build() } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/DefaultCryptoService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/DefaultCryptoService.kt index 7f5cfe8df1..d170ae3dd3 100755 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/DefaultCryptoService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/DefaultCryptoService.kt @@ -388,7 +388,7 @@ internal class DefaultCryptoService @Inject constructor( cryptoStore.close() } - // Aways enabled on RiotX + // Always enabled on Matrix Android SDK2 override fun isCryptoEnabled() = true /** @@ -928,14 +928,10 @@ internal class DefaultCryptoService @Inject constructor( * Export the crypto keys * * @param password the password - * @param callback the exported keys + * @return the exported keys */ - override fun exportRoomKeys(password: String, callback: MatrixCallback) { - cryptoCoroutineScope.launch(coroutineDispatchers.main) { - runCatching { - exportRoomKeys(password, MXMegolmExportEncryption.DEFAULT_ITERATION_COUNT) - }.foldToCallback(callback) - } + override suspend fun exportRoomKeys(password: String): ByteArray { + return exportRoomKeys(password, MXMegolmExportEncryption.DEFAULT_ITERATION_COUNT) } /** @@ -963,42 +959,37 @@ internal class DefaultCryptoService @Inject constructor( * @param roomKeysAsArray the room keys as array. * @param password the password * @param progressListener the progress listener - * @param callback the asynchronous callback. + * @return the result ImportRoomKeysResult */ - override fun importRoomKeys(roomKeysAsArray: ByteArray, - password: String, - progressListener: ProgressListener?, - callback: MatrixCallback) { - cryptoCoroutineScope.launch(coroutineDispatchers.main) { - runCatching { - withContext(coroutineDispatchers.crypto) { - Timber.v("## CRYPTO | importRoomKeys starts") + override suspend fun importRoomKeys(roomKeysAsArray: ByteArray, + password: String, + progressListener: ProgressListener?): ImportRoomKeysResult { + return withContext(coroutineDispatchers.crypto) { + Timber.v("## CRYPTO | importRoomKeys starts") - val t0 = System.currentTimeMillis() - val roomKeys = MXMegolmExportEncryption.decryptMegolmKeyFile(roomKeysAsArray, password) - val t1 = System.currentTimeMillis() + val t0 = System.currentTimeMillis() + val roomKeys = MXMegolmExportEncryption.decryptMegolmKeyFile(roomKeysAsArray, password) + val t1 = System.currentTimeMillis() - Timber.v("## CRYPTO | importRoomKeys : decryptMegolmKeyFile done in ${t1 - t0} ms") + Timber.v("## CRYPTO | importRoomKeys : decryptMegolmKeyFile done in ${t1 - t0} ms") - val importedSessions = MoshiProvider.providesMoshi() - .adapter>(Types.newParameterizedType(List::class.java, MegolmSessionData::class.java)) - .fromJson(roomKeys) + val importedSessions = MoshiProvider.providesMoshi() + .adapter>(Types.newParameterizedType(List::class.java, MegolmSessionData::class.java)) + .fromJson(roomKeys) - val t2 = System.currentTimeMillis() + val t2 = System.currentTimeMillis() - Timber.v("## CRYPTO | importRoomKeys : JSON parsing ${t2 - t1} ms") + Timber.v("## CRYPTO | importRoomKeys : JSON parsing ${t2 - t1} ms") - if (importedSessions == null) { - throw Exception("Error") - } + if (importedSessions == null) { + throw Exception("Error") + } - megolmSessionDataImporter.handle( - megolmSessionsData = importedSessions, - fromBackup = false, - progressListener = progressListener - ) - } - }.foldToCallback(callback) + megolmSessionDataImporter.handle( + megolmSessionsData = importedSessions, + fromBackup = false, + progressListener = progressListener + ) } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/megolm/MXMegolmDecryption.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/megolm/MXMegolmDecryption.kt index a29ac457fb..70d2022299 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/megolm/MXMegolmDecryption.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/megolm/MXMegolmDecryption.kt @@ -155,7 +155,7 @@ internal class MXMegolmDecryption(private val userId: String, withHeldInfo.code?.value ?: "", withHeldInfo.reason) } else { - // This is un-used in riotX SDK, not sure if needed + // This is un-used in Matrix Android SDK2, not sure if needed // addEventToPendingList(event, timeline) if (requestKeysOnFail) { requestKeysForEvent(event, false) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/attachments/MXEncryptedAttachments.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/attachments/MXEncryptedAttachments.kt index 5a9852b6db..70730326da 100755 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/attachments/MXEncryptedAttachments.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/attachments/MXEncryptedAttachments.kt @@ -39,7 +39,9 @@ internal object MXEncryptedAttachments { private const val SECRET_KEY_SPEC_ALGORITHM = "AES" private const val MESSAGE_DIGEST_ALGORITHM = "SHA-256" - fun encrypt(clearStream: InputStream, mimetype: String?, outputFile: File, progress: ((current: Int, total: Int) -> Unit)): EncryptedFileInfo { + fun encrypt(clearStream: InputStream, + outputFile: File, + progress: ((current: Int, total: Int) -> Unit)): EncryptedFileInfo { val t0 = System.currentTimeMillis() val secureRandom = SecureRandom() val initVectorBytes = ByteArray(16) { 0.toByte() } @@ -86,7 +88,6 @@ internal object MXEncryptedAttachments { return EncryptedFileInfo( url = null, - mimetype = mimetype, key = EncryptedFileKey( alg = "A256CTR", ext = true, @@ -155,10 +156,9 @@ internal object MXEncryptedAttachments { * Encrypt an attachment stream. * DO NOT USE for big files, it will load all in memory * @param attachmentStream the attachment stream. Will be closed after this method call. - * @param mimetype the mime type * @return the encryption file info */ - fun encryptAttachment(attachmentStream: InputStream, mimetype: String?): EncryptionResult { + fun encryptAttachment(attachmentStream: InputStream): EncryptionResult { val t0 = System.currentTimeMillis() val secureRandom = SecureRandom() @@ -207,7 +207,6 @@ internal object MXEncryptedAttachments { return EncryptionResult( encryptedFileInfo = EncryptedFileInfo( url = null, - mimetype = mimetype, key = EncryptedFileKey( alg = "A256CTR", ext = true, @@ -232,7 +231,9 @@ internal object MXEncryptedAttachments { * @param outputStream the outputStream where the decrypted attachment will be write. * @return true in case of success, false in case of error */ - fun decryptAttachment(attachmentStream: InputStream?, elementToDecrypt: ElementToDecrypt?, outputStream: OutputStream): Boolean { + fun decryptAttachment(attachmentStream: InputStream?, + elementToDecrypt: ElementToDecrypt?, + outputStream: OutputStream): Boolean { // sanity checks if (null == attachmentStream || elementToDecrypt == null) { Timber.e("## decryptAttachment() : null stream") diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/MXDeviceInfo.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/MXDeviceInfo.kt index 00b8bde5d9..68cc41005e 100755 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/MXDeviceInfo.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/MXDeviceInfo.kt @@ -56,7 +56,7 @@ data class MXDeviceInfo( val signatures: Map>? = null, /* - * Additional data from the home server. + * Additional data from the homeserver. */ @Json(name = "unsigned") val unsigned: JsonDict? = null, diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/rest/EncryptedFileInfo.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/rest/EncryptedFileInfo.kt index 0ed6c0dac9..4fc3adb42c 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/rest/EncryptedFileInfo.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/rest/EncryptedFileInfo.kt @@ -29,12 +29,6 @@ data class EncryptedFileInfo( @Json(name = "url") val url: String? = null, - /** - * Not documented - */ - @Json(name = "mimetype") - val mimetype: String? = null, - /** * Required. A JSON Web Key object. */ diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/secrets/DefaultSharedSecretStorageService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/secrets/DefaultSharedSecretStorageService.kt index 1f80ce2c81..fb10cf4482 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/secrets/DefaultSharedSecretStorageService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/secrets/DefaultSharedSecretStorageService.kt @@ -18,7 +18,7 @@ package org.matrix.android.sdk.internal.crypto.secrets import org.matrix.android.sdk.api.extensions.orFalse import org.matrix.android.sdk.api.listeners.ProgressListener -import org.matrix.android.sdk.api.session.accountdata.AccountDataService +import org.matrix.android.sdk.api.session.accountdata.SessionAccountDataService import org.matrix.android.sdk.api.session.events.model.toContent import org.matrix.android.sdk.api.session.securestorage.EncryptedSecretContent import org.matrix.android.sdk.api.session.securestorage.IntegrityResult @@ -56,7 +56,7 @@ import kotlin.experimental.and internal class DefaultSharedSecretStorageService @Inject constructor( @UserId private val userId: String, - private val accountDataService: AccountDataService, + private val accountDataService: SessionAccountDataService, private val outgoingGossipingRequestManager: OutgoingGossipingRequestManager, private val coroutineDispatchers: MatrixCoroutineDispatchers, private val cryptoCoroutineScope: CoroutineScope @@ -84,7 +84,7 @@ internal class DefaultSharedSecretStorageService @Inject constructor( ) } ?: storageKeyContent - accountDataService.updateAccountData("$KEY_ID_BASE.$keyId", signedContent.toContent()) + accountDataService.updateUserAccountData("$KEY_ID_BASE.$keyId", signedContent.toContent()) SsssKeyCreationInfo( keyId = keyId, content = storageKeyContent, @@ -113,7 +113,7 @@ internal class DefaultSharedSecretStorageService @Inject constructor( ) } ?: storageKeyContent - accountDataService.updateAccountData( + accountDataService.updateUserAccountData( "$KEY_ID_BASE.$keyId", signedContent.toContent() ) @@ -127,11 +127,11 @@ internal class DefaultSharedSecretStorageService @Inject constructor( } override fun hasKey(keyId: String): Boolean { - return accountDataService.getAccountDataEvent("$KEY_ID_BASE.$keyId") != null + return accountDataService.getUserAccountDataEvent("$KEY_ID_BASE.$keyId") != null } override fun getKey(keyId: String): KeyInfoResult { - val accountData = accountDataService.getAccountDataEvent("$KEY_ID_BASE.$keyId") + val accountData = accountDataService.getUserAccountDataEvent("$KEY_ID_BASE.$keyId") ?: return KeyInfoResult.Error(SharedSecretStorageError.UnknownKey(keyId)) return SecretStorageKeyContent.fromJson(accountData.content)?.let { KeyInfoResult.Success( @@ -143,14 +143,14 @@ internal class DefaultSharedSecretStorageService @Inject constructor( override suspend fun setDefaultKey(keyId: String) { val existingKey = getKey(keyId) if (existingKey is KeyInfoResult.Success) { - accountDataService.updateAccountData(DEFAULT_KEY_ID, mapOf("key" to keyId)) + accountDataService.updateUserAccountData(DEFAULT_KEY_ID, mapOf("key" to keyId)) } else { throw SharedSecretStorageError.UnknownKey(keyId) } } override fun getDefaultKey(): KeyInfoResult { - val accountData = accountDataService.getAccountDataEvent(DEFAULT_KEY_ID) + val accountData = accountDataService.getUserAccountDataEvent(DEFAULT_KEY_ID) ?: return KeyInfoResult.Error(SharedSecretStorageError.UnknownKey(DEFAULT_KEY_ID)) val keyId = accountData.content["key"] as? String ?: return KeyInfoResult.Error(SharedSecretStorageError.UnknownKey(DEFAULT_KEY_ID)) @@ -178,7 +178,7 @@ internal class DefaultSharedSecretStorageService @Inject constructor( } } - accountDataService.updateAccountData( + accountDataService.updateUserAccountData( type = name, content = mapOf("encrypted" to encryptedContents) ) @@ -288,7 +288,7 @@ internal class DefaultSharedSecretStorageService @Inject constructor( } override fun getAlgorithmsForSecret(name: String): List { - val accountData = accountDataService.getAccountDataEvent(name) + val accountData = accountDataService.getUserAccountDataEvent(name) ?: return listOf(KeyInfoResult.Error(SharedSecretStorageError.UnknownSecret(name))) val encryptedContent = accountData.content[ENCRYPTED] as? Map<*, *> ?: return listOf(KeyInfoResult.Error(SharedSecretStorageError.SecretNotEncrypted(name))) @@ -303,7 +303,7 @@ internal class DefaultSharedSecretStorageService @Inject constructor( } override suspend fun getSecret(name: String, keyId: String?, secretKey: SsssKeySpec): String { - val accountData = accountDataService.getAccountDataEvent(name) ?: throw SharedSecretStorageError.UnknownSecret(name) + val accountData = accountDataService.getUserAccountDataEvent(name) ?: throw SharedSecretStorageError.UnknownSecret(name) val encryptedContent = accountData.content[ENCRYPTED] as? Map<*, *> ?: throw SharedSecretStorageError.SecretNotEncrypted(name) val key = keyId?.let { getKey(it) } as? KeyInfoResult.Success ?: getDefaultKey() as? KeyInfoResult.Success ?: throw SharedSecretStorageError.UnknownKey(name) @@ -368,7 +368,7 @@ internal class DefaultSharedSecretStorageService @Inject constructor( } secretNames.forEach { secretName -> - val secretEvent = accountDataService.getAccountDataEvent(secretName) + val secretEvent = accountDataService.getUserAccountDataEvent(secretName) ?: return IntegrityResult.Error(SharedSecretStorageError.UnknownSecret(secretName)) if ((secretEvent.content["encrypted"] as? Map<*, *>)?.get(keyInfo.id) == null) { return IntegrityResult.Error(SharedSecretStorageError.SecretNotEncryptedWithKey(secretName, keyInfo.id)) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/RealmCryptoStoreMigration.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/RealmCryptoStoreMigration.kt index bca7914388..8e77f5b823 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/RealmCryptoStoreMigration.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/RealmCryptoStoreMigration.kt @@ -18,6 +18,9 @@ package org.matrix.android.sdk.internal.crypto.store.db import com.squareup.moshi.Moshi import com.squareup.moshi.Types +import io.realm.DynamicRealm +import io.realm.RealmMigration +import io.realm.RealmObjectSchema import org.matrix.android.sdk.api.extensions.tryOrNull import org.matrix.android.sdk.api.util.JsonDict import org.matrix.android.sdk.internal.crypto.model.MXDeviceInfo @@ -35,29 +38,24 @@ import org.matrix.android.sdk.internal.crypto.store.db.model.KeysBackupDataEntit import org.matrix.android.sdk.internal.crypto.store.db.model.MyDeviceLastSeenInfoEntityFields import org.matrix.android.sdk.internal.crypto.store.db.model.OlmInboundGroupSessionEntityFields import org.matrix.android.sdk.internal.crypto.store.db.model.OlmSessionEntityFields +import org.matrix.android.sdk.internal.crypto.store.db.model.OutboundGroupSessionInfoEntityFields import org.matrix.android.sdk.internal.crypto.store.db.model.OutgoingGossipingRequestEntityFields import org.matrix.android.sdk.internal.crypto.store.db.model.SharedSessionEntityFields import org.matrix.android.sdk.internal.crypto.store.db.model.TrustLevelEntityFields import org.matrix.android.sdk.internal.crypto.store.db.model.UserEntityFields import org.matrix.android.sdk.internal.crypto.store.db.model.WithHeldSessionEntityFields +import org.matrix.android.sdk.internal.di.MoshiProvider import org.matrix.android.sdk.internal.di.SerializeNulls -import io.realm.DynamicRealm -import io.realm.RealmMigration -import io.realm.RealmObjectSchema -import org.matrix.android.sdk.internal.crypto.store.db.model.OutboundGroupSessionInfoEntityFields import org.matrix.androidsdk.crypto.data.MXOlmInboundGroupSession2 import timber.log.Timber -import javax.inject.Inject import org.matrix.androidsdk.crypto.data.MXDeviceInfo as LegacyMXDeviceInfo -internal class RealmCryptoStoreMigration @Inject constructor(private val crossSigningKeysMapper: CrossSigningKeysMapper) : RealmMigration { +internal object RealmCryptoStoreMigration : RealmMigration { - companion object { - // 0, 1, 2: legacy Riot-Android - // 3: migrate to RiotX schema - // 4, 5, 6, 7, 8, 9: migrations from RiotX (which was previously 1, 2, 3, 4, 5, 6) - const val CRYPTO_STORE_SCHEMA_VERSION = 12L - } + // 0, 1, 2: legacy Riot-Android + // 3: migrate to RiotX schema + // 4, 5, 6, 7, 8, 9: migrations from RiotX (which was previously 1, 2, 3, 4, 5, 6) + const val CRYPTO_STORE_SCHEMA_VERSION = 12L private fun RealmObjectSchema.addFieldIfNotExists(fieldName: String, fieldType: Class<*>): RealmObjectSchema { if (!hasField(fieldName)) { @@ -384,6 +382,8 @@ internal class RealmCryptoStoreMigration @Inject constructor(private val crossSi private fun migrateTo7(realm: DynamicRealm) { Timber.d("Step 6 -> 7") Timber.d("Updating KeyInfoEntity table") + val crossSigningKeysMapper = CrossSigningKeysMapper(MoshiProvider.providesMoshi()) + val keyInfoEntities = realm.where("KeyInfoEntity").findAll() try { keyInfoEntities.forEach { diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/tasks/InitializeCrossSigningTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/tasks/InitializeCrossSigningTask.kt index f8a8354e48..1d40e5defd 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/tasks/InitializeCrossSigningTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/tasks/InitializeCrossSigningTask.kt @@ -97,7 +97,7 @@ internal class DefaultInitializeCrossSigningTask @Inject constructor( Timber.v("## CrossSigning - sskPublicKey:$sskPublicKey") - // Sign userSigningKey with master + // Sign selfSigningKey with master val signedSSK = CryptoCrossSigningKey.Builder(userId, KeyUsage.SELF_SIGNING) .key(sskPublicKey) .build() diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/RealmSessionStoreMigration.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/RealmSessionStoreMigration.kt index 2b3c3b28ee..28ae4d8bfd 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/RealmSessionStoreMigration.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/RealmSessionStoreMigration.kt @@ -19,9 +19,10 @@ package org.matrix.android.sdk.internal.database import io.realm.DynamicRealm import io.realm.FieldAttribute import io.realm.RealmMigration -import org.matrix.android.sdk.api.session.room.model.VersioningState import org.matrix.android.sdk.api.session.events.model.EventType +import org.matrix.android.sdk.api.session.room.model.Membership import org.matrix.android.sdk.api.session.room.model.RoomJoinRulesContent +import org.matrix.android.sdk.api.session.room.model.VersioningState import org.matrix.android.sdk.api.session.room.model.create.RoomCreateContent import org.matrix.android.sdk.api.session.room.model.tag.RoomTag import org.matrix.android.sdk.internal.database.model.CurrentStateEventEntityFields @@ -40,14 +41,12 @@ import org.matrix.android.sdk.internal.database.model.SpaceChildSummaryEntityFie import org.matrix.android.sdk.internal.database.model.SpaceParentSummaryEntityFields import org.matrix.android.sdk.internal.database.model.TimelineEventEntityFields import org.matrix.android.sdk.internal.di.MoshiProvider +import org.matrix.android.sdk.internal.query.process import timber.log.Timber -import javax.inject.Inject -class RealmSessionStoreMigration @Inject constructor() : RealmMigration { +internal object RealmSessionStoreMigration : RealmMigration { - companion object { - const val SESSION_STORE_SCHEMA_VERSION = 14L - } + const val SESSION_STORE_SCHEMA_VERSION = 16L override fun migrate(realm: DynamicRealm, oldVersion: Long, newVersion: Long) { Timber.v("Migrating Realm Session from $oldVersion to $newVersion") @@ -66,6 +65,8 @@ class RealmSessionStoreMigration @Inject constructor() : RealmMigration { if (oldVersion <= 11) migrateTo12(realm) if (oldVersion <= 12) migrateTo13(realm) if (oldVersion <= 13) migrateTo14(realm) + if (oldVersion <= 14) migrateTo15(realm) + if (oldVersion <= 15) migrateTo16(realm) } private fun migrateTo1(realm: DynamicRealm) { @@ -292,7 +293,7 @@ class RealmSessionStoreMigration @Inject constructor() : RealmMigration { Timber.d("Step 13 -> 14") val roomAccountDataSchema = realm.schema.create("RoomAccountDataEntity") .addField(RoomAccountDataEntityFields.CONTENT_STR, String::class.java) - .addField(RoomAccountDataEntityFields.TYPE, String::class.java, FieldAttribute.INDEXED) + .addField(RoomAccountDataEntityFields.TYPE, String::class.java, FieldAttribute.INDEXED) realm.schema.get("RoomEntity") ?.addRealmListField(RoomEntityFields.ACCOUNT_DATA.`$`, roomAccountDataSchema) @@ -306,4 +307,27 @@ class RealmSessionStoreMigration @Inject constructor() : RealmMigration { roomAccountDataSchema.isEmbedded = true } + + private fun migrateTo15(realm: DynamicRealm) { + Timber.d("Step 14 -> 15") + // fix issue with flattenParentIds on DM that kept growing with duplicate + // so we reset it, will be updated next sync + realm.where("RoomSummaryEntity") + .process(RoomSummaryEntityFields.MEMBERSHIP_STR, Membership.activeMemberships()) + .equalTo(RoomSummaryEntityFields.IS_DIRECT, true) + .findAll() + .onEach { + it.setString(RoomSummaryEntityFields.FLATTEN_PARENT_IDS, null) + } + } + + private fun migrateTo16(realm: DynamicRealm) { + Timber.d("Step 15 -> 16") + realm.schema.get("HomeServerCapabilitiesEntity") + ?.addField(HomeServerCapabilitiesEntityFields.ROOM_VERSIONS_JSON, String::class.java) + ?.transform { obj -> + // Schedule a refresh of the capabilities + obj.setLong(HomeServerCapabilitiesEntityFields.LAST_UPDATED_TIMESTAMP, 0) + } + } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/SessionRealmConfigurationFactory.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/SessionRealmConfigurationFactory.kt index 244fe3432a..1771c5b202 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/SessionRealmConfigurationFactory.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/SessionRealmConfigurationFactory.kt @@ -43,7 +43,6 @@ internal class SessionRealmConfigurationFactory @Inject constructor( @SessionFilesDirectory val directory: File, @SessionId val sessionId: String, @UserMd5 val userMd5: String, - val migration: RealmSessionStoreMigration, context: Context) { // Keep legacy preferences name for compatibility reason @@ -72,7 +71,7 @@ internal class SessionRealmConfigurationFactory @Inject constructor( .allowWritesOnUiThread(true) .modules(SessionRealmModule()) .schemaVersion(RealmSessionStoreMigration.SESSION_STORE_SCHEMA_VERSION) - .migration(migration) + .migration(RealmSessionStoreMigration) .build() // Try creating a realm instance and if it succeeds we can clear the flag diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/mapper/AccountDataMapper.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/mapper/AccountDataMapper.kt index 4edfdad897..dca0f927ad 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/mapper/AccountDataMapper.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/mapper/AccountDataMapper.kt @@ -17,7 +17,8 @@ package org.matrix.android.sdk.internal.database.mapper import com.squareup.moshi.Moshi -import org.matrix.android.sdk.api.session.accountdata.AccountDataEvent +import org.matrix.android.sdk.api.session.accountdata.UserAccountDataEvent +import org.matrix.android.sdk.api.session.room.accountdata.RoomAccountDataEvent import org.matrix.android.sdk.api.util.JSON_DICT_PARAMETERIZED_TYPE import org.matrix.android.sdk.internal.database.model.RoomAccountDataEntity import org.matrix.android.sdk.internal.database.model.UserAccountDataEntity @@ -27,15 +28,16 @@ internal class AccountDataMapper @Inject constructor(moshi: Moshi) { private val adapter = moshi.adapter>(JSON_DICT_PARAMETERIZED_TYPE) - fun map(entity: UserAccountDataEntity): AccountDataEvent { - return AccountDataEvent( + fun map(entity: UserAccountDataEntity): UserAccountDataEvent { + return UserAccountDataEvent( type = entity.type ?: "", content = entity.contentStr?.let { adapter.fromJson(it) }.orEmpty() ) } - fun map(entity: RoomAccountDataEntity): AccountDataEvent { - return AccountDataEvent( + fun map(roomId: String, entity: RoomAccountDataEntity): RoomAccountDataEvent { + return RoomAccountDataEvent( + roomId = roomId, type = entity.type ?: "", content = entity.contentStr?.let { adapter.fromJson(it) }.orEmpty() ) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/mapper/HomeServerCapabilitiesMapper.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/mapper/HomeServerCapabilitiesMapper.kt index b18c67294f..2575cdef26 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/mapper/HomeServerCapabilitiesMapper.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/mapper/HomeServerCapabilitiesMapper.kt @@ -16,8 +16,15 @@ package org.matrix.android.sdk.internal.database.mapper +import org.matrix.android.sdk.api.extensions.tryOrNull import org.matrix.android.sdk.api.session.homeserver.HomeServerCapabilities +import org.matrix.android.sdk.api.session.homeserver.RoomVersionCapabilities +import org.matrix.android.sdk.api.session.homeserver.RoomVersionInfo +import org.matrix.android.sdk.api.session.homeserver.RoomVersionStatus import org.matrix.android.sdk.internal.database.model.HomeServerCapabilitiesEntity +import org.matrix.android.sdk.internal.di.MoshiProvider +import org.matrix.android.sdk.internal.session.homeserver.RoomVersions +import org.matrix.android.sdk.internal.session.room.version.DefaultRoomVersionService /** * HomeServerCapabilitiesEntity -> HomeSeverCapabilities @@ -29,7 +36,30 @@ internal object HomeServerCapabilitiesMapper { canChangePassword = entity.canChangePassword, maxUploadFileSize = entity.maxUploadFileSize, lastVersionIdentityServerSupported = entity.lastVersionIdentityServerSupported, - defaultIdentityServerUrl = entity.defaultIdentityServerUrl + defaultIdentityServerUrl = entity.defaultIdentityServerUrl, + roomVersions = mapRoomVersion(entity.roomVersionsJson) ) } + + private fun mapRoomVersion(roomVersionsJson: String?): RoomVersionCapabilities? { + roomVersionsJson ?: return null + + return tryOrNull { + MoshiProvider.providesMoshi().adapter(RoomVersions::class.java).fromJson(roomVersionsJson)?.let { + RoomVersionCapabilities( + defaultRoomVersion = it.default ?: DefaultRoomVersionService.DEFAULT_ROOM_VERSION, + supportedVersion = it.available.entries.map { entry -> + RoomVersionInfo( + version = entry.key, + status = if (entry.value == "stable") { + RoomVersionStatus.STABLE + } else { + RoomVersionStatus.UNSTABLE + } + ) + } + ) + } + } + } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/HomeServerCapabilitiesEntity.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/HomeServerCapabilitiesEntity.kt index 763dcf80a2..980449ddfb 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/HomeServerCapabilitiesEntity.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/HomeServerCapabilitiesEntity.kt @@ -21,6 +21,7 @@ import org.matrix.android.sdk.api.session.homeserver.HomeServerCapabilities internal open class HomeServerCapabilitiesEntity( var canChangePassword: Boolean = true, + var roomVersionsJson: String? = null, var maxUploadFileSize: Long = HomeServerCapabilities.MAX_UPLOAD_FILE_SIZE_UNKNOWN, var lastVersionIdentityServerSupported: Boolean = false, var defaultIdentityServerUrl: String? = null, diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/UserAccountDataEntity.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/UserAccountDataEntity.kt index cfdb84d033..e258c20df5 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/UserAccountDataEntity.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/UserAccountDataEntity.kt @@ -20,7 +20,7 @@ import io.realm.RealmObject import io.realm.annotations.Index /** - * Clients can store custom config data for their account on their HomeServer. + * Clients can store custom config data for their account on their homeserver. * This account data will be synced between different devices and can persist across installations on a particular device. * Users may only view the account data for their own account. * The account_data may be either global or scoped to a particular rooms. diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/federation/FederationModule.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/federation/FederationModule.kt index 320bf1d445..a4eef80c58 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/federation/FederationModule.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/federation/FederationModule.kt @@ -37,7 +37,8 @@ internal abstract class FederationModule { fun providesFederationAPI(@Unauthenticated okHttpClient: Lazy, sessionParams: SessionParams, retrofitFactory: RetrofitFactory): FederationAPI { - return retrofitFactory.create(okHttpClient, sessionParams.homeServerUrl).create(FederationAPI::class.java) + return retrofitFactory.create(okHttpClient, sessionParams.homeServerUrlBase) + .create(FederationAPI::class.java) } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/legacy/DefaultLegacySessionImporter.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/legacy/DefaultLegacySessionImporter.kt index e0e2f96fa9..ad2aff4c9d 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/legacy/DefaultLegacySessionImporter.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/legacy/DefaultLegacySessionImporter.kt @@ -42,7 +42,6 @@ import org.matrix.android.sdk.internal.legacy.riot.HomeServerConnectionConfig as internal class DefaultLegacySessionImporter @Inject constructor( private val context: Context, private val sessionParamsStore: SessionParamsStore, - private val realmCryptoStoreMigration: RealmCryptoStoreMigration, private val realmKeysUtils: RealmKeysUtils ) : LegacySessionImporter { @@ -153,7 +152,7 @@ internal class DefaultLegacySessionImporter @Inject constructor( } private fun importCryptoDb(legacyConfig: LegacyHomeServerConnectionConfig) { - // Here we migrate the DB, we copy the crypto DB to the location specific to RiotX, and we encrypt it. + // Here we migrate the DB, we copy the crypto DB to the location specific to Matrix SDK2, and we encrypt it. val userMd5 = legacyConfig.credentials.userId.md5() val sessionId = legacyConfig.credentials.let { (if (it.deviceId.isNullOrBlank()) it.userId else "${it.userId}|${it.deviceId}").md5() } @@ -172,17 +171,17 @@ internal class DefaultLegacySessionImporter @Inject constructor( .name("crypto_store.realm") .modules(RealmCryptoStoreModule()) .schemaVersion(RealmCryptoStoreMigration.CRYPTO_STORE_SCHEMA_VERSION) - .migration(realmCryptoStoreMigration) + .migration(RealmCryptoStoreMigration) .build() Timber.d("Migration: copy DB to encrypted DB") Realm.getInstance(realmConfiguration).use { - // Move the DB to the new location, handled by RiotX + // Move the DB to the new location, handled by Matrix SDK2 it.writeEncryptedCopyTo(File(newLocation, realmConfiguration.realmFileName), realmKeysUtils.getRealmEncryptionKey(keyAlias)) } } - // Delete all the files created by Riot Android which will not be used anymore by RiotX + // Delete all the files created by Riot Android which will not be used anymore by Element private fun clearFileSystem(legacyConfig: LegacyHomeServerConnectionConfig) { val cryptoFolder = legacyConfig.credentials.userId.md5() diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/legacy/riot/Credentials.java b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/legacy/riot/Credentials.java index 6844744044..0ca0c7db85 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/legacy/riot/Credentials.java +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/legacy/riot/Credentials.java @@ -41,7 +41,7 @@ public class Credentials { public String deviceId; - // Optional data that may contain info to override home server and/or identity server + // Optional data that may contain info to override homeserver and/or identity server public WellKnown wellKnown; public JSONObject toJson() throws JSONException { diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/legacy/riot/HomeServerConnectionConfig.java b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/legacy/riot/HomeServerConnectionConfig.java index 21d069f295..75fc187c45 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/legacy/riot/HomeServerConnectionConfig.java +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/legacy/riot/HomeServerConnectionConfig.java @@ -44,7 +44,7 @@ */ public class HomeServerConnectionConfig { - // the home server URI + // the homeserver URI private Uri mHomeServerUri; // the jitsi server URI. Can be null @Nullable @@ -82,7 +82,7 @@ private HomeServerConnectionConfig() { } /** - * Update the home server URI. + * Update the homeserver URI. * * @param uri the new HS uri */ @@ -91,7 +91,7 @@ public void setHomeserverUri(Uri uri) { } /** - * @return the home server uri + * @return the homeserver uri */ public Uri getHomeserverUri() { return mHomeServerUri; @@ -145,7 +145,7 @@ public Credentials getCredentials() { public void setCredentials(Credentials credentials) { mCredentials = credentials; - // Override home server url and/or identity server url if provided + // Override homeserver url and/or identity server url if provided if (credentials.wellKnown != null) { if (credentials.wellKnown.homeServer != null) { String homeServerUrl = credentials.wellKnown.homeServer.baseURL; @@ -200,7 +200,7 @@ public boolean shouldPin() { } /** - * TLS versions accepted for TLS connections with the home server. + * TLS versions accepted for TLS connections with the homeserver. */ @Nullable public List getAcceptedTlsVersions() { @@ -208,7 +208,7 @@ public List getAcceptedTlsVersions() { } /** - * TLS cipher suites accepted for TLS connections with the home server. + * TLS cipher suites accepted for TLS connections with the homeserver. */ @Nullable public List getAcceptedTlsCipherSuites() { @@ -426,7 +426,7 @@ public Builder(HomeServerConnectionConfig from) { */ public Builder withHomeServerUri(final Uri homeServerUri) { if (homeServerUri == null || (!"http".equals(homeServerUri.getScheme()) && !"https".equals(homeServerUri.getScheme()))) { - throw new RuntimeException("Invalid home server URI: " + homeServerUri); + throw new RuntimeException("Invalid homeserver URI: " + homeServerUri); } // remove trailing / @@ -435,7 +435,7 @@ public Builder withHomeServerUri(final Uri homeServerUri) { String url = homeServerUri.toString(); mHomeServerConnectionConfig.mHomeServerUri = Uri.parse(url.substring(0, url.length() - 1)); } catch (Exception e) { - throw new RuntimeException("Invalid home server URI: " + homeServerUri); + throw new RuntimeException("Invalid homeserver URI: " + homeServerUri); } } else { mHomeServerConnectionConfig.mHomeServerUri = homeServerUri; @@ -549,7 +549,7 @@ public Builder withShouldAcceptTlsExtensions(boolean shouldAcceptTlsExtension) { } /** - * Add an accepted TLS version for TLS connections with the home server. + * Add an accepted TLS version for TLS connections with the homeserver. * * @param tlsVersion the tls version to add to the set of TLS versions accepted. * @return this builder @@ -577,7 +577,7 @@ public Builder forceUsageOfTlsVersions(boolean forceUsageOfTlsVersions) { } /** - * Add a TLS cipher suite to the list of accepted TLS connections with the home server. + * Add a TLS cipher suite to the list of accepted TLS connections with the homeserver. * * @param tlsCipherSuite the tls cipher suite to add. * @return this builder @@ -666,7 +666,7 @@ public Builder withProxy(@Nullable String proxyHostname, int proxyPort) { public HomeServerConnectionConfig build() { // Check mandatory parameters if (mHomeServerConnectionConfig.mHomeServerUri == null) { - throw new RuntimeException("Home server URI not set"); + throw new RuntimeException("Homeserver URI not set"); } return mHomeServerConnectionConfig; diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/legacy/riot/LoginStorage.java b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/legacy/riot/LoginStorage.java index 516007524e..2820b66886 100755 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/legacy/riot/LoginStorage.java +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/legacy/riot/LoginStorage.java @@ -38,7 +38,7 @@ public class LoginStorage { private static final String PREFS_LOGIN = "Vector.LoginStorage"; - // multi accounts + home server config + // multi accounts + homeserver config private static final String PREFS_KEY_CONNECTION_CONFIGS = "PREFS_KEY_CONNECTION_CONFIGS"; private final Context mContext; @@ -49,7 +49,7 @@ public LoginStorage(Context appContext) { } /** - * @return the list of home server configurations. + * @return the list of homeserver configurations. */ public List getCredentialsList() { SharedPreferences prefs = mContext.getSharedPreferences(PREFS_LOGIN, Context.MODE_PRIVATE); @@ -85,7 +85,7 @@ public List getCredentialsList() { /** * Add a credentials to the credentials list * - * @param config the home server config to add. + * @param config the homeserver config to add. */ public void addCredentials(HomeServerConnectionConfig config) { if (null != config && config.getCredentials() != null) { @@ -203,4 +203,4 @@ public void clear() { //Need to commit now because called before forcing an app restart editor.commit(); } -} \ No newline at end of file +} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/network/UserAgentHolder.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/network/UserAgentHolder.kt index 973c120f59..1a88404128 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/network/UserAgentHolder.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/network/UserAgentHolder.kt @@ -36,7 +36,7 @@ internal class UserAgentHolder @Inject constructor(private val context: Context, /** * Create an user agent with the application version. - * Ex: RiotX/1.0.0 (Linux; U; Android 6.0.1; SM-A510F Build/MMB29; Flavour GPlay; MatrixAndroidSDK_X 1.0) + * Ex: Element/1.0.0 (Linux; U; Android 6.0.1; SM-A510F Build/MMB29; Flavour GPlay; MatrixAndroidSDK_X 1.0) * * @param flavorDescription the flavor description */ diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/network/ssl/CertUtil.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/network/ssl/CertUtil.kt index 9d7263f56a..976751446b 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/network/ssl/CertUtil.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/network/ssl/CertUtil.kt @@ -253,7 +253,7 @@ internal object CertUtil { val list = ArrayList() list.add(builder.build()) // TODO: we should display a warning if user enter an http url - if (hsConfig.allowHttpExtension || hsConfig.homeServerUri.toString().startsWith("http://")) { + if (hsConfig.allowHttpExtension || hsConfig.homeServerUriBase.toString().startsWith("http://")) { list.add(ConnectionSpec.CLEARTEXT) } return list diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/raw/DefaultRawService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/raw/DefaultRawService.kt index 42b826de16..bca1e498de 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/raw/DefaultRawService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/raw/DefaultRawService.kt @@ -29,10 +29,9 @@ internal class DefaultRawService @Inject constructor( return getUrlTask.execute(GetUrlTask.Params(url, cacheStrategy)) } - override suspend fun getWellknown(userId: String): String { - val homeServerDomain = userId.substringAfter(":") + override suspend fun getWellknown(domain: String): String { return getUrl( - "https://$homeServerDomain/.well-known/matrix/client", + "https://$domain/.well-known/matrix/client", CacheStrategy.TtlCache(TimeUnit.HOURS.toMillis(8), false) ) } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/DefaultSession.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/DefaultSession.kt index 1f47978198..c2bd1e24ed 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/DefaultSession.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/DefaultSession.kt @@ -29,7 +29,7 @@ import org.matrix.android.sdk.api.pushrules.PushRuleService import org.matrix.android.sdk.api.session.Session import org.matrix.android.sdk.api.session.SessionLifecycleObserver import org.matrix.android.sdk.api.session.account.AccountService -import org.matrix.android.sdk.api.session.accountdata.AccountDataService +import org.matrix.android.sdk.api.session.accountdata.SessionAccountDataService import org.matrix.android.sdk.api.session.cache.CacheService import org.matrix.android.sdk.api.session.call.CallSignalingService import org.matrix.android.sdk.api.session.content.ContentUploadStateTracker @@ -74,7 +74,6 @@ import org.matrix.android.sdk.internal.session.identity.DefaultIdentityService import org.matrix.android.sdk.internal.session.sync.SyncTokenStore import org.matrix.android.sdk.internal.session.sync.job.SyncThread import org.matrix.android.sdk.internal.session.sync.job.SyncWorker -import org.matrix.android.sdk.internal.session.user.accountdata.UserAccountDataService import org.matrix.android.sdk.internal.util.createUIHandler import timber.log.Timber import javax.inject.Inject @@ -118,7 +117,7 @@ internal class DefaultSession @Inject constructor( private val contentDownloadStateTracker: ContentDownloadStateTracker, private val initialSyncProgressService: Lazy, private val homeServerCapabilitiesService: Lazy, - private val accountDataService: Lazy, + private val accountDataService: Lazy, private val _sharedSecretStorageService: Lazy, private val accountService: Lazy, private val eventService: Lazy, @@ -293,7 +292,7 @@ internal class DefaultSession @Inject constructor( override fun openIdService(): OpenIdService = openIdService.get() - override fun userAccountDataService(): AccountDataService = accountDataService.get() + override fun accountDataService(): SessionAccountDataService = accountDataService.get() override fun getOkHttpClient(): OkHttpClient { return unauthenticatedWithCertificateOkHttpClient.get() @@ -314,7 +313,7 @@ internal class DefaultSession @Inject constructor( override fun getUiaSsoFallbackUrl(authenticationSessionId: String): String { val hsBas = sessionParams.homeServerConnectionConfig - .homeServerUri + .homeServerUriBase .toString() .trim { it == '/' } return buildString { diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/SessionModule.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/SessionModule.kt index 49ce92372e..cb29cb4819 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/SessionModule.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/SessionModule.kt @@ -34,7 +34,7 @@ import org.matrix.android.sdk.api.auth.data.sessionId import org.matrix.android.sdk.api.crypto.MXCryptoConfig import org.matrix.android.sdk.api.session.Session import org.matrix.android.sdk.api.session.SessionLifecycleObserver -import org.matrix.android.sdk.api.session.accountdata.AccountDataService +import org.matrix.android.sdk.api.session.accountdata.SessionAccountDataService import org.matrix.android.sdk.api.session.events.EventService import org.matrix.android.sdk.api.session.homeserver.HomeServerCapabilitiesService import org.matrix.android.sdk.api.session.initsync.InitialSyncProgressService @@ -93,7 +93,7 @@ import org.matrix.android.sdk.internal.session.room.send.queue.EventSenderProces import org.matrix.android.sdk.internal.session.room.tombstone.RoomTombstoneEventProcessor import org.matrix.android.sdk.internal.session.securestorage.DefaultSecureStorageService import org.matrix.android.sdk.internal.session.typing.DefaultTypingUsersTracker -import org.matrix.android.sdk.internal.session.user.accountdata.UserAccountDataService +import org.matrix.android.sdk.internal.session.user.accountdata.DefaultSessionAccountDataService import org.matrix.android.sdk.internal.session.widgets.DefaultWidgetURLFormatter import org.matrix.android.sdk.internal.util.md5 import retrofit2.Retrofit @@ -261,7 +261,7 @@ internal abstract class SessionModule { sessionParams: SessionParams, retrofitFactory: RetrofitFactory): Retrofit { return retrofitFactory - .create(okHttpClient, sessionParams.homeServerConnectionConfig.homeServerUri.toString()) + .create(okHttpClient, sessionParams.homeServerConnectionConfig.homeServerUriBase.toString()) } @JvmStatic @@ -364,7 +364,7 @@ internal abstract class SessionModule { abstract fun bindHomeServerCapabilitiesService(service: DefaultHomeServerCapabilitiesService): HomeServerCapabilitiesService @Binds - abstract fun bindAccountDataService(service: UserAccountDataService): AccountDataService + abstract fun bindSessionAccountDataService(service: DefaultSessionAccountDataService): SessionAccountDataService @Binds abstract fun bindEventService(service: DefaultEventService): EventService diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/call/CallEventProcessor.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/call/CallEventProcessor.kt index a190ff62ac..473adeb8d2 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/call/CallEventProcessor.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/call/CallEventProcessor.kt @@ -37,7 +37,9 @@ internal class CallEventProcessor @Inject constructor(private val callSignalingH EventType.CALL_CANDIDATES, EventType.CALL_INVITE, EventType.CALL_HANGUP, - EventType.ENCRYPTED + EventType.ENCRYPTED, + EventType.CALL_ASSERTED_IDENTITY, + EventType.CALL_ASSERTED_IDENTITY_PREFIX ) private val eventsToPostProcess = mutableListOf() @@ -57,9 +59,8 @@ internal class CallEventProcessor @Inject constructor(private val callSignalingH return eventType == EventType.CALL_INVITE } - suspend fun processFastLane(event: Event) { - eventsToPostProcess.add(event) - onPostProcess() + fun processFastLane(event: Event) { + dispatchToCallSignalingHandlerIfNeeded(event) } override suspend fun onPostProcess() { @@ -71,13 +72,12 @@ internal class CallEventProcessor @Inject constructor(private val callSignalingH private fun dispatchToCallSignalingHandlerIfNeeded(event: Event) { val now = System.currentTimeMillis() - // TODO might check if an invite is not closed (hangup/answered) in the same event batch? event.roomId ?: return Unit.also { Timber.w("Event with no room id ${event.eventId}") } val age = now - (event.ageLocalTs ?: now) if (age > 40_000) { - // To old to ring? + // Too old to ring? return } callSignalingHandler.onCallEvent(event) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/call/CallListenersDispatcher.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/call/CallListenersDispatcher.kt index 1de2d8a106..dad17f4ac9 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/call/CallListenersDispatcher.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/call/CallListenersDispatcher.kt @@ -20,6 +20,7 @@ import org.matrix.android.sdk.api.extensions.tryOrNull import org.matrix.android.sdk.api.session.call.CallListener import org.matrix.android.sdk.api.session.call.MxCall import org.matrix.android.sdk.api.session.room.model.call.CallAnswerContent +import org.matrix.android.sdk.api.session.room.model.call.CallAssertedIdentityContent import org.matrix.android.sdk.api.session.room.model.call.CallCandidatesContent import org.matrix.android.sdk.api.session.room.model.call.CallHangupContent import org.matrix.android.sdk.api.session.room.model.call.CallInviteContent @@ -64,6 +65,10 @@ internal class CallListenersDispatcher(private val listeners: Set) it.onCallNegotiateReceived(callNegotiateContent) } + override fun onCallAssertedIdentityReceived(callAssertedIdentityContent: CallAssertedIdentityContent) = dispatch { + it.onCallAssertedIdentityReceived(callAssertedIdentityContent) + } + private fun dispatch(lambda: (CallListener) -> Unit) { listeners.toList().forEach { tryOrNull { diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/call/CallSignalingHandler.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/call/CallSignalingHandler.kt index 61ea660b60..b0901af719 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/call/CallSignalingHandler.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/call/CallSignalingHandler.kt @@ -23,6 +23,7 @@ import org.matrix.android.sdk.api.session.events.model.Event import org.matrix.android.sdk.api.session.events.model.EventType import org.matrix.android.sdk.api.session.events.model.toModel import org.matrix.android.sdk.api.session.room.model.call.CallAnswerContent +import org.matrix.android.sdk.api.session.room.model.call.CallAssertedIdentityContent import org.matrix.android.sdk.api.session.room.model.call.CallCandidatesContent import org.matrix.android.sdk.api.session.room.model.call.CallHangupContent import org.matrix.android.sdk.api.session.room.model.call.CallInviteContent @@ -40,6 +41,7 @@ internal class CallSignalingHandler @Inject constructor(private val activeCallHa private val mxCallFactory: MxCallFactory, @UserId private val userId: String) { + private val invitedCallIds = mutableSetOf() private val callListeners = mutableSetOf() private val callListenersDispatcher = CallListenersDispatcher(callListeners) @@ -53,28 +55,42 @@ internal class CallSignalingHandler @Inject constructor(private val activeCallHa fun onCallEvent(event: Event) { when (event.getClearType()) { - EventType.CALL_ANSWER -> { + EventType.CALL_ANSWER -> { handleCallAnswerEvent(event) } - EventType.CALL_INVITE -> { + EventType.CALL_INVITE -> { handleCallInviteEvent(event) } - EventType.CALL_HANGUP -> { + EventType.CALL_HANGUP -> { handleCallHangupEvent(event) } - EventType.CALL_REJECT -> { + EventType.CALL_REJECT -> { handleCallRejectEvent(event) } - EventType.CALL_CANDIDATES -> { + EventType.CALL_CANDIDATES -> { handleCallCandidatesEvent(event) } - EventType.CALL_SELECT_ANSWER -> { + EventType.CALL_SELECT_ANSWER -> { handleCallSelectAnswerEvent(event) } - EventType.CALL_NEGOTIATE -> { + EventType.CALL_NEGOTIATE -> { handleCallNegotiateEvent(event) } + EventType.CALL_ASSERTED_IDENTITY, + EventType.CALL_ASSERTED_IDENTITY_PREFIX -> { + handleCallAssertedIdentityEvent(event) + } + } + } + + private fun handleCallAssertedIdentityEvent(event: Event) { + val content = event.getClearContent().toModel() ?: return + val call = content.getCall() ?: return + if (call.ourPartyId == content.partyId) { + // Ignore remote echo (not that we send asserted identity, but still...) + return } + callListenersDispatcher.onCallAssertedIdentityReceived(content) } private fun handleCallNegotiateEvent(event: Event) { @@ -167,17 +183,17 @@ internal class CallSignalingHandler @Inject constructor(private val activeCallHa val content = event.getClearContent().toModel() ?: return content.callId ?: return - if (activeCallHandler.getCallWithId(content.callId) != null) { + if (invitedCallIds.contains(content.callId)) { // Call is already known, maybe due to fast lane. Ignore Timber.d("Ignoring already known call invite") return } - val incomingCall = mxCallFactory.createIncomingCall( roomId = event.roomId, opponentUserId = event.senderId, content = content ) ?: return + invitedCallIds.add(content.callId) activeCallHandler.addCall(incomingCall) callListenersDispatcher.onCallInviteReceived(incomingCall, content) } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/cleanup/CleanupSession.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/cleanup/CleanupSession.kt index b5c1e6a282..8870d92a35 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/cleanup/CleanupSession.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/cleanup/CleanupSession.kt @@ -51,15 +51,12 @@ internal class CleanupSession @Inject constructor( @UserMd5 private val userMd5: String ) { suspend fun handle() { - Timber.d("Cleanup: release session...") - sessionManager.releaseSession(sessionId) + Timber.d("Cleanup: delete session params...") + sessionParamsStore.delete(sessionId) Timber.d("Cleanup: cancel pending works...") workManagerProvider.cancelAllWorks() - Timber.d("Cleanup: delete session params...") - sessionParamsStore.delete(sessionId) - Timber.d("Cleanup: clear session data...") clearSessionDataTask.execute(Unit) @@ -74,6 +71,9 @@ internal class CleanupSession @Inject constructor( realmKeysUtils.clear(SessionModule.getKeyAlias(userMd5)) realmKeysUtils.clear(CryptoModule.getKeyAlias(userMd5)) + Timber.d("Cleanup: release session...") + sessionManager.releaseSession(sessionId) + // Sanity check if (BuildConfig.DEBUG) { Realm.getGlobalInstanceCount(realmSessionConfiguration) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/content/DefaultContentUrlResolver.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/content/DefaultContentUrlResolver.kt index f765056496..e4efdaa254 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/content/DefaultContentUrlResolver.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/content/DefaultContentUrlResolver.kt @@ -26,7 +26,7 @@ private const val MATRIX_CONTENT_URI_SCHEME = "mxc://" internal class DefaultContentUrlResolver @Inject constructor(homeServerConnectionConfig: HomeServerConnectionConfig) : ContentUrlResolver { - private val baseUrl = homeServerConnectionConfig.homeServerUri.toString().ensureTrailingSlash() + private val baseUrl = homeServerConnectionConfig.homeServerUriBase.toString().ensureTrailingSlash() override val uploadUrl = baseUrl + NetworkConstants.URI_API_MEDIA_PREFIX_PATH_R0 + "upload" diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/content/UploadContentWorker.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/content/UploadContentWorker.kt index 237411db53..f14c85cf80 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/content/UploadContentWorker.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/content/UploadContentWorker.kt @@ -234,7 +234,7 @@ internal class UploadContentWorker(val context: Context, params: WorkerParameter .also { filesToDelete.add(it) } uploadedFileEncryptedFileInfo = - MXEncryptedAttachments.encrypt(fileToUpload.inputStream(), attachment.getSafeMimeType(), encryptedFile) { read, total -> + MXEncryptedAttachments.encrypt(fileToUpload.inputStream(), encryptedFile) { read, total -> notifyTracker(params) { contentUploadStateTracker.setEncrypting(it, read.toLong(), total.toLong()) } @@ -315,7 +315,7 @@ internal class UploadContentWorker(val context: Context, params: WorkerParameter if (params.isEncrypted) { Timber.v("Encrypt thumbnail") notifyTracker(params) { contentUploadStateTracker.setEncryptingThumbnail(it) } - val encryptionResult = MXEncryptedAttachments.encryptAttachment(thumbnailData.bytes.inputStream(), thumbnailData.mimeType) + val encryptionResult = MXEncryptedAttachments.encryptAttachment(thumbnailData.bytes.inputStream()) val contentUploadResponse = fileUploader.uploadByteArray( byteArray = encryptionResult.encryptedByteArray, filename = null, diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/events/DefaultEventService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/events/DefaultEventService.kt index d7e9ef2ee0..9d80f27e01 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/events/DefaultEventService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/events/DefaultEventService.kt @@ -29,7 +29,7 @@ internal class DefaultEventService @Inject constructor( override suspend fun getEvent(roomId: String, eventId: String): Event { val event = getEventTask.execute(GetEventTask.Params(roomId, eventId)) - + event.ageLocalTs = event.unsignedData?.age?.let { System.currentTimeMillis() - it } // Fast lane to the call event processors: try to make the incoming call ring faster if (callEventProcessor.shouldProcessFastLane(event.getClearType())) { callEventProcessor.processFastLane(event) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/homeserver/DefaultHomeServerCapabilitiesService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/homeserver/DefaultHomeServerCapabilitiesService.kt index 0ed690d972..4c755b54b5 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/homeserver/DefaultHomeServerCapabilitiesService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/homeserver/DefaultHomeServerCapabilitiesService.kt @@ -16,18 +16,12 @@ package org.matrix.android.sdk.internal.session.homeserver -import com.zhuinden.monarchy.Monarchy -import io.realm.Realm import org.matrix.android.sdk.api.session.homeserver.HomeServerCapabilities import org.matrix.android.sdk.api.session.homeserver.HomeServerCapabilitiesService -import org.matrix.android.sdk.internal.database.mapper.HomeServerCapabilitiesMapper -import org.matrix.android.sdk.internal.database.model.HomeServerCapabilitiesEntity -import org.matrix.android.sdk.internal.database.query.get -import org.matrix.android.sdk.internal.di.SessionDatabase import javax.inject.Inject internal class DefaultHomeServerCapabilitiesService @Inject constructor( - @SessionDatabase private val monarchy: Monarchy, + private val homeServerCapabilitiesDataSource: HomeServerCapabilitiesDataSource, private val getHomeServerCapabilitiesTask: GetHomeServerCapabilitiesTask ) : HomeServerCapabilitiesService { @@ -36,11 +30,7 @@ internal class DefaultHomeServerCapabilitiesService @Inject constructor( } override fun getHomeServerCapabilities(): HomeServerCapabilities { - return Realm.getInstance(monarchy.realmConfiguration).use { realm -> - HomeServerCapabilitiesEntity.get(realm)?.let { - HomeServerCapabilitiesMapper.map(it) - } - } + return homeServerCapabilitiesDataSource.getHomeServerCapabilities() ?: HomeServerCapabilities() } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/homeserver/GetCapabilitiesResult.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/homeserver/GetCapabilitiesResult.kt index ab029a0fce..c4bc09a233 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/homeserver/GetCapabilitiesResult.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/homeserver/GetCapabilitiesResult.kt @@ -19,6 +19,7 @@ package org.matrix.android.sdk.internal.session.homeserver import com.squareup.moshi.Json import com.squareup.moshi.JsonClass import org.matrix.android.sdk.api.extensions.orTrue +import org.matrix.android.sdk.api.util.JsonDict /** * Ref: https://matrix.org/docs/spec/client_server/latest#get-matrix-client-r0-capabilities @@ -38,9 +39,14 @@ internal data class Capabilities( * Capability to indicate if the user can change their password. */ @Json(name = "m.change_password") - val changePassword: ChangePassword? = null + val changePassword: ChangePassword? = null, - // No need for m.room_versions for the moment + /** + * This capability describes the default and available room versions a server supports, and at what level of stability. + * Clients should make use of this capability to determine if users need to be encouraged to upgrade their rooms. + */ + @Json(name = "m.room_versions") + val roomVersions: RoomVersions? = null ) @JsonClass(generateAdapter = true) @@ -52,6 +58,21 @@ internal data class ChangePassword( val enabled: Boolean? ) +@JsonClass(generateAdapter = true) +internal data class RoomVersions( + /** + * Required. The default room version the server is using for new rooms. + */ + @Json(name = "default") + val default: String?, + + /** + * Required. A detailed description of the room versions the server supports. + */ + @Json(name = "available") + val available: JsonDict +) + // The spec says: If not present, the client should assume that password changes are possible via the API internal fun GetCapabilitiesResult.canChangePassword(): Boolean { return capabilities?.changePassword?.enabled.orTrue() diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/homeserver/GetHomeServerCapabilitiesTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/homeserver/GetHomeServerCapabilitiesTask.kt index 740370123f..612b98f863 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/homeserver/GetHomeServerCapabilitiesTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/homeserver/GetHomeServerCapabilitiesTask.kt @@ -17,6 +17,7 @@ package org.matrix.android.sdk.internal.session.homeserver import com.zhuinden.monarchy.Monarchy +import org.matrix.android.sdk.api.MatrixPatterns.getDomain import org.matrix.android.sdk.api.auth.data.HomeServerConnectionConfig import org.matrix.android.sdk.api.auth.wellknown.WellknownResult import org.matrix.android.sdk.api.session.homeserver.HomeServerCapabilities @@ -24,6 +25,7 @@ import org.matrix.android.sdk.internal.auth.version.Versions import org.matrix.android.sdk.internal.auth.version.isLoginAndRegistrationSupportedBySdk import org.matrix.android.sdk.internal.database.model.HomeServerCapabilitiesEntity import org.matrix.android.sdk.internal.database.query.getOrCreate +import org.matrix.android.sdk.internal.di.MoshiProvider import org.matrix.android.sdk.internal.di.SessionDatabase import org.matrix.android.sdk.internal.di.UserId import org.matrix.android.sdk.internal.network.GlobalErrorReceiver @@ -89,7 +91,10 @@ internal class DefaultGetHomeServerCapabilitiesTask @Inject constructor( }.getOrNull() val wellknownResult = runCatching { - getWellknownTask.execute(GetWellknownTask.Params(userId, homeServerConnectionConfig)) + getWellknownTask.execute(GetWellknownTask.Params( + domain = userId.getDomain(), + homeServerConnectionConfig = homeServerConnectionConfig + )) }.getOrNull() insertInDb(capabilities, mediaConfig, versions, wellknownResult) @@ -104,6 +109,10 @@ internal class DefaultGetHomeServerCapabilitiesTask @Inject constructor( if (getCapabilitiesResult != null) { homeServerCapabilitiesEntity.canChangePassword = getCapabilitiesResult.canChangePassword() + + homeServerCapabilitiesEntity.roomVersionsJson = getCapabilitiesResult.capabilities?.roomVersions?.let { + MoshiProvider.providesMoshi().adapter(RoomVersions::class.java).toJson(it) + } } if (getMediaConfigResult != null) { diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/homeserver/HomeServerCapabilitiesDataSource.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/homeserver/HomeServerCapabilitiesDataSource.kt new file mode 100644 index 0000000000..6c913fa41e --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/homeserver/HomeServerCapabilitiesDataSource.kt @@ -0,0 +1,38 @@ +/* + * Copyright (c) 2021 The Matrix.org Foundation C.I.C. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.matrix.android.sdk.internal.session.homeserver + +import com.zhuinden.monarchy.Monarchy +import io.realm.Realm +import org.matrix.android.sdk.api.session.homeserver.HomeServerCapabilities +import org.matrix.android.sdk.internal.database.mapper.HomeServerCapabilitiesMapper +import org.matrix.android.sdk.internal.database.model.HomeServerCapabilitiesEntity +import org.matrix.android.sdk.internal.database.query.get +import org.matrix.android.sdk.internal.di.SessionDatabase +import javax.inject.Inject + +internal class HomeServerCapabilitiesDataSource @Inject constructor( + @SessionDatabase private val monarchy: Monarchy +) { + fun getHomeServerCapabilities(): HomeServerCapabilities? { + return Realm.getInstance(monarchy.realmConfiguration).use { realm -> + HomeServerCapabilitiesEntity.get(realm)?.let { + HomeServerCapabilitiesMapper.map(it) + } + } + } +} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/identity/DefaultIdentityService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/identity/DefaultIdentityService.kt index 4f88d8eb95..48870b86b7 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/identity/DefaultIdentityService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/identity/DefaultIdentityService.kt @@ -191,7 +191,7 @@ internal class DefaultIdentityService @Inject constructor( } else { // Disconnect previous one if any, first, because the token will change. // In case of error when configuring the new identity server, this is not a big deal, - // we will ask for a new token on the previous Identity server + // we will ask for a new token on the previous identity server runCatching { identityDisconnectTask.execute(Unit) } .onFailure { Timber.w(it, "Unable to disconnect identity server") } @@ -241,7 +241,7 @@ internal class DefaultIdentityService @Inject constructor( override suspend fun getShareStatus(threePids: List): Map { // Note: we do not require user consent here, because it is used for emails and phone numbers that the user has already sent - // to the home server, and not emails and phone numbers from the contact book of the user + // to the homeserver, and not emails and phone numbers from the contact book of the user if (threePids.isEmpty()) { return emptyMap() diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/identity/IdentityAuthAPI.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/identity/IdentityAuthAPI.kt index 9d990d4d8f..f77eb296aa 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/identity/IdentityAuthAPI.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/identity/IdentityAuthAPI.kt @@ -42,7 +42,7 @@ internal interface IdentityAuthAPI { suspend fun ping() /** - * Ping v1 will be used to check outdated Identity server + * Ping v1 will be used to check outdated identity server */ @GET("_matrix/identity/api/v1") suspend fun pingV1() diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/identity/IdentityModule.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/identity/IdentityModule.kt index 7a39a333a5..4d664b76be 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/identity/IdentityModule.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/identity/IdentityModule.kt @@ -60,7 +60,6 @@ internal abstract class IdentityModule { @SessionScope fun providesIdentityRealmConfiguration(realmKeysUtils: RealmKeysUtils, @SessionFilesDirectory directory: File, - migration: RealmIdentityStoreMigration, @UserMd5 userMd5: String): RealmConfiguration { return RealmConfiguration.Builder() .directory(directory) @@ -69,7 +68,7 @@ internal abstract class IdentityModule { realmKeysUtils.configureEncryption(this, SessionModule.getKeyAlias(userMd5)) } .schemaVersion(RealmIdentityStoreMigration.IDENTITY_STORE_SCHEMA_VERSION) - .migration(migration) + .migration(RealmIdentityStoreMigration) .allowWritesOnUiThread(true) .modules(IdentityRealmModule()) .build() diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/identity/db/RealmIdentityStoreMigration.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/identity/db/RealmIdentityStoreMigration.kt index 6081dbab12..21c0f8eb9e 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/identity/db/RealmIdentityStoreMigration.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/identity/db/RealmIdentityStoreMigration.kt @@ -19,13 +19,10 @@ package org.matrix.android.sdk.internal.session.identity.db import io.realm.DynamicRealm import io.realm.RealmMigration import timber.log.Timber -import javax.inject.Inject -internal class RealmIdentityStoreMigration @Inject constructor() : RealmMigration { +internal object RealmIdentityStoreMigration : RealmMigration { - companion object { - const val IDENTITY_STORE_SCHEMA_VERSION = 1L - } + const val IDENTITY_STORE_SCHEMA_VERSION = 1L override fun migrate(realm: DynamicRealm, oldVersion: Long, newVersion: Long) { Timber.v("Migrating Realm Identity from $oldVersion to $newVersion") diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/integrationmanager/IntegrationManager.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/integrationmanager/IntegrationManager.kt index f79f8084a8..b654b8610d 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/integrationmanager/IntegrationManager.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/integrationmanager/IntegrationManager.kt @@ -33,7 +33,7 @@ import org.matrix.android.sdk.internal.extensions.observeNotNull import org.matrix.android.sdk.api.session.SessionLifecycleObserver import org.matrix.android.sdk.internal.session.SessionScope import org.matrix.android.sdk.api.session.accountdata.UserAccountDataTypes -import org.matrix.android.sdk.api.session.accountdata.AccountDataEvent +import org.matrix.android.sdk.api.session.accountdata.UserAccountDataEvent import org.matrix.android.sdk.internal.session.user.accountdata.UserAccountDataDataSource import org.matrix.android.sdk.internal.session.user.accountdata.UpdateUserAccountDataTask import org.matrix.android.sdk.internal.session.widgets.helper.WidgetFactory @@ -43,8 +43,8 @@ import javax.inject.Inject /** * The integration manager allows to - * - Get the Integration Manager that a user has explicitly set for its account (via account data) - * - Get the recommended/preferred Integration Manager list as defined by the HomeServer (via wellknown) + * - Get the integration manager that a user has explicitly set for its account (via account data) + * - Get the recommended/preferred integration manager list as defined by the homeserver (via wellknown) * - Check if the user has disabled the integration manager feature * - Allow / Disallow Integration manager (propagated to other riot clients) * @@ -240,7 +240,7 @@ internal class IntegrationManager @Inject constructor(matrixConfiguration: Matri ) } - private fun AccountDataEvent.asIntegrationManagerWidgetContent(): WidgetContent? { + private fun UserAccountDataEvent.asIntegrationManagerWidgetContent(): WidgetContent? { return extractWidgetSequence(widgetFactory) .filter { WidgetType.IntegrationManager == it.type diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/permalinks/ViaParameterFinder.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/permalinks/ViaParameterFinder.kt index 72fbfcced5..82565d8118 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/permalinks/ViaParameterFinder.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/permalinks/ViaParameterFinder.kt @@ -16,6 +16,7 @@ package org.matrix.android.sdk.internal.session.permalinks +import org.matrix.android.sdk.api.MatrixPatterns.getDomain import org.matrix.android.sdk.api.session.room.members.roomMemberQueryParams import org.matrix.android.sdk.api.session.room.model.Membership import org.matrix.android.sdk.internal.di.UserId @@ -47,9 +48,9 @@ internal class ViaParameterFinder @Inject constructor( } fun computeViaParams(userId: String, roomId: String, max: Int): List { - val userHomeserver = userId.substringAfter(":") + val userHomeserver = userId.getDomain() return getUserIdsOfJoinedMembers(roomId) - .map { it.substringAfter(":") } + .map { it.getDomain() } .groupBy { it } .mapValues { it.value.size } .toMutableMap() diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/profile/ProfileAPI.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/profile/ProfileAPI.kt index 485a4973ca..4b56db9f13 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/profile/ProfileAPI.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/profile/ProfileAPI.kt @@ -86,7 +86,7 @@ internal interface ProfileAPI { suspend fun addMsisdn(@Body body: AddMsisdnBody): AddMsisdnResponse /** - * Validate Msisdn code (same model than for Identity server API) + * Validate Msisdn code (same model than for identity server API) */ @POST suspend fun validateMsisdn(@Url url: String, diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/DefaultRoom.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/DefaultRoom.kt index 5a2eef7e8a..8afd690f64 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/DefaultRoom.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/DefaultRoom.kt @@ -17,10 +17,10 @@ package org.matrix.android.sdk.internal.session.room import androidx.lifecycle.LiveData -import org.matrix.android.sdk.api.session.accountdata.AccountDataService import org.matrix.android.sdk.api.session.crypto.CryptoService import org.matrix.android.sdk.api.session.events.model.EventType import org.matrix.android.sdk.api.session.room.Room +import org.matrix.android.sdk.api.session.room.accountdata.RoomAccountDataService import org.matrix.android.sdk.api.session.room.alias.AliasService import org.matrix.android.sdk.api.session.room.call.RoomCallService import org.matrix.android.sdk.api.session.room.members.MembershipService @@ -37,12 +37,12 @@ import org.matrix.android.sdk.api.session.room.tags.TagsService import org.matrix.android.sdk.api.session.room.timeline.TimelineService import org.matrix.android.sdk.api.session.room.typing.TypingService import org.matrix.android.sdk.api.session.room.uploads.UploadsService +import org.matrix.android.sdk.api.session.room.version.RoomVersionService import org.matrix.android.sdk.api.session.search.SearchResult import org.matrix.android.sdk.api.session.space.Space import org.matrix.android.sdk.api.util.Optional import org.matrix.android.sdk.internal.crypto.MXCRYPTO_ALGORITHM_MEGOLM import org.matrix.android.sdk.internal.session.permalinks.ViaParameterFinder -import org.matrix.android.sdk.internal.session.room.accountdata.RoomAccountDataService import org.matrix.android.sdk.internal.session.room.state.SendStateTask import org.matrix.android.sdk.internal.session.room.summary.RoomSummaryDataSource import org.matrix.android.sdk.internal.session.search.SearchTask @@ -68,9 +68,11 @@ internal class DefaultRoom(override val roomId: String, private val roomMembersService: MembershipService, private val roomPushRuleService: RoomPushRuleService, private val roomAccountDataService: RoomAccountDataService, + private val roomVersionService: RoomVersionService, private val sendStateTask: SendStateTask, private val viaParameterFinder: ViaParameterFinder, - private val searchTask: SearchTask) : + private val searchTask: SearchTask +) : Room, TimelineService by timelineService, SendService by sendService, @@ -86,7 +88,8 @@ internal class DefaultRoom(override val roomId: String, RelationService by relationService, MembershipService by roomMembersService, RoomPushRuleService by roomPushRuleService, - AccountDataService by roomAccountDataService { + RoomAccountDataService by roomAccountDataService, + RoomVersionService by roomVersionService { override fun getRoomSummaryLive(): LiveData> { return roomSummaryDataSource.getRoomSummaryLive(roomId) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/DefaultRoomDirectoryService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/DefaultRoomDirectoryService.kt index 218d846afb..7330c91c20 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/DefaultRoomDirectoryService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/DefaultRoomDirectoryService.kt @@ -16,10 +16,13 @@ package org.matrix.android.sdk.internal.session.room +import org.matrix.android.sdk.api.session.room.AliasAvailabilityResult import org.matrix.android.sdk.api.session.room.RoomDirectoryService +import org.matrix.android.sdk.api.session.room.alias.RoomAliasError import org.matrix.android.sdk.api.session.room.model.RoomDirectoryVisibility import org.matrix.android.sdk.api.session.room.model.roomdirectory.PublicRoomsParams import org.matrix.android.sdk.api.session.room.model.roomdirectory.PublicRoomsResponse +import org.matrix.android.sdk.internal.session.room.alias.RoomAliasAvailabilityChecker import org.matrix.android.sdk.internal.session.room.directory.GetPublicRoomTask import org.matrix.android.sdk.internal.session.room.directory.GetRoomDirectoryVisibilityTask import org.matrix.android.sdk.internal.session.room.directory.SetRoomDirectoryVisibilityTask @@ -28,7 +31,8 @@ import javax.inject.Inject internal class DefaultRoomDirectoryService @Inject constructor( private val getPublicRoomTask: GetPublicRoomTask, private val getRoomDirectoryVisibilityTask: GetRoomDirectoryVisibilityTask, - private val setRoomDirectoryVisibilityTask: SetRoomDirectoryVisibilityTask + private val setRoomDirectoryVisibilityTask: SetRoomDirectoryVisibilityTask, + private val roomAliasAvailabilityChecker: RoomAliasAvailabilityChecker ) : RoomDirectoryService { override suspend fun getPublicRooms(server: String?, @@ -43,4 +47,13 @@ internal class DefaultRoomDirectoryService @Inject constructor( override suspend fun setRoomDirectoryVisibility(roomId: String, roomDirectoryVisibility: RoomDirectoryVisibility) { setRoomDirectoryVisibilityTask.execute(SetRoomDirectoryVisibilityTask.Params(roomId, roomDirectoryVisibility)) } + + override suspend fun checkAliasAvailability(aliasLocalPart: String?): AliasAvailabilityResult { + return try { + roomAliasAvailabilityChecker.check(aliasLocalPart) + AliasAvailabilityResult.Available + } catch (failure: RoomAliasError) { + AliasAvailabilityResult.NotAvailable(failure) + } + } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/DefaultRoomService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/DefaultRoomService.kt index d9fe1288e2..632ea4c450 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/DefaultRoomService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/DefaultRoomService.kt @@ -134,6 +134,10 @@ internal class DefaultRoomService @Inject constructor( deleteRoomAliasTask.execute(DeleteRoomAliasTask.Params(roomAlias)) } + override fun getChangeMemberships(roomIdOrAlias: String): ChangeMembershipState { + return roomChangeMembershipStateDataSource.getState(roomIdOrAlias) + } + override fun getChangeMembershipsLive(): LiveData> { return roomChangeMembershipStateDataSource.getLiveStates() } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/RoomAPI.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/RoomAPI.kt index 4f12604039..18ece60629 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/RoomAPI.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/RoomAPI.kt @@ -369,4 +369,15 @@ internal interface RoomAPI { @Path("roomId") roomId: String, @Path("type") type: String, @Body content: JsonDict) + + /** + * Upgrades the given room to a particular room version. + * Errors: + * 400, The request was invalid. One way this can happen is if the room version requested is not supported by the homeserver + * (M_UNSUPPORTED_ROOM_VERSION) + * 403: The user is not permitted to upgrade the room.(M_FORBIDDEN) + */ + @POST(NetworkConstants.URI_API_PREFIX_PATH_R0 + "rooms/{roomId}/upgrade") + suspend fun upgradeRoom(@Path("roomId") roomId: String, + @Body body: RoomUpgradeBody): RoomUpgradeResponse } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/RoomFactory.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/RoomFactory.kt index 8efbf2360a..d44eb32529 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/RoomFactory.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/RoomFactory.kt @@ -19,8 +19,8 @@ package org.matrix.android.sdk.internal.session.room import org.matrix.android.sdk.api.session.crypto.CryptoService import org.matrix.android.sdk.api.session.room.Room import org.matrix.android.sdk.internal.session.SessionScope -import org.matrix.android.sdk.internal.session.room.accountdata.RoomAccountDataService import org.matrix.android.sdk.internal.session.permalinks.ViaParameterFinder +import org.matrix.android.sdk.internal.session.room.accountdata.DefaultRoomAccountDataService import org.matrix.android.sdk.internal.session.room.alias.DefaultAliasService import org.matrix.android.sdk.internal.session.room.call.DefaultRoomCallService import org.matrix.android.sdk.internal.session.room.draft.DefaultDraftService @@ -37,6 +37,7 @@ import org.matrix.android.sdk.internal.session.room.tags.DefaultTagsService import org.matrix.android.sdk.internal.session.room.timeline.DefaultTimelineService import org.matrix.android.sdk.internal.session.room.typing.DefaultTypingService import org.matrix.android.sdk.internal.session.room.uploads.DefaultUploadsService +import org.matrix.android.sdk.internal.session.room.version.DefaultRoomVersionService import org.matrix.android.sdk.internal.session.search.SearchTask import javax.inject.Inject @@ -61,7 +62,8 @@ internal class DefaultRoomFactory @Inject constructor(private val cryptoService: private val relationServiceFactory: DefaultRelationService.Factory, private val membershipServiceFactory: DefaultMembershipService.Factory, private val roomPushRuleServiceFactory: DefaultRoomPushRuleService.Factory, - private val roomAccountDataServiceFactory: RoomAccountDataService.Factory, + private val roomVersionServiceFactory: DefaultRoomVersionService.Factory, + private val roomAccountDataServiceFactory: DefaultRoomAccountDataService.Factory, private val sendStateTask: SendStateTask, private val viaParameterFinder: ViaParameterFinder, private val searchTask: SearchTask) : @@ -87,6 +89,7 @@ internal class DefaultRoomFactory @Inject constructor(private val cryptoService: roomMembersService = membershipServiceFactory.create(roomId), roomPushRuleService = roomPushRuleServiceFactory.create(roomId), roomAccountDataService = roomAccountDataServiceFactory.create(roomId), + roomVersionService = roomVersionServiceFactory.create(roomId), sendStateTask = sendStateTask, searchTask = searchTask, viaParameterFinder = viaParameterFinder diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/RoomModule.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/RoomModule.kt index d88c195056..c04c899e18 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/RoomModule.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/RoomModule.kt @@ -92,6 +92,8 @@ import org.matrix.android.sdk.internal.session.room.typing.DefaultSendTypingTask import org.matrix.android.sdk.internal.session.room.typing.SendTypingTask import org.matrix.android.sdk.internal.session.room.uploads.DefaultGetUploadsTask import org.matrix.android.sdk.internal.session.room.uploads.GetUploadsTask +import org.matrix.android.sdk.internal.session.room.version.DefaultRoomVersionUpgradeTask +import org.matrix.android.sdk.internal.session.room.version.RoomVersionUpgradeTask import org.matrix.android.sdk.internal.session.space.DefaultSpaceService import retrofit2.Retrofit @@ -243,4 +245,7 @@ internal abstract class RoomModule { @Binds abstract fun bindGetEventTask(task: DefaultGetEventTask): GetEventTask + + @Binds + abstract fun bindRoomVersionUpgradeTask(task: DefaultRoomVersionUpgradeTask): RoomVersionUpgradeTask } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/RoomUpgradeBody.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/RoomUpgradeBody.kt new file mode 100644 index 0000000000..4629f6e409 --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/RoomUpgradeBody.kt @@ -0,0 +1,26 @@ +/* + * Copyright 2021 The Matrix.org Foundation C.I.C. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.matrix.android.sdk.internal.session.room + +import com.squareup.moshi.Json +import com.squareup.moshi.JsonClass + +@JsonClass(generateAdapter = true) +internal data class RoomUpgradeBody( + @Json(name = "new_version") + val newVersion: String +) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/RoomUpgradeResponse.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/RoomUpgradeResponse.kt new file mode 100644 index 0000000000..1cca2c572b --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/RoomUpgradeResponse.kt @@ -0,0 +1,26 @@ +/* + * Copyright 2021 The Matrix.org Foundation C.I.C. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.matrix.android.sdk.internal.session.room + +import com.squareup.moshi.Json +import com.squareup.moshi.JsonClass + +@JsonClass(generateAdapter = true) +internal data class RoomUpgradeResponse( + @Json(name = "replacement_room") + val replacementRoomId: String +) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/accountdata/RoomAccountDataService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/accountdata/DefaultRoomAccountDataService.kt similarity index 72% rename from matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/accountdata/RoomAccountDataService.kt rename to matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/accountdata/DefaultRoomAccountDataService.kt index 9e9e9dc322..caeeb3bf53 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/accountdata/RoomAccountDataService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/accountdata/DefaultRoomAccountDataService.kt @@ -20,34 +20,34 @@ import androidx.lifecycle.LiveData import dagger.assisted.Assisted import dagger.assisted.AssistedFactory import dagger.assisted.AssistedInject -import org.matrix.android.sdk.api.session.accountdata.AccountDataEvent -import org.matrix.android.sdk.api.session.accountdata.AccountDataService import org.matrix.android.sdk.api.session.events.model.Content +import org.matrix.android.sdk.api.session.room.accountdata.RoomAccountDataEvent +import org.matrix.android.sdk.api.session.room.accountdata.RoomAccountDataService import org.matrix.android.sdk.api.util.Optional -internal class RoomAccountDataService @AssistedInject constructor(@Assisted private val roomId: String, - private val dataSource: RoomAccountDataDataSource, - private val updateRoomAccountDataTask: UpdateRoomAccountDataTask -) : AccountDataService { +internal class DefaultRoomAccountDataService @AssistedInject constructor(@Assisted private val roomId: String, + private val dataSource: RoomAccountDataDataSource, + private val updateRoomAccountDataTask: UpdateRoomAccountDataTask +) : RoomAccountDataService { @AssistedFactory interface Factory { - fun create(roomId: String): RoomAccountDataService + fun create(roomId: String): DefaultRoomAccountDataService } - override fun getAccountDataEvent(type: String): AccountDataEvent? { + override fun getAccountDataEvent(type: String): RoomAccountDataEvent? { return dataSource.getAccountDataEvent(roomId, type) } - override fun getLiveAccountDataEvent(type: String): LiveData> { + override fun getLiveAccountDataEvent(type: String): LiveData> { return dataSource.getLiveAccountDataEvent(roomId, type) } - override fun getAccountDataEvents(types: Set): List { + override fun getAccountDataEvents(types: Set): List { return dataSource.getAccountDataEvents(roomId, types) } - override fun getLiveAccountDataEvents(types: Set): LiveData> { + override fun getLiveAccountDataEvents(types: Set): LiveData> { return dataSource.getLiveAccountDataEvents(roomId, types) } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/accountdata/RoomAccountDataDataSource.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/accountdata/RoomAccountDataDataSource.kt index 0bcf9d7f38..0e4493846c 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/accountdata/RoomAccountDataDataSource.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/accountdata/RoomAccountDataDataSource.kt @@ -20,14 +20,16 @@ import androidx.lifecycle.LiveData import androidx.lifecycle.MediatorLiveData import androidx.lifecycle.Transformations import com.zhuinden.monarchy.Monarchy -import org.matrix.android.sdk.api.session.accountdata.AccountDataEvent +import io.realm.Realm +import io.realm.RealmQuery +import org.matrix.android.sdk.api.session.room.accountdata.RoomAccountDataEvent import org.matrix.android.sdk.api.util.Optional import org.matrix.android.sdk.api.util.toOptional import org.matrix.android.sdk.internal.database.RealmSessionProvider import org.matrix.android.sdk.internal.database.mapper.AccountDataMapper import org.matrix.android.sdk.internal.database.model.RoomAccountDataEntityFields import org.matrix.android.sdk.internal.database.model.RoomEntity -import org.matrix.android.sdk.internal.database.query.where +import org.matrix.android.sdk.internal.database.model.RoomEntityFields import org.matrix.android.sdk.internal.di.SessionDatabase import javax.inject.Inject @@ -35,43 +37,62 @@ internal class RoomAccountDataDataSource @Inject constructor(@SessionDatabase pr private val realmSessionProvider: RealmSessionProvider, private val accountDataMapper: AccountDataMapper) { - fun getAccountDataEvent(roomId: String, type: String): AccountDataEvent? { + fun getAccountDataEvent(roomId: String, type: String): RoomAccountDataEvent? { return getAccountDataEvents(roomId, setOf(type)).firstOrNull() } - fun getLiveAccountDataEvent(roomId: String, type: String): LiveData> { + fun getLiveAccountDataEvent(roomId: String, type: String): LiveData> { return Transformations.map(getLiveAccountDataEvents(roomId, setOf(type))) { it.firstOrNull()?.toOptional() } } - fun getAccountDataEvents(roomId: String, types: Set): List { + /** + * @param roomId the roomId to search for account data event. If null will check in every room. + * @param types the types to filter. If empty will return all account data event in given room (or every room if roomId is null) + * + */ + fun getAccountDataEvents(roomId: String?, types: Set): List { return realmSessionProvider.withRealm { realm -> - val roomEntity = RoomEntity.where(realm, roomId).findFirst() ?: return@withRealm emptyList() + val roomEntity = buildRoomQuery(realm, roomId, types).findFirst() ?: return@withRealm emptyList() roomEntity.accountDataEvents(types) } } - fun getLiveAccountDataEvents(roomId: String, types: Set): LiveData> { - val liveRoomEntity = monarchy.findAllManagedWithChanges { RoomEntity.where(it, roomId) } - val resultLiveData = MediatorLiveData>() - resultLiveData.addSource(liveRoomEntity) { - val roomEntity = it.realmResults.firstOrNull() - if (roomEntity == null) { - resultLiveData.postValue(emptyList()) - } else { - val mappedResult = roomEntity.accountDataEvents(types) - resultLiveData.postValue(mappedResult) - } + /** + * @param roomId the roomId to search for account data event. If null will check in every room. + * @param types the types to filter. If empty will return all account data event in the given room (or every room if roomId is null). + * + */ + fun getLiveAccountDataEvents(roomId: String?, types: Set): LiveData> { + val liveRoomEntity = monarchy.findAllManagedWithChanges { + buildRoomQuery(it, roomId, types) + } + val resultLiveData = MediatorLiveData>() + resultLiveData.addSource(liveRoomEntity) { changeSet -> + val mappedResult = changeSet.realmResults.flatMap { it.accountDataEvents(types) } + resultLiveData.postValue(mappedResult) } return resultLiveData } - private fun RoomEntity.accountDataEvents(types: Set): List { + private fun buildRoomQuery(realm: Realm, roomId: String?, types: Set): RealmQuery { + val query = realm.where(RoomEntity::class.java) + if (roomId != null) { + query.equalTo(RoomEntityFields.ROOM_ID, roomId) + } + query.isNotEmpty(RoomEntityFields.ACCOUNT_DATA.`$`) + if (types.isNotEmpty()) { + query.`in`(RoomEntityFields.ACCOUNT_DATA.TYPE, types.toTypedArray()) + } + return query + } + + private fun RoomEntity.accountDataEvents(types: Set): List { val query = accountData.where() if (types.isNotEmpty()) { query.`in`(RoomAccountDataEntityFields.TYPE, types.toTypedArray()) } - return query.findAll().map { accountDataMapper.map(it) } + return query.findAll().map { accountDataMapper.map(roomId, it) } } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/alias/RoomAliasAvailabilityChecker.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/alias/RoomAliasAvailabilityChecker.kt index b39cbaa582..7c137a8102 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/alias/RoomAliasAvailabilityChecker.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/alias/RoomAliasAvailabilityChecker.kt @@ -16,6 +16,7 @@ package org.matrix.android.sdk.internal.session.room.alias +import org.matrix.android.sdk.api.MatrixPatterns.getDomain import org.matrix.android.sdk.api.failure.Failure import org.matrix.android.sdk.api.session.room.alias.RoomAliasError import org.matrix.android.sdk.internal.di.UserId @@ -51,19 +52,19 @@ internal class RoomAliasAvailabilityChecker @Inject constructor( } catch (throwable: Throwable) { if (throwable is Failure.ServerError && throwable.httpCode == 404) { // This is a 404, so the alias is available: nominal case - null + return } else { // Other error, propagate it throw throwable } } - ?.let { + .let { // Alias already exists: error case throw RoomAliasError.AliasNotAvailable } } companion object { - internal fun String.toFullLocalAlias(userId: String) = "#" + this + ":" + userId.substringAfter(":") + internal fun String.toFullLocalAlias(userId: String) = "#" + this + ":" + userId.getDomain() } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/create/CreateRoomBodyBuilder.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/create/CreateRoomBodyBuilder.kt index 018b865388..86d2b70da1 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/create/CreateRoomBodyBuilder.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/create/CreateRoomBodyBuilder.kt @@ -59,7 +59,7 @@ internal class CreateRoomBodyBuilder @Inject constructor( val invite3pids = params.invite3pids .takeIf { it.isNotEmpty() } ?.let { invites -> - // This can throw Exception if Identity server is not configured + // This can throw an exception if identity server is not configured ensureIdentityTokenTask.execute(Unit) val identityServerUrlWithoutProtocol = identityStore.getIdentityServerUrlWithoutProtocol() diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/membership/RoomChangeMembershipStateDataSource.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/membership/RoomChangeMembershipStateDataSource.kt index b9c547d4fb..35d8cb08af 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/membership/RoomChangeMembershipStateDataSource.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/membership/RoomChangeMembershipStateDataSource.kt @@ -21,6 +21,7 @@ import androidx.lifecycle.MutableLiveData import org.matrix.android.sdk.api.session.room.members.ChangeMembershipState import org.matrix.android.sdk.api.session.room.model.Membership import org.matrix.android.sdk.internal.session.SessionScope +import java.util.concurrent.ConcurrentHashMap import javax.inject.Inject /** @@ -30,7 +31,7 @@ import javax.inject.Inject internal class RoomChangeMembershipStateDataSource @Inject constructor() { private val mutableLiveStates = MutableLiveData>(emptyMap()) - private val states = HashMap() + private val states = ConcurrentHashMap() /** * This will update local states to be synced with the server. diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/membership/joining/JoinRoomTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/membership/joining/JoinRoomTask.kt index 33776e4f6e..562b25683b 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/membership/joining/JoinRoomTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/membership/joining/JoinRoomTask.kt @@ -54,6 +54,10 @@ internal class DefaultJoinRoomTask @Inject constructor( ) : JoinRoomTask { override suspend fun execute(params: JoinRoomTask.Params) { + val currentState = roomChangeMembershipStateDataSource.getState(params.roomIdOrAlias) + if (currentState.isInProgress() || currentState == ChangeMembershipState.Joined) { + return + } roomChangeMembershipStateDataSource.updateState(params.roomIdOrAlias, ChangeMembershipState.Joining) val joinRoomResponse = try { executeRequest(globalErrorReceiver) { diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/DefaultSendService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/DefaultSendService.kt index 449189e6b5..092fec4d72 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/DefaultSendService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/DefaultSendService.kt @@ -142,7 +142,7 @@ internal class DefaultSendService @AssistedInject constructor( // The image has not yet been sent val attachmentData = ContentAttachmentData( size = messageContent.info!!.size, - mimeType = messageContent.info.mimeType!!, + mimeType = messageContent.mimeType, width = messageContent.info.width.toLong(), height = messageContent.info.height.toLong(), name = messageContent.body, @@ -169,7 +169,7 @@ internal class DefaultSendService @AssistedInject constructor( is MessageFileContent -> { val attachmentData = ContentAttachmentData( size = messageContent.info!!.size, - mimeType = messageContent.info.mimeType!!, + mimeType = messageContent.mimeType, name = messageContent.getFileName(), queryUri = Uri.parse(messageContent.url), type = ContentAttachmentData.Type.FILE @@ -181,7 +181,7 @@ internal class DefaultSendService @AssistedInject constructor( val attachmentData = ContentAttachmentData( size = messageContent.audioInfo?.size ?: 0, duration = messageContent.audioInfo?.duration?.toLong() ?: 0L, - mimeType = messageContent.audioInfo?.mimeType, + mimeType = messageContent.mimeType, name = messageContent.body, queryUri = Uri.parse(messageContent.url), type = ContentAttachmentData.Type.AUDIO diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/queue/EventSenderProcessorCoroutine.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/queue/EventSenderProcessorCoroutine.kt index 80bfd02b0e..3be01762e7 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/queue/EventSenderProcessorCoroutine.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/queue/EventSenderProcessorCoroutine.kt @@ -46,7 +46,7 @@ private const val MAX_RETRY_COUNT = 3 /** * This class is responsible for sending events in order in each room. It uses the QueuedTask.queueIdentifier to execute tasks sequentially. - * Each send is retried 3 times, if there is no network (e.g if cannot ping home server) it will wait and + * Each send is retried 3 times, if there is no network (e.g if cannot ping homeserver) it will wait and * periodically test reachability before resume (does not count as a retry) * * If the app is killed before all event were sent, on next wakeup the scheduled events will be re posted diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/queue/EventSenderProcessorThread.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/queue/EventSenderProcessorThread.kt index 9db7cc9039..f32890f3fb 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/queue/EventSenderProcessorThread.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/queue/EventSenderProcessorThread.kt @@ -42,7 +42,7 @@ import kotlin.concurrent.schedule /** * A simple ever running thread unique for that session responsible of sending events in order. - * Each send is retried 3 times, if there is no network (e.g if cannot ping home server) it will wait and + * Each send is retried 3 times, if there is no network (e.g if cannot ping homeserver) it will wait and * periodically test reachability before resume (does not count as a retry) * * If the app is killed before all event were sent, on next wakeup the scheduled events will be re posted diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/queue/HomeServerAvailabilityChecker.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/queue/HomeServerAvailabilityChecker.kt index 2d53699917..1d7ce587f9 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/queue/HomeServerAvailabilityChecker.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/queue/HomeServerAvailabilityChecker.kt @@ -26,8 +26,8 @@ import java.net.Socket internal class HomeServerAvailabilityChecker(val sessionParams: SessionParams) { fun check(): Boolean { - val host = sessionParams.homeServerConnectionConfig.homeServerUri.host ?: return false - val port = sessionParams.homeServerConnectionConfig.homeServerUri.port.takeIf { it != -1 } ?: 80 + val host = sessionParams.homeServerConnectionConfig.homeServerUriBase.host ?: return false + val port = sessionParams.homeServerConnectionConfig.homeServerUriBase.port.takeIf { it != -1 } ?: 80 val timeout = 30_000 try { Socket().use { socket -> diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/summary/RoomSummaryUpdater.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/summary/RoomSummaryUpdater.kt index 7cbcfee713..842c9d3aba 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/summary/RoomSummaryUpdater.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/summary/RoomSummaryUpdater.kt @@ -199,7 +199,6 @@ internal class RoomSummaryUpdater @Inject constructor( measureTimeMillis { val lookupMap = realm.where(RoomSummaryEntity::class.java) .process(RoomSummaryEntityFields.MEMBERSHIP_STR, Membership.activeMemberships()) - .equalTo(RoomSummaryEntityFields.IS_DIRECT, false) // we order by roomID to be consistent when breaking parent/child cycles .sort(RoomSummaryEntityFields.ROOM_ID) .findAll().map { diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/DefaultTimeline.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/DefaultTimeline.kt index e230599f8f..8cc5d943b7 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/DefaultTimeline.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/DefaultTimeline.kt @@ -24,7 +24,6 @@ import io.realm.RealmQuery import io.realm.RealmResults import io.realm.Sort import org.matrix.android.sdk.api.MatrixCallback -import org.matrix.android.sdk.api.NoOpMatrixCallback import org.matrix.android.sdk.api.extensions.orFalse import org.matrix.android.sdk.api.extensions.tryOrNull import org.matrix.android.sdk.api.session.events.model.EventType @@ -168,9 +167,7 @@ internal class DefaultTimeline( timelineEvents.addChangeListener(eventsChangeListener) handleInitialLoad() loadRoomMembersTask - .configureWith(LoadRoomMembersTask.Params(roomId)) { - this.callback = NoOpMatrixCallback() - } + .configureWith(LoadRoomMembersTask.Params(roomId)) .executeBy(taskExecutor) // Ensure ReadReceipt from init sync are loaded diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/typing/DefaultTypingService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/typing/DefaultTypingService.kt index 39b7967bc1..b76829e893 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/typing/DefaultTypingService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/typing/DefaultTypingService.kt @@ -18,13 +18,13 @@ package org.matrix.android.sdk.internal.session.room.typing import android.os.SystemClock import dagger.assisted.Assisted -import dagger.assisted.AssistedInject import dagger.assisted.AssistedFactory -import org.matrix.android.sdk.api.MatrixCallback +import dagger.assisted.AssistedInject +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Job +import kotlinx.coroutines.delay +import kotlinx.coroutines.launch import org.matrix.android.sdk.api.session.room.typing.TypingService -import org.matrix.android.sdk.api.util.Cancelable -import org.matrix.android.sdk.internal.task.TaskExecutor -import org.matrix.android.sdk.internal.task.configureWith import timber.log.Timber /** @@ -35,7 +35,6 @@ import timber.log.Timber */ internal class DefaultTypingService @AssistedInject constructor( @Assisted private val roomId: String, - private val taskExecutor: TaskExecutor, private val sendTypingTask: SendTypingTask ) : TypingService { @@ -44,8 +43,8 @@ internal class DefaultTypingService @AssistedInject constructor( fun create(roomId: String): DefaultTypingService } - private var currentTask: Cancelable? = null - private var currentAutoStopTask: Cancelable? = null + private val coroutineScope = CoroutineScope(Job()) + private var currentTask: Job? = null // What the homeserver knows private var userIsTyping = false @@ -53,26 +52,24 @@ internal class DefaultTypingService @AssistedInject constructor( // Last time the user is typing event has been sent private var lastRequestTimestamp: Long = 0 + /** + * Notify to the server that the user is typing and schedule the auto typing off + */ override fun userIsTyping() { - scheduleAutoStop() - val now = SystemClock.elapsedRealtime() - - if (userIsTyping && now < lastRequestTimestamp + MIN_DELAY_BETWEEN_TWO_USER_IS_TYPING_REQUESTS_MILLIS) { - Timber.d("Typing: Skip start request") - return - } - - Timber.d("Typing: Send start request") - userIsTyping = true - lastRequestTimestamp = now - currentTask?.cancel() - - val params = SendTypingTask.Params(roomId, true) - currentTask = sendTypingTask - .configureWith(params) - .executeBy(taskExecutor) + currentTask = coroutineScope.launch { + if (userIsTyping && now < lastRequestTimestamp + MIN_DELAY_BETWEEN_TWO_USER_IS_TYPING_REQUESTS_MILLIS) { + Timber.d("Typing: Skip start request") + } else { + Timber.d("Typing: Send start request") + lastRequestTimestamp = now + sendRequest(true) + } + delay(MIN_DELAY_TO_SEND_STOP_TYPING_REQUEST_WHEN_NO_USER_ACTIVITY_MILLIS) + Timber.d("Typing: auto stop") + sendRequest(false) + } } override fun userStopsTyping() { @@ -82,35 +79,22 @@ internal class DefaultTypingService @AssistedInject constructor( } Timber.d("Typing: Send stop request") - userIsTyping = false lastRequestTimestamp = 0 - currentAutoStopTask?.cancel() currentTask?.cancel() - - val params = SendTypingTask.Params(roomId, false) - currentTask = sendTypingTask - .configureWith(params) - .executeBy(taskExecutor) + currentTask = coroutineScope.launch { + sendRequest(false) + } } - private fun scheduleAutoStop() { - Timber.d("Typing: Schedule auto stop") - currentAutoStopTask?.cancel() - - val params = SendTypingTask.Params( - roomId, - false, - delay = MIN_DELAY_TO_SEND_STOP_TYPING_REQUEST_WHEN_NO_USER_ACTIVITY_MILLIS) - currentAutoStopTask = sendTypingTask - .configureWith(params) { - callback = object : MatrixCallback { - override fun onSuccess(data: Unit) { - userIsTyping = false - } - } - } - .executeBy(taskExecutor) + private suspend fun sendRequest(isTyping: Boolean) { + try { + sendTypingTask.execute(SendTypingTask.Params(roomId, isTyping)) + userIsTyping = isTyping + } catch (failure: Throwable) { + // Ignore network error, etc... + Timber.w(failure, "Unable to send typing request") + } } companion object { diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/typing/SendTypingTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/typing/SendTypingTask.kt index 0b0df74311..0bdceb9ade 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/typing/SendTypingTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/typing/SendTypingTask.kt @@ -17,11 +17,10 @@ package org.matrix.android.sdk.internal.session.room.typing import org.matrix.android.sdk.internal.di.UserId +import org.matrix.android.sdk.internal.network.GlobalErrorReceiver import org.matrix.android.sdk.internal.network.executeRequest import org.matrix.android.sdk.internal.session.room.RoomAPI import org.matrix.android.sdk.internal.task.Task -import kotlinx.coroutines.delay -import org.matrix.android.sdk.internal.network.GlobalErrorReceiver import javax.inject.Inject internal interface SendTypingTask : Task { @@ -29,9 +28,7 @@ internal interface SendTypingTask : Task { data class Params( val roomId: String, val isTyping: Boolean, - val typingTimeoutMillis: Int? = 30_000, - // Optional delay before sending the request to the homeserver - val delay: Long? = null + val typingTimeoutMillis: Int? = 30_000 ) } @@ -42,8 +39,6 @@ internal class DefaultSendTypingTask @Inject constructor( ) : SendTypingTask { override suspend fun execute(params: SendTypingTask.Params) { - delay(params.delay ?: -1) - executeRequest(globalErrorReceiver) { roomAPI.sendTypingState( params.roomId, diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/version/DefaultRoomVersionService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/version/DefaultRoomVersionService.kt new file mode 100644 index 0000000000..dc12c3209b --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/version/DefaultRoomVersionService.kt @@ -0,0 +1,84 @@ +/* + * Copyright 2021 The Matrix.org Foundation C.I.C. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.matrix.android.sdk.internal.session.room.version + +import dagger.assisted.Assisted +import dagger.assisted.AssistedFactory +import dagger.assisted.AssistedInject +import org.matrix.android.sdk.api.query.QueryStringValue +import org.matrix.android.sdk.api.session.events.model.EventType +import org.matrix.android.sdk.api.session.events.model.toModel +import org.matrix.android.sdk.api.session.homeserver.RoomVersionStatus +import org.matrix.android.sdk.api.session.room.model.PowerLevelsContent +import org.matrix.android.sdk.api.session.room.model.create.RoomCreateContent +import org.matrix.android.sdk.api.session.room.powerlevels.PowerLevelsHelper +import org.matrix.android.sdk.api.session.room.version.RoomVersionService +import org.matrix.android.sdk.internal.session.homeserver.HomeServerCapabilitiesDataSource +import org.matrix.android.sdk.internal.session.room.state.StateEventDataSource + +internal class DefaultRoomVersionService @AssistedInject constructor( + @Assisted private val roomId: String, + private val homeServerCapabilitiesDataSource: HomeServerCapabilitiesDataSource, + private val stateEventDataSource: StateEventDataSource, + private val roomVersionUpgradeTask: RoomVersionUpgradeTask +) : RoomVersionService { + + @AssistedFactory + interface Factory { + fun create(roomId: String): DefaultRoomVersionService + } + + override fun getRoomVersion(): String { + return stateEventDataSource.getStateEvent(roomId, EventType.STATE_ROOM_CREATE, QueryStringValue.IsEmpty) + ?.content + ?.toModel() + ?.roomVersion + // as per spec -> Defaults to "1" if the key does not exist. + ?: DEFAULT_ROOM_VERSION + } + + override suspend fun upgradeToVersion(version: String): String { + return roomVersionUpgradeTask.execute( + RoomVersionUpgradeTask.Params( + roomId = roomId, + newVersion = version + ) + ) + } + + override fun getRecommendedVersion(): String { + return homeServerCapabilitiesDataSource.getHomeServerCapabilities()?.roomVersions?.defaultRoomVersion ?: DEFAULT_ROOM_VERSION + } + + override fun isUsingUnstableRoomVersion(): Boolean { + val versionCaps = homeServerCapabilitiesDataSource.getHomeServerCapabilities()?.roomVersions + val currentVersion = getRoomVersion() + return versionCaps?.supportedVersion?.firstOrNull { it.version == currentVersion }?.status == RoomVersionStatus.UNSTABLE + } + + override fun userMayUpgradeRoom(userId: String): Boolean { + val powerLevelsHelper = stateEventDataSource.getStateEvent(roomId, EventType.STATE_ROOM_POWER_LEVELS, QueryStringValue.NoCondition) + ?.content?.toModel() + ?.let { PowerLevelsHelper(it) } + + return powerLevelsHelper?.isUserAllowedToSend(userId, true, EventType.STATE_ROOM_TOMBSTONE) ?: false + } + + companion object { + const val DEFAULT_ROOM_VERSION = "1" + } +} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/version/RoomVersionUpgradeTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/version/RoomVersionUpgradeTask.kt new file mode 100644 index 0000000000..457bb3e948 --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/version/RoomVersionUpgradeTask.kt @@ -0,0 +1,66 @@ +/* + * Copyright 2021 The Matrix.org Foundation C.I.C. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.matrix.android.sdk.internal.session.room.version + +import io.realm.RealmConfiguration +import org.matrix.android.sdk.api.extensions.tryOrNull +import org.matrix.android.sdk.api.session.room.model.Membership +import org.matrix.android.sdk.internal.database.awaitNotEmptyResult +import org.matrix.android.sdk.internal.database.model.RoomSummaryEntity +import org.matrix.android.sdk.internal.database.model.RoomSummaryEntityFields +import org.matrix.android.sdk.internal.di.SessionDatabase +import org.matrix.android.sdk.internal.network.GlobalErrorReceiver +import org.matrix.android.sdk.internal.network.executeRequest +import org.matrix.android.sdk.internal.session.room.RoomAPI +import org.matrix.android.sdk.internal.session.room.RoomUpgradeBody +import org.matrix.android.sdk.internal.task.Task +import java.util.concurrent.TimeUnit +import javax.inject.Inject + +internal interface RoomVersionUpgradeTask : Task { + data class Params( + val roomId: String, + val newVersion: String + ) +} + +internal class DefaultRoomVersionUpgradeTask @Inject constructor( + private val roomAPI: RoomAPI, + private val globalErrorReceiver: GlobalErrorReceiver, + @SessionDatabase + private val realmConfiguration: RealmConfiguration +) : RoomVersionUpgradeTask { + + override suspend fun execute(params: RoomVersionUpgradeTask.Params): String { + val replacementRoomId = executeRequest(globalErrorReceiver) { + roomAPI.upgradeRoom( + roomId = params.roomId, + body = RoomUpgradeBody(params.newVersion) + ) + }.replacementRoomId + + // Wait for room to come back from the sync (but it can maybe be in the DB if the sync response is received before) + tryOrNull { + awaitNotEmptyResult(realmConfiguration, TimeUnit.MINUTES.toMillis(1L)) { realm -> + realm.where(RoomSummaryEntity::class.java) + .equalTo(RoomSummaryEntityFields.ROOM_ID, replacementRoomId) + .equalTo(RoomSummaryEntityFields.MEMBERSHIP_STR, Membership.JOIN.name) + } + } + return replacementRoomId + } +} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/space/DefaultSpace.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/space/DefaultSpace.kt index 70c52bf4ae..233eef45f8 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/space/DefaultSpace.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/space/DefaultSpace.kt @@ -86,6 +86,12 @@ internal class DefaultSpace( ) } + override fun getChildInfo(roomId: String): SpaceChildContent? { + return room.getStateEvents(setOf(EventType.STATE_SPACE_CHILD), QueryStringValue.Equals(roomId)) + .firstOrNull() + ?.content.toModel() + } + override suspend fun setChildrenOrder(roomId: String, order: String?) { val existing = room.getStateEvents(setOf(EventType.STATE_SPACE_CHILD), QueryStringValue.Equals(roomId)) .firstOrNull() diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/space/DefaultSpaceService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/space/DefaultSpaceService.kt index 9c6153b349..0c5c0416f9 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/space/DefaultSpaceService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/space/DefaultSpaceService.kt @@ -66,12 +66,13 @@ internal class DefaultSpaceService @Inject constructor( return createRoomTask.executeRetry(params, 3) } - override suspend fun createSpace(name: String, topic: String?, avatarUri: Uri?, isPublic: Boolean): String { + override suspend fun createSpace(name: String, topic: String?, avatarUri: Uri?, isPublic: Boolean, roomAliasLocalPart: String?): String { return createSpace(CreateSpaceParams().apply { this.name = name this.topic = topic this.avatarUri = avatarUri if (isPublic) { + this.roomAliasName = roomAliasLocalPart this.powerLevelContentOverride = (powerLevelContentOverride ?: PowerLevelsContent()).copy( invite = 0 ) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/SyncTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/SyncTask.kt index 83a2ffc446..c80fbe60c1 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/SyncTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/SyncTask.kt @@ -94,7 +94,7 @@ internal class DefaultSyncTask @Inject constructor( userStore.createOrUpdate(userId) initialSyncProgressService.startRoot(InitSyncStep.ImportingAccount, 100) } - // Maybe refresh the home server capabilities data we know + // Maybe refresh the homeserver capabilities data we know getHomeServerCapabilitiesTask.execute(GetHomeServerCapabilitiesTask.Params(forceRefresh = false)) val readTimeOut = (params.timeout + TIMEOUT_MARGIN).coerceAtLeast(TimeOutInterceptor.DEFAULT_LONG_TIMEOUT) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/UserAccountDataSyncHandler.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/UserAccountDataSyncHandler.kt index 3aebd90ed2..b8d987d500 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/UserAccountDataSyncHandler.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/UserAccountDataSyncHandler.kt @@ -23,7 +23,7 @@ import io.realm.kotlin.where import org.matrix.android.sdk.api.pushrules.RuleScope import org.matrix.android.sdk.api.pushrules.RuleSetKey import org.matrix.android.sdk.api.pushrules.rest.GetPushRulesResponse -import org.matrix.android.sdk.api.session.accountdata.AccountDataEvent +import org.matrix.android.sdk.api.session.accountdata.UserAccountDataEvent import org.matrix.android.sdk.api.session.accountdata.UserAccountDataTypes import org.matrix.android.sdk.api.session.events.model.Content import org.matrix.android.sdk.api.session.events.model.toModel @@ -113,7 +113,7 @@ internal class UserAccountDataSyncHandler @Inject constructor( } } - private fun handlePushRules(realm: Realm, event: AccountDataEvent) { + private fun handlePushRules(realm: Realm, event: UserAccountDataEvent) { val pushRules = event.content.toModel() ?: return realm.where(PushRulesEntity::class.java) .findAll() @@ -155,7 +155,7 @@ internal class UserAccountDataSyncHandler @Inject constructor( realm.insertOrUpdate(underrides) } - private fun handleDirectChatRooms(realm: Realm, event: AccountDataEvent) { + private fun handleDirectChatRooms(realm: Realm, event: UserAccountDataEvent) { val content = event.content.toModel() ?: return content.forEach { (userId, roomIds) -> roomIds.forEach { roomId -> @@ -181,7 +181,7 @@ internal class UserAccountDataSyncHandler @Inject constructor( } } - private fun handleIgnoredUsers(realm: Realm, event: AccountDataEvent) { + private fun handleIgnoredUsers(realm: Realm, event: UserAccountDataEvent) { val userIds = event.content.toModel()?.ignoredUsers?.keys ?: return realm.where(IgnoredUserEntity::class.java) .findAll() @@ -191,7 +191,7 @@ internal class UserAccountDataSyncHandler @Inject constructor( // TODO If not initial sync, we should execute a init sync } - private fun handleBreadcrumbs(realm: Realm, event: AccountDataEvent) { + private fun handleBreadcrumbs(realm: Realm, event: UserAccountDataEvent) { val recentRoomIds = event.content.toModel()?.recentRoomIds ?: return val entity = BreadcrumbsEntity.getOrCreate(realm) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/model/accountdata/UserAccountDataSync.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/model/accountdata/UserAccountDataSync.kt index ddb71cd19f..05b50ab2c5 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/model/accountdata/UserAccountDataSync.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/model/accountdata/UserAccountDataSync.kt @@ -18,9 +18,9 @@ package org.matrix.android.sdk.internal.session.sync.model.accountdata import com.squareup.moshi.Json import com.squareup.moshi.JsonClass -import org.matrix.android.sdk.api.session.accountdata.AccountDataEvent +import org.matrix.android.sdk.api.session.accountdata.UserAccountDataEvent @JsonClass(generateAdapter = true) internal data class UserAccountDataSync( - @Json(name = "events") val list: List = emptyList() + @Json(name = "events") val list: List = emptyList() ) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/parsing/RoomSyncAccountDataHandler.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/parsing/RoomSyncAccountDataHandler.kt index c8aab586a0..8bf9ad5b90 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/parsing/RoomSyncAccountDataHandler.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/parsing/RoomSyncAccountDataHandler.kt @@ -56,7 +56,6 @@ internal class RoomSyncAccountDataHandler @Inject constructor(private val roomTa private fun handleGeneric(roomEntity: RoomEntity, content: JsonDict?, eventType: String) { val existing = roomEntity.accountData.where().equalTo(RoomAccountDataEntityFields.TYPE, eventType).findFirst() if (existing != null) { - // Update current value existing.contentStr = ContentMapper.map(content) } else { val roomAccountData = RoomAccountDataEntity( diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/user/accountdata/UserAccountDataService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/user/accountdata/DefaultSessionAccountDataService.kt similarity index 55% rename from matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/user/accountdata/UserAccountDataService.kt rename to matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/user/accountdata/DefaultSessionAccountDataService.kt index b15d1d0f8b..ff1750ce8e 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/user/accountdata/UserAccountDataService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/user/accountdata/DefaultSessionAccountDataService.kt @@ -18,42 +18,53 @@ package org.matrix.android.sdk.internal.session.user.accountdata import androidx.lifecycle.LiveData import com.zhuinden.monarchy.Monarchy -import org.matrix.android.sdk.api.session.accountdata.AccountDataService +import org.matrix.android.sdk.api.session.accountdata.SessionAccountDataService import org.matrix.android.sdk.api.session.events.model.Content import org.matrix.android.sdk.api.util.Optional import org.matrix.android.sdk.internal.di.SessionDatabase import org.matrix.android.sdk.internal.session.sync.UserAccountDataSyncHandler -import org.matrix.android.sdk.api.session.accountdata.AccountDataEvent +import org.matrix.android.sdk.api.session.accountdata.UserAccountDataEvent +import org.matrix.android.sdk.api.session.room.accountdata.RoomAccountDataEvent +import org.matrix.android.sdk.internal.session.room.accountdata.RoomAccountDataDataSource import org.matrix.android.sdk.internal.task.TaskExecutor import org.matrix.android.sdk.internal.task.configureWith import org.matrix.android.sdk.internal.util.awaitCallback import javax.inject.Inject -internal class UserAccountDataService @Inject constructor( +internal class DefaultSessionAccountDataService @Inject constructor( @SessionDatabase private val monarchy: Monarchy, private val updateUserAccountDataTask: UpdateUserAccountDataTask, private val userAccountDataSyncHandler: UserAccountDataSyncHandler, - private val accountDataDataSource: UserAccountDataDataSource, + private val userAccountDataDataSource: UserAccountDataDataSource, + private val roomAccountDataDataSource: RoomAccountDataDataSource, private val taskExecutor: TaskExecutor -) : AccountDataService { +) : SessionAccountDataService { - override fun getAccountDataEvent(type: String): AccountDataEvent? { - return accountDataDataSource.getAccountDataEvent(type) + override fun getUserAccountDataEvent(type: String): UserAccountDataEvent? { + return userAccountDataDataSource.getAccountDataEvent(type) } - override fun getLiveAccountDataEvent(type: String): LiveData> { - return accountDataDataSource.getLiveAccountDataEvent(type) + override fun getLiveUserAccountDataEvent(type: String): LiveData> { + return userAccountDataDataSource.getLiveAccountDataEvent(type) } - override fun getAccountDataEvents(types: Set): List { - return accountDataDataSource.getAccountDataEvents(types) + override fun getUserAccountDataEvents(types: Set): List { + return userAccountDataDataSource.getAccountDataEvents(types) } - override fun getLiveAccountDataEvents(types: Set): LiveData> { - return accountDataDataSource.getLiveAccountDataEvents(types) + override fun getLiveUserAccountDataEvents(types: Set): LiveData> { + return userAccountDataDataSource.getLiveAccountDataEvents(types) } - override suspend fun updateAccountData(type: String, content: Content) { + override fun getRoomAccountDataEvents(types: Set): List { + return roomAccountDataDataSource.getAccountDataEvents(null, types) + } + + override fun getLiveRoomAccountDataEvents(types: Set): LiveData> { + return roomAccountDataDataSource.getLiveAccountDataEvents(null, types) + } + + override suspend fun updateUserAccountData(type: String, content: Content) { val params = UpdateUserAccountDataTask.AnyParams(type = type, any = content) awaitCallback { callback -> updateUserAccountDataTask.configureWith(params) { diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/user/accountdata/DirectChatsHelper.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/user/accountdata/DirectChatsHelper.kt index e297f59b96..a9e5089774 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/user/accountdata/DirectChatsHelper.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/user/accountdata/DirectChatsHelper.kt @@ -31,9 +31,11 @@ internal class DirectChatsHelper @Inject constructor(@SessionDatabase */ fun getLocalUserAccount(filterRoomId: String? = null): MutableMap> { return Realm.getInstance(realmConfiguration).use { realm -> + // Makes sure we have the latest realm updates, this is important as we sent this information to the server. + realm.refresh() RoomSummaryEntity.getDirectRooms(realm) .asSequence() - .filter { it.roomId != filterRoomId && it.directUserId != null } + .filter { it.roomId != filterRoomId && it.directUserId != null && it.membership.isActive() } .groupByTo(mutableMapOf(), { it.directUserId!! }, { it.roomId }) } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/user/accountdata/UserAccountDataDataSource.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/user/accountdata/UserAccountDataDataSource.kt index f64b1bdd2e..b36bdc80f8 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/user/accountdata/UserAccountDataDataSource.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/user/accountdata/UserAccountDataDataSource.kt @@ -21,7 +21,7 @@ import androidx.lifecycle.Transformations import com.zhuinden.monarchy.Monarchy import io.realm.Realm import io.realm.RealmQuery -import org.matrix.android.sdk.api.session.accountdata.AccountDataEvent +import org.matrix.android.sdk.api.session.accountdata.UserAccountDataEvent import org.matrix.android.sdk.api.util.Optional import org.matrix.android.sdk.api.util.toOptional import org.matrix.android.sdk.internal.database.RealmSessionProvider @@ -35,23 +35,23 @@ internal class UserAccountDataDataSource @Inject constructor(@SessionDatabase pr private val realmSessionProvider: RealmSessionProvider, private val accountDataMapper: AccountDataMapper) { - fun getAccountDataEvent(type: String): AccountDataEvent? { + fun getAccountDataEvent(type: String): UserAccountDataEvent? { return getAccountDataEvents(setOf(type)).firstOrNull() } - fun getLiveAccountDataEvent(type: String): LiveData> { + fun getLiveAccountDataEvent(type: String): LiveData> { return Transformations.map(getLiveAccountDataEvents(setOf(type))) { it.firstOrNull()?.toOptional() } } - fun getAccountDataEvents(types: Set): List { + fun getAccountDataEvents(types: Set): List { return realmSessionProvider.withRealm { accountDataEventsQuery(it, types).findAll().map(accountDataMapper::map) } } - fun getLiveAccountDataEvents(types: Set): LiveData> { + fun getLiveAccountDataEvents(types: Set): LiveData> { return monarchy.findAllMappedWithChanges( { accountDataEventsQuery(it, types) }, accountDataMapper::map diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/widgets/WidgetManager.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/widgets/WidgetManager.kt index ca1a129da7..e0f43a11c5 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/widgets/WidgetManager.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/widgets/WidgetManager.kt @@ -23,7 +23,7 @@ import androidx.lifecycle.LiveData import androidx.lifecycle.Transformations import org.matrix.android.sdk.api.query.QueryStringValue import org.matrix.android.sdk.api.session.Session -import org.matrix.android.sdk.api.session.accountdata.AccountDataEvent +import org.matrix.android.sdk.api.session.accountdata.UserAccountDataEvent import org.matrix.android.sdk.api.session.accountdata.UserAccountDataTypes import org.matrix.android.sdk.api.session.events.model.Content import org.matrix.android.sdk.api.session.events.model.Event @@ -47,7 +47,7 @@ import javax.inject.Inject @SessionScope internal class WidgetManager @Inject constructor(private val integrationManager: IntegrationManager, - private val accountDataDataSource: UserAccountDataDataSource, + private val userAccountDataDataSource: UserAccountDataDataSource, private val stateEventDataSource: StateEventDataSource, private val createWidgetTask: CreateWidgetTask, private val widgetFactory: WidgetFactory, @@ -136,7 +136,7 @@ internal class WidgetManager @Inject constructor(private val integrationManager: widgetTypes: Set? = null, excludedTypes: Set? = null ): LiveData> { - val widgetsAccountData = accountDataDataSource.getLiveAccountDataEvent(UserAccountDataTypes.TYPE_WIDGETS) + val widgetsAccountData = userAccountDataDataSource.getLiveAccountDataEvent(UserAccountDataTypes.TYPE_WIDGETS) return Transformations.map(widgetsAccountData) { it.getOrNull()?.mapToWidgets(widgetTypes, excludedTypes).orEmpty() } @@ -146,12 +146,12 @@ internal class WidgetManager @Inject constructor(private val integrationManager: widgetTypes: Set? = null, excludedTypes: Set? = null ): List { - val widgetsAccountData = accountDataDataSource.getAccountDataEvent(UserAccountDataTypes.TYPE_WIDGETS) ?: return emptyList() + val widgetsAccountData = userAccountDataDataSource.getAccountDataEvent(UserAccountDataTypes.TYPE_WIDGETS) ?: return emptyList() return widgetsAccountData.mapToWidgets(widgetTypes, excludedTypes) } - private fun AccountDataEvent.mapToWidgets(widgetTypes: Set? = null, - excludedTypes: Set? = null): List { + private fun UserAccountDataEvent.mapToWidgets(widgetTypes: Set? = null, + excludedTypes: Set? = null): List { return extractWidgetSequence(widgetFactory) .filter { val widgetType = it.widgetContent.type ?: return@filter false diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/widgets/helper/UserAccountWidgets.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/widgets/helper/UserAccountWidgets.kt index 5aa32d5a31..6f423b38a0 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/widgets/helper/UserAccountWidgets.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/widgets/helper/UserAccountWidgets.kt @@ -19,10 +19,10 @@ package org.matrix.android.sdk.internal.session.widgets.helper import org.matrix.android.sdk.api.session.events.model.Event import org.matrix.android.sdk.api.session.events.model.toModel import org.matrix.android.sdk.api.util.JsonDict -import org.matrix.android.sdk.api.session.accountdata.AccountDataEvent +import org.matrix.android.sdk.api.session.accountdata.UserAccountDataEvent import org.matrix.android.sdk.api.session.widgets.model.Widget -internal fun AccountDataEvent.extractWidgetSequence(widgetFactory: WidgetFactory): Sequence { +internal fun UserAccountDataEvent.extractWidgetSequence(widgetFactory: WidgetFactory): Sequence { return content.asSequence() .mapNotNull { @Suppress("UNCHECKED_CAST") diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/wellknown/GetWellknownTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/wellknown/GetWellknownTask.kt index 7a9beac8c0..f11e87e1e7 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/wellknown/GetWellknownTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/wellknown/GetWellknownTask.kt @@ -18,7 +18,6 @@ package org.matrix.android.sdk.internal.wellknown import android.util.MalformedJsonException import dagger.Lazy -import org.matrix.android.sdk.api.MatrixPatterns import org.matrix.android.sdk.api.auth.data.HomeServerConnectionConfig import org.matrix.android.sdk.api.auth.data.WellKnown import org.matrix.android.sdk.api.auth.wellknown.WellknownResult @@ -39,7 +38,11 @@ import javax.net.ssl.HttpsURLConnection internal interface GetWellknownTask : Task { data class Params( - val matrixId: String, + /** + * domain, for instance "matrix.org" + * the URL will be https://{domain}/.well-known/matrix/client + */ + val domain: String, val homeServerConnectionConfig: HomeServerConnectionConfig? ) } @@ -54,14 +57,8 @@ internal class DefaultGetWellknownTask @Inject constructor( ) : GetWellknownTask { override suspend fun execute(params: GetWellknownTask.Params): WellknownResult { - if (!MatrixPatterns.isUserId(params.matrixId)) { - return WellknownResult.InvalidMatrixId - } - - val homeServerDomain = params.matrixId.substringAfter(":") - val client = buildClient(params.homeServerConnectionConfig) - return findClientConfig(homeServerDomain, client) + return findClientConfig(params.domain, client) } private fun buildClient(homeServerConnectionConfig: HomeServerConnectionConfig?): OkHttpClient { @@ -133,7 +130,7 @@ internal class DefaultGetWellknownTask @Inject constructor( } /** - * Return true if home server is valid, and (if applicable) if identity server is pingable + * Return true if homeserver is valid, and (if applicable) if identity server is pingable */ private suspend fun validateHomeServer(homeServerBaseUrl: String, wellKnown: WellKnown, client: OkHttpClient): WellknownResult { val capabilitiesAPI = retrofitFactory.create(client, homeServerBaseUrl) @@ -189,7 +186,7 @@ internal class DefaultGetWellknownTask @Inject constructor( } /** - * Try to get an identity server URL from a home server URL, using a .wellknown request + * Try to get an identity server URL from a homeserver URL, using a .wellknown request */ /* fun getIdentityServer(homeServerUrl: String, callback: ApiCallback) { diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/worker/WorkerParamsFactory.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/worker/WorkerParamsFactory.kt index 7aed74d2ca..7a47270efd 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/worker/WorkerParamsFactory.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/worker/WorkerParamsFactory.kt @@ -46,7 +46,7 @@ internal object WorkerParamsFactory { inline fun fromData(data: Data) = fromData(T::class.java, data) - fun fromData(clazz: Class, data: Data): T? = tryOrNull("Unable to parse work parameters") { + fun fromData(clazz: Class, data: Data): T? = tryOrNull("Unable to parse work parameters") { val json = data.getString(KEY) return if (json == null) { null diff --git a/matrix-sdk-android/src/main/java/org/matrix/androidsdk/crypto/data/MXDeviceInfo.java b/matrix-sdk-android/src/main/java/org/matrix/androidsdk/crypto/data/MXDeviceInfo.java index 393c633c6a..1014ceda0e 100755 --- a/matrix-sdk-android/src/main/java/org/matrix/androidsdk/crypto/data/MXDeviceInfo.java +++ b/matrix-sdk-android/src/main/java/org/matrix/androidsdk/crypto/data/MXDeviceInfo.java @@ -66,7 +66,7 @@ public class MXDeviceInfo implements Serializable { public Map> signatures; /* - * Additional data from the home server. + * Additional data from the homeserver. */ public Map unsigned; @@ -81,4 +81,4 @@ public class MXDeviceInfo implements Serializable { public MXDeviceInfo() { mVerified = DEVICE_VERIFICATION_UNKNOWN; } -} \ No newline at end of file +} diff --git a/matrix-sdk-android/src/main/res/values-fr/strings_sas.xml b/matrix-sdk-android/src/main/res/values-fr/strings_sas.xml index af9d797542..e7d6ef8058 100644 --- a/matrix-sdk-android/src/main/res/values-fr/strings_sas.xml +++ b/matrix-sdk-android/src/main/res/values-fr/strings_sas.xml @@ -33,11 +33,11 @@ CƓur Sourire Robot - ChĂąpeau + Chapeau Lunettes ClĂ© Ă  molette PĂšre NoĂ«l - Pouce en l\'air + Pouce en l’air Parapluie Sablier RĂ©veil diff --git a/matrix-sdk-android/src/main/res/values-hu/strings_sas.xml b/matrix-sdk-android/src/main/res/values-hu/strings_sas.xml new file mode 100644 index 0000000000..5b5e0e0205 --- /dev/null +++ b/matrix-sdk-android/src/main/res/values-hu/strings_sas.xml @@ -0,0 +1,68 @@ + + + + Kutya + Macska + OroszlĂĄn + LĂł + EgyszarvĂș + Malac + ElefĂĄnt + NyĂșl + Panda + Kakas + Pingvin + TeknƑs + Hal + Polip + PillangĂł + VirĂĄg + Fa + Kaktusz + Gomba + Földgömb + Hold + FelhƑ + TƱz + BanĂĄn + Alma + Eper + Kukorica + Pizza + SĂŒti + SzĂ­v + Mosoly + Robot + Kalap + SzemĂŒveg + Csavarkulcs + TĂ©lapĂł + HĂŒvelykujj fel + EsernyƑ + HomokĂłra + Óra + AjĂĄndĂ©k + ÉgƑ + Könyv + Ceruza + GĂ©mkapocs + OllĂł + Lakat + Kulcs + KalapĂĄcs + Telefon + ZĂĄszlĂł + Vonat + KerĂ©kpĂĄr + RepĂŒlƑ + RakĂĄta + TrĂłfea + Labda + GitĂĄr + Trombita + Harang + Horgony + FejhallgatĂł + Mappa + Rajszeg + diff --git a/matrix-sdk-android/src/main/res/values-tzm/strings_sas.xml b/matrix-sdk-android/src/main/res/values-tzm/strings_sas.xml new file mode 100644 index 0000000000..bebf64e5ab --- /dev/null +++ b/matrix-sdk-android/src/main/res/values-tzm/strings_sas.xml @@ -0,0 +1,30 @@ + + + + Aydi + Amuc + Izem + Ayyis + Ilef + Ilu + Agnin + Ayaáș“iឍ + Ifker + Aselm + Aseklu + Agursel + Ayyur + Timessi + Tabanant + Tadeffuyt + Ul + Aáč›ubu + Taraza + Adlis + Tasarut + Atilifun + Acenyal + Tcama + Agiáč­aáč› + Asdaw + From 4331c0bc6cc0f3f6c4f6ccf24ab3949d31e6419b Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Mon, 30 Aug 2021 14:31:28 +0200 Subject: [PATCH 03/17] Import 1.2.0 from Element --- matrix-sdk-android/build.gradle | 8 +- .../android/sdk/api/logger/LoggerTag.kt | 34 +++++ .../sdk/api/pushrules/PushRuleService.kt | 8 +- .../sdk/api/pushrules/rest/PushRule.kt | 8 ++ .../android/sdk/api/session/call/CallState.kt | 6 +- .../android/sdk/api/session/call/MxCall.kt | 4 +- .../session/content/ContentAttachmentData.kt | 3 +- .../homeserver/HomeServerCapabilities.kt | 56 ++++++++ .../session/homeserver/RoomVersionModel.kt | 9 +- .../session/room/RoomSummaryQueryParams.kt | 9 +- .../room/model/call/CallHangupContent.kt | 28 +--- .../room/model/call/CallRejectContent.kt | 7 +- .../session/room/model/call/EndCallReason.kt | 50 +++++++ .../room/model/create/CreateRoomParams.kt | 11 +- .../room/model/create/CreateRoomStateEvent.kt | 36 +++++ .../room/model/create/RoomFeaturePreset.kt | 56 ++++++++ .../session/room/model/message/AudioInfo.kt | 6 +- .../room/model/message/AudioWaveformInfo.kt | 36 +++++ .../room/model/message/MessageAudioContent.kt | 13 +- .../api/session/room/state/StateService.kt | 7 +- .../matrix/android/sdk/api/util/MimeTypes.kt | 2 + .../sdk/internal/auth/SessionCreator.kt | 2 + .../internal/crypto/DefaultCryptoService.kt | 8 +- .../sdk/internal/crypto/DeviceListManager.kt | 8 +- .../internal/crypto/OneTimeKeysUploader.kt | 18 ++- .../internal/crypto/model/CryptoDeviceInfo.kt | 6 - .../internal/crypto/store/IMXCryptoStore.kt | 2 +- .../crypto/store/db/RealmCryptoStore.kt | 44 ++++--- .../store/db/RealmCryptoStoreMigration.kt | 59 ++++++++- .../crypto/store/db/model/CryptoMapper.kt | 43 +++--- .../VerificationTransportToDevice.kt | 8 +- .../mapper/HomeServerCapabilitiesMapper.kt | 34 +++-- .../internal/session/DefaultFileService.kt | 34 ++++- .../session/call/CallEventProcessor.kt | 11 +- .../session/call/CallSignalingHandler.kt | 27 ++-- .../call/DefaultCallSignalingService.kt | 2 - .../internal/session/call/model/MxCallImpl.kt | 34 ++--- .../homeserver/GetCapabilitiesResult.kt | 17 ++- .../notification/DefaultPushRuleService.kt | 4 +- .../session/permalinks/ViaParameterFinder.kt | 33 ++++- .../pushers/UpdatePushRuleActionsTask.kt | 26 ++-- .../room/create/CreateRoomBodyBuilder.kt | 58 ++++----- .../session/room/send/DefaultSendService.kt | 3 +- .../room/send/LocalEchoEventFactory.kt | 11 +- .../session/room/send/WaveFormSanitizer.kt | 86 ++++++++++++ .../session/room/state/DefaultStateService.kt | 38 +++++- .../room/summary/RoomSummaryDataSource.kt | 18 +-- .../internal/util/file/AtomicFileCreator.kt | 42 ++++++ .../room/send/WaveFormSanitizerTest.kt | 123 ++++++++++++++++++ 49 files changed, 971 insertions(+), 225 deletions(-) create mode 100644 matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/logger/LoggerTag.kt create mode 100644 matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/call/EndCallReason.kt create mode 100644 matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/create/CreateRoomStateEvent.kt create mode 100644 matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/create/RoomFeaturePreset.kt create mode 100644 matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/AudioWaveformInfo.kt create mode 100644 matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/WaveFormSanitizer.kt create mode 100644 matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/util/file/AtomicFileCreator.kt create mode 100644 matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/session/room/send/WaveFormSanitizerTest.kt diff --git a/matrix-sdk-android/build.gradle b/matrix-sdk-android/build.gradle index 391e408768..2717f95ed8 100644 --- a/matrix-sdk-android/build.gradle +++ b/matrix-sdk-android/build.gradle @@ -21,7 +21,7 @@ android { minSdkVersion 21 targetSdkVersion 30 versionCode 1 - versionName "1.1.13" + versionName "1.2.0" // Multidex is useful for tests multiDexEnabled true testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" @@ -112,7 +112,7 @@ dependencies { def lifecycle_version = '2.2.0' def arch_version = '2.1.0' def markwon_version = '3.1.0' - def daggerVersion = '2.37' + def daggerVersion = '2.38' def work_version = '2.5.0' def retrofit_version = '2.9.0' @@ -120,7 +120,7 @@ dependencies { implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:$kotlin_coroutines_version" implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$kotlin_coroutines_version" - implementation "androidx.appcompat:appcompat:1.3.0" + implementation "androidx.appcompat:appcompat:1.3.1" implementation "androidx.core:core-ktx:1.6.0" implementation "androidx.lifecycle:lifecycle-extensions:$lifecycle_version" @@ -169,7 +169,7 @@ dependencies { implementation 'com.otaliastudios:transcoder:0.10.3' // Phone number https://github.com/google/libphonenumber - implementation 'com.googlecode.libphonenumber:libphonenumber:8.12.27' + implementation 'com.googlecode.libphonenumber:libphonenumber:8.12.28' testImplementation 'junit:junit:4.13.2' testImplementation 'org.robolectric:robolectric:4.5.1' diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/logger/LoggerTag.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/logger/LoggerTag.kt new file mode 100644 index 0000000000..51f9b50699 --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/logger/LoggerTag.kt @@ -0,0 +1,34 @@ +/* + * Copyright (c) 2021 The Matrix.org Foundation C.I.C. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.matrix.android.sdk.api.logger + +/** + * Parent class for custom logger tags. Can be used with Timber : + * + * val loggerTag = LoggerTag("MyTag", LoggerTag.VOIP) + * Timber.tag(loggerTag.value).v("My log message") + */ +open class LoggerTag(_value: String, parentTag: LoggerTag? = null) { + + object VOIP : LoggerTag("VOIP") + + val value: String = if (parentTag == null) { + _value + } else { + "${parentTag.value}/$_value" + } +} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/pushrules/PushRuleService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/pushrules/PushRuleService.kt index d9bf5cfd13..4534368679 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/pushrules/PushRuleService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/pushrules/PushRuleService.kt @@ -31,7 +31,13 @@ interface PushRuleService { suspend fun addPushRule(kind: RuleKind, pushRule: PushRule) - suspend fun updatePushRuleActions(kind: RuleKind, oldPushRule: PushRule, newPushRule: PushRule) + /** + * Enables/Disables a push rule and updates the actions if necessary + * @param enable Enables/Disables the rule + * @param actions Actions to update if not null + */ + + suspend fun updatePushRuleActions(kind: RuleKind, ruleId: String, enable: Boolean, actions: List?) suspend fun removePushRule(kind: RuleKind, pushRule: PushRule) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/pushrules/rest/PushRule.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/pushrules/rest/PushRule.kt index 3a9fc4fb83..31d7770a9f 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/pushrules/rest/PushRule.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/pushrules/rest/PushRule.kt @@ -18,6 +18,7 @@ package org.matrix.android.sdk.api.pushrules.rest import com.squareup.moshi.Json import com.squareup.moshi.JsonClass +import org.matrix.android.sdk.api.extensions.orFalse import org.matrix.android.sdk.api.pushrules.Action import org.matrix.android.sdk.api.pushrules.getActions import org.matrix.android.sdk.api.pushrules.toJson @@ -100,6 +101,13 @@ data class PushRule( ) } + /** + * Get the highlight status. As spec mentions assume false if no tweak present. + */ + fun getHighlight(): Boolean { + return getActions().filterIsInstance().firstOrNull()?.highlight.orFalse() + } + /** * Set the notification status. * diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/call/CallState.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/call/CallState.kt index 2dbd1c9b01..47a63b4a25 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/call/CallState.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/call/CallState.kt @@ -16,6 +16,8 @@ package org.matrix.android.sdk.api.session.call +import org.matrix.android.sdk.api.session.room.model.call.EndCallReason + sealed class CallState { /** Idle, setting up objects */ @@ -42,6 +44,6 @@ sealed class CallState { * */ data class Connected(val iceConnectionState: MxPeerConnectionState) : CallState() - /** Terminated. Incoming/Outgoing call, the call is terminated */ - object Terminated : CallState() + /** Ended. Incoming/Outgoing call, the call is terminated */ + data class Ended(val reason: EndCallReason? = null) : CallState() } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/call/MxCall.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/call/MxCall.kt index fcc9f7072d..dd23e81cc6 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/call/MxCall.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/call/MxCall.kt @@ -18,7 +18,7 @@ package org.matrix.android.sdk.api.session.call import org.matrix.android.sdk.api.session.room.model.call.CallCandidate import org.matrix.android.sdk.api.session.room.model.call.CallCapabilities -import org.matrix.android.sdk.api.session.room.model.call.CallHangupContent +import org.matrix.android.sdk.api.session.room.model.call.EndCallReason import org.matrix.android.sdk.api.session.room.model.call.SdpType import org.matrix.android.sdk.api.util.Optional @@ -69,7 +69,7 @@ interface MxCall : MxCallDetail { /** * End the call */ - fun hangUp(reason: CallHangupContent.Reason? = null) + fun hangUp(reason: EndCallReason? = null) /** * Start a call diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/content/ContentAttachmentData.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/content/ContentAttachmentData.kt index 98a84b8b66..7ee26de8db 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/content/ContentAttachmentData.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/content/ContentAttachmentData.kt @@ -35,7 +35,8 @@ data class ContentAttachmentData( val name: String? = null, val queryUri: Uri, val mimeType: String?, - val type: Type + val type: Type, + val waveform: List? = null ) : Parcelable { @JsonClass(generateAdapter = false) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/homeserver/HomeServerCapabilities.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/homeserver/HomeServerCapabilities.kt index 10c2db4535..b49236c338 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/homeserver/HomeServerCapabilities.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/homeserver/HomeServerCapabilities.kt @@ -40,7 +40,63 @@ data class HomeServerCapabilities( */ val roomVersions: RoomVersionCapabilities? = null ) { + + enum class RoomCapabilitySupport { + SUPPORTED, + SUPPORTED_UNSTABLE, + UNSUPPORTED, + UNKNOWN + } + + /** + * Check if a feature is supported by the homeserver. + * @return + * UNKNOWN if the server does not implement room caps + * UNSUPPORTED if this feature is not supported + * SUPPORTED if this feature is supported by a stable version + * SUPPORTED_UNSTABLE if this feature is supported by an unstable version + * (unstable version should only be used for dev/experimental purpose) + */ + fun isFeatureSupported(feature: String): RoomCapabilitySupport { + if (roomVersions?.capabilities == null) return RoomCapabilitySupport.UNKNOWN + val info = roomVersions.capabilities[feature] ?: return RoomCapabilitySupport.UNSUPPORTED + + val preferred = info.preferred ?: info.support.lastOrNull() + val versionCap = roomVersions.supportedVersion.firstOrNull { it.version == preferred } + + return when { + versionCap == null -> { + RoomCapabilitySupport.UNKNOWN + } + versionCap.status == RoomVersionStatus.STABLE -> { + RoomCapabilitySupport.SUPPORTED + } + else -> { + RoomCapabilitySupport.SUPPORTED_UNSTABLE + } + } + } + fun isFeatureSupported(feature: String, byRoomVersion: String): Boolean { + if (roomVersions?.capabilities == null) return false + val info = roomVersions.capabilities[feature] ?: return false + + return info.preferred == byRoomVersion || info.support.contains(byRoomVersion) + } + + /** + * Use this method to know if you should force a version when creating + * a room that requires this feature. + * You can also use #isFeatureSupported prior to this call to check if the + * feature is supported and report some feedback to user. + */ + fun versionOverrideForFeature(feature: String) : String? { + val cap = roomVersions?.capabilities?.get(feature) + return cap?.preferred ?: cap?.support?.lastOrNull() + } + companion object { const val MAX_UPLOAD_FILE_SIZE_UNKNOWN = -1L + const val ROOM_CAP_KNOCK = "knock" + const val ROOM_CAP_RESTRICTED = "restricted" } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/homeserver/RoomVersionModel.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/homeserver/RoomVersionModel.kt index 7798b4cc63..9f8e9aa1d1 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/homeserver/RoomVersionModel.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/homeserver/RoomVersionModel.kt @@ -18,7 +18,9 @@ package org.matrix.android.sdk.api.session.homeserver data class RoomVersionCapabilities( val defaultRoomVersion: String, - val supportedVersion: List + val supportedVersion: List, + // Keys are capabilities defined per spec, as for now knock or restricted + val capabilities: Map? ) data class RoomVersionInfo( @@ -26,6 +28,11 @@ data class RoomVersionInfo( val status: RoomVersionStatus ) +data class RoomCapabilitySupport( + val preferred: String?, + val support: List +) + enum class RoomVersionStatus { STABLE, UNSTABLE diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/RoomSummaryQueryParams.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/RoomSummaryQueryParams.kt index 88ec2de768..b440857518 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/RoomSummaryQueryParams.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/RoomSummaryQueryParams.kt @@ -39,12 +39,6 @@ fun spaceSummaryQueryParams(init: (RoomSummaryQueryParams.Builder.() -> Unit) = .build() } -enum class RoomCategoryFilter { - ONLY_DM, - ONLY_ROOMS, - ALL -} - /** * This class can be used to filter room summaries to use with: * [org.matrix.android.sdk.api.session.room.Room] and [org.matrix.android.sdk.api.session.room.RoomService] @@ -59,11 +53,10 @@ data class RoomSummaryQueryParams( val excludeType: List?, val includeType: List?, val activeSpaceFilter: ActiveSpaceFilter?, - var activeGroupId: String? = null + val activeGroupId: String? = null ) { class Builder { - var roomId: QueryStringValue = QueryStringValue.IsNotEmpty var displayName: QueryStringValue = QueryStringValue.IsNotEmpty var canonicalAlias: QueryStringValue = QueryStringValue.NoCondition diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/call/CallHangupContent.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/call/CallHangupContent.kt index 9d6e1a7eae..31f801dd6f 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/call/CallHangupContent.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/call/CallHangupContent.kt @@ -43,29 +43,5 @@ data class CallHangupContent( * or `invite_timeout` for when the other party did not answer in time. * One of: ["ice_failed", "invite_timeout"] */ - @Json(name = "reason") val reason: Reason? = null -) : CallSignalingContent { - @JsonClass(generateAdapter = false) - enum class Reason { - @Json(name = "ice_failed") - ICE_FAILED, - - @Json(name = "ice_timeout") - ICE_TIMEOUT, - - @Json(name = "user_hangup") - USER_HANGUP, - - @Json(name = "replaced") - REPLACED, - - @Json(name = "user_media_failed") - USER_MEDIA_FAILED, - - @Json(name = "invite_timeout") - INVITE_TIMEOUT, - - @Json(name = "unknown_error") - UNKWOWN_ERROR - } -} + @Json(name = "reason") val reason: EndCallReason? = null +) : CallSignalingContent diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/call/CallRejectContent.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/call/CallRejectContent.kt index ea412fbe3e..1b9a7186e2 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/call/CallRejectContent.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/call/CallRejectContent.kt @@ -36,5 +36,10 @@ data class CallRejectContent( /** * Required. The version of the VoIP specification this message adheres to. */ - @Json(name = "version") override val version: String? + @Json(name = "version") override val version: String?, + + /** + * Optional error reason for the reject. + */ + @Json(name = "reason") val reason: EndCallReason? = null ) : CallSignalingContent diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/call/EndCallReason.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/call/EndCallReason.kt new file mode 100644 index 0000000000..60e038b2f9 --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/call/EndCallReason.kt @@ -0,0 +1,50 @@ +/* + * Copyright (c) 2021 The Matrix.org Foundation C.I.C. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.matrix.android.sdk.api.session.room.model.call + +import com.squareup.moshi.Json +import com.squareup.moshi.JsonClass + +@JsonClass(generateAdapter = false) +enum class EndCallReason { + @Json(name = "ice_failed") + ICE_FAILED, + + @Json(name = "ice_timeout") + ICE_TIMEOUT, + + @Json(name = "user_hangup") + USER_HANGUP, + + @Json(name = "replaced") + REPLACED, + + @Json(name = "user_media_failed") + USER_MEDIA_FAILED, + + @Json(name = "invite_timeout") + INVITE_TIMEOUT, + + @Json(name = "unknown_error") + UNKWOWN_ERROR, + + @Json(name = "user_busy") + USER_BUSY, + + @Json(name = "answered_elsewhere") + ANSWERED_ELSEWHERE +} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/create/CreateRoomParams.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/create/CreateRoomParams.kt index ca8c66bb3b..5667906000 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/create/CreateRoomParams.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/create/CreateRoomParams.kt @@ -22,10 +22,8 @@ import org.matrix.android.sdk.api.session.room.model.GuestAccess import org.matrix.android.sdk.api.session.room.model.PowerLevelsContent import org.matrix.android.sdk.api.session.room.model.RoomDirectoryVisibility import org.matrix.android.sdk.api.session.room.model.RoomHistoryVisibility -import org.matrix.android.sdk.api.session.room.model.RoomJoinRulesAllowEntry import org.matrix.android.sdk.internal.crypto.MXCRYPTO_ALGORITHM_MEGOLM -// TODO Give a way to include other initial states open class CreateRoomParams { /** * A public visibility indicates that the room will be shown in the published room list. @@ -103,6 +101,13 @@ open class CreateRoomParams { */ val creationContent = mutableMapOf() + /** + * A list of state events to set in the new room. This allows the user to override the default state events + * set in the new room. The expected format of the state events are an object with type, state_key and content keys set. + * Takes precedence over events set by preset, but gets overridden by name and topic keys. + */ + val initialStates = mutableListOf() + /** * Set to true to disable federation of this room. * Default: false @@ -156,7 +161,7 @@ open class CreateRoomParams { var roomVersion: String? = null - var joinRuleRestricted: List? = null + var featurePreset: RoomFeaturePreset? = null companion object { private const val CREATION_CONTENT_KEY_M_FEDERATE = "m.federate" diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/create/CreateRoomStateEvent.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/create/CreateRoomStateEvent.kt new file mode 100644 index 0000000000..fcfdc3e333 --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/create/CreateRoomStateEvent.kt @@ -0,0 +1,36 @@ +/* + * Copyright (c) 2021 The Matrix.org Foundation C.I.C. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.matrix.android.sdk.api.session.room.model.create + +import org.matrix.android.sdk.api.session.events.model.Content + +data class CreateRoomStateEvent( + /** + * Required. The type of event to send. + */ + val type: String, + + /** + * Required. The content of the event. + */ + val content: Content, + + /** + * The state_key of the state event. Defaults to an empty string. + */ + val stateKey: String = "" +) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/create/RoomFeaturePreset.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/create/RoomFeaturePreset.kt new file mode 100644 index 0000000000..f5f722d783 --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/create/RoomFeaturePreset.kt @@ -0,0 +1,56 @@ +/* + * Copyright 2020 The Matrix.org Foundation C.I.C. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.matrix.android.sdk.api.session.room.model.create + +import org.matrix.android.sdk.api.session.events.model.Event +import org.matrix.android.sdk.api.session.events.model.EventType +import org.matrix.android.sdk.api.session.events.model.toContent +import org.matrix.android.sdk.api.session.homeserver.HomeServerCapabilities +import org.matrix.android.sdk.api.session.room.model.GuestAccess +import org.matrix.android.sdk.api.session.room.model.RoomHistoryVisibility +import org.matrix.android.sdk.api.session.room.model.RoomJoinRules +import org.matrix.android.sdk.api.session.room.model.RoomJoinRulesAllowEntry +import org.matrix.android.sdk.api.session.room.model.RoomJoinRulesContent + +interface RoomFeaturePreset { + + fun updateRoomParams(params: CreateRoomParams) + + fun setupInitialStates(): List? +} + +class RestrictedRoomPreset(val homeServerCapabilities: HomeServerCapabilities, val restrictedList: List) : RoomFeaturePreset { + + override fun updateRoomParams(params: CreateRoomParams) { + params.historyVisibility = params.historyVisibility ?: RoomHistoryVisibility.SHARED + params.guestAccess = params.guestAccess ?: GuestAccess.Forbidden + params.roomVersion = homeServerCapabilities.versionOverrideForFeature(HomeServerCapabilities.ROOM_CAP_RESTRICTED) + } + + override fun setupInitialStates(): List? { + return listOf( + Event( + type = EventType.STATE_ROOM_JOIN_RULES, + stateKey = "", + content = RoomJoinRulesContent( + _joinRules = RoomJoinRules.RESTRICTED.value, + allowList = restrictedList + ).toContent() + ) + ) + } +} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/AudioInfo.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/AudioInfo.kt index b72c8e5dfd..ea6bdfafad 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/AudioInfo.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/AudioInfo.kt @@ -24,15 +24,15 @@ data class AudioInfo( /** * The mimetype of the audio e.g. "audio/aac". */ - @Json(name = "mimetype") val mimeType: String?, + @Json(name = "mimetype") val mimeType: String? = null, /** * The size of the audio clip in bytes. */ - @Json(name = "size") val size: Long = 0, + @Json(name = "size") val size: Long? = null, /** * The duration of the audio in milliseconds. */ - @Json(name = "duration") val duration: Int = 0 + @Json(name = "duration") val duration: Int? = null ) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/AudioWaveformInfo.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/AudioWaveformInfo.kt new file mode 100644 index 0000000000..d576f1057a --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/AudioWaveformInfo.kt @@ -0,0 +1,36 @@ +/* + * Copyright 2020 The Matrix.org Foundation C.I.C. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.matrix.android.sdk.api.session.room.model.message + +import com.squareup.moshi.Json +import com.squareup.moshi.JsonClass + +/** + * See https://github.com/matrix-org/matrix-doc/blob/travis/msc/audio-waveform/proposals/3246-audio-waveform.md + */ +@JsonClass(generateAdapter = true) +data class AudioWaveformInfo( + @Json(name = "duration") + val duration: Int? = null, + + /** + * The array should have no less than 30 elements and no more than 120. + * List of integers between zero and 1024, inclusive. + */ + @Json(name = "waveform") + val waveform: List? = null +) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/MessageAudioContent.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/MessageAudioContent.kt index b4ba5c0a66..1bcb10d88c 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/MessageAudioContent.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/MessageAudioContent.kt @@ -20,6 +20,7 @@ import com.squareup.moshi.Json import com.squareup.moshi.JsonClass import org.matrix.android.sdk.api.session.events.model.Content import org.matrix.android.sdk.api.session.room.model.relation.RelationDefaultContent +import org.matrix.android.sdk.api.util.JsonDict import org.matrix.android.sdk.internal.crypto.model.rest.EncryptedFileInfo @JsonClass(generateAdapter = true) @@ -50,7 +51,17 @@ data class MessageAudioContent( /** * Required if the file is encrypted. Information on the encrypted file, as specified in End-to-end encryption. */ - @Json(name = "file") override val encryptedFileInfo: EncryptedFileInfo? = null + @Json(name = "file") override val encryptedFileInfo: EncryptedFileInfo? = null, + + /** + * Encapsulates waveform and duration of the audio. + */ + @Json(name = "org.matrix.msc1767.audio") val audioWaveformInfo: AudioWaveformInfo? = null, + + /** + * Indicates that is a voice message. + */ + @Json(name = "org.matrix.msc3245.voice") val voiceMessageIndicator: JsonDict? = null ) : MessageWithAttachmentContent { override val mimeType: String? diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/state/StateService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/state/StateService.kt index e614ea91d6..4d3f95233d 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/state/StateService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/state/StateService.kt @@ -23,6 +23,7 @@ import org.matrix.android.sdk.api.session.events.model.Event import org.matrix.android.sdk.api.session.room.model.GuestAccess import org.matrix.android.sdk.api.session.room.model.RoomHistoryVisibility import org.matrix.android.sdk.api.session.room.model.RoomJoinRules +import org.matrix.android.sdk.api.session.room.model.RoomJoinRulesAllowEntry import org.matrix.android.sdk.api.util.JsonDict import org.matrix.android.sdk.api.util.Optional @@ -53,7 +54,7 @@ interface StateService { /** * Update the join rule and/or the guest access */ - suspend fun updateJoinRule(joinRules: RoomJoinRules?, guestAccess: GuestAccess?) + suspend fun updateJoinRule(joinRules: RoomJoinRules?, guestAccess: GuestAccess?, allowList: List? = null) /** * Update the avatar of the room @@ -91,4 +92,8 @@ interface StateService { * @param eventTypes Set of eventType to observe. If empty, all state events will be observed */ fun getStateEventsLive(eventTypes: Set, stateKey: QueryStringValue = QueryStringValue.NoCondition): LiveData> + + suspend fun setJoinRulePublic() + suspend fun setJoinRuleInviteOnly() + suspend fun setJoinRuleRestricted(allowList: List) } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/util/MimeTypes.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/util/MimeTypes.kt index 182b37f2ad..ef47775f1b 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/util/MimeTypes.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/util/MimeTypes.kt @@ -31,6 +31,8 @@ object MimeTypes { const val Jpeg = "image/jpeg" const val Gif = "image/gif" + const val Ogg = "audio/ogg" + fun String?.normalizeMimeType() = if (this == BadJpg) Jpeg else this fun String?.isMimeTypeImage() = this?.startsWith("image/").orFalse() diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/SessionCreator.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/SessionCreator.kt index 160fd2d556..cc00c963ea 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/SessionCreator.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/SessionCreator.kt @@ -49,6 +49,8 @@ internal class DefaultSessionCreator @Inject constructor( // remove trailing "/" ?.trim { it == '/' } ?.takeIf { it.isNotBlank() } + // It can be the same value, so in this case, do not check again the validity + ?.takeIf { it != homeServerConnectionConfig.homeServerUriBase.toString() } ?.also { Timber.d("Overriding homeserver url to $it (will check if valid)") } ?.let { Uri.parse(it) } ?.takeIf { diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/DefaultCryptoService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/DefaultCryptoService.kt index d170ae3dd3..563c890950 100755 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/DefaultCryptoService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/DefaultCryptoService.kt @@ -314,6 +314,12 @@ internal class DefaultCryptoService @Inject constructor( cryptoCoroutineScope.launchToCallback(coroutineDispatchers.crypto, NoOpMatrixCallback()) { // Open the store cryptoStore.open() + + if (!cryptoStore.areDeviceKeysUploaded()) { + // Schedule upload of OTK + oneTimeKeysUploader.updateOneTimeKeyCount(0) + } + // this can throw if no network tryOrNull { uploadDeviceKeys() @@ -905,7 +911,7 @@ internal class DefaultCryptoService @Inject constructor( * Upload my user's device keys. */ private suspend fun uploadDeviceKeys() { - if (cryptoStore.getDeviceKeysUploaded()) { + if (cryptoStore.areDeviceKeysUploaded()) { Timber.d("Keys already uploaded, nothing to do") return } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/DeviceListManager.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/DeviceListManager.kt index 63f15aaf6e..79910c6de2 100755 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/DeviceListManager.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/DeviceListManager.kt @@ -16,6 +16,7 @@ package org.matrix.android.sdk.internal.crypto +import kotlinx.coroutines.CancellationException import kotlinx.coroutines.launch import org.matrix.android.sdk.api.MatrixPatterns import org.matrix.android.sdk.api.auth.data.Credentials @@ -336,7 +337,12 @@ internal class DeviceListManager @Inject constructor(private val cryptoStore: IM downloadKeysForUsersTask.execute(params) } catch (throwable: Throwable) { Timber.e(throwable, "## CRYPTO | doKeyDownloadForUsers(): error") - onKeysDownloadFailed(filteredUsers) + if (throwable is CancellationException) { + // the crypto module is getting closed, so we cannot access the DB anymore + Timber.w("The crypto module is closed, ignoring this error") + } else { + onKeysDownloadFailed(filteredUsers) + } throw throwable } Timber.v("## CRYPTO | doKeyDownloadForUsers() : Got keys for " + filteredUsers.size + " users") diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/OneTimeKeysUploader.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/OneTimeKeysUploader.kt index 6695234d62..c4b62fe9fe 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/OneTimeKeysUploader.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/OneTimeKeysUploader.kt @@ -16,6 +16,7 @@ package org.matrix.android.sdk.internal.crypto +import org.matrix.android.sdk.api.extensions.tryOrNull import org.matrix.android.sdk.internal.crypto.model.MXKey import org.matrix.android.sdk.internal.crypto.model.rest.KeysUploadResponse import org.matrix.android.sdk.internal.crypto.tasks.UploadKeysTask @@ -77,6 +78,10 @@ internal class OneTimeKeysUploader @Inject constructor( // discard the oldest private keys first. This will eventually clean // out stale private keys that won't receive a message. val keyLimit = floor(maxOneTimeKeys / 2.0).toInt() + if (oneTimeKeyCount == null) { + // Ask the server how many otk he has + oneTimeKeyCount = fetchOtkCount() + } val oneTimeKeyCountFromSync = oneTimeKeyCount if (oneTimeKeyCountFromSync != null) { // We need to keep a pool of one time public keys on the server so that @@ -90,17 +95,22 @@ internal class OneTimeKeysUploader @Inject constructor( // private keys clogging up our local storage. // So we need some kind of engineering compromise to balance all of // these factors. - try { + tryOrNull("Unable to upload OTK") { val uploadedKeys = uploadOTK(oneTimeKeyCountFromSync, keyLimit) Timber.v("## uploadKeys() : success, $uploadedKeys key(s) sent") - } finally { - oneTimeKeyCheckInProgress = false } } else { Timber.w("maybeUploadOneTimeKeys: waiting to know the number of OTK from the sync") - oneTimeKeyCheckInProgress = false lastOneTimeKeyCheck = 0 } + oneTimeKeyCheckInProgress = false + } + + private suspend fun fetchOtkCount(): Int? { + return tryOrNull("Unable to get OTK count") { + val result = uploadKeysTask.execute(UploadKeysTask.Params(null, null)) + result.oneTimeKeyCountsForAlgorithm(MXKey.KEY_SIGNED_CURVE_25519_TYPE) + } } /** diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/CryptoDeviceInfo.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/CryptoDeviceInfo.kt index 4004294d97..5e7744853a 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/CryptoDeviceInfo.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/CryptoDeviceInfo.kt @@ -18,8 +18,6 @@ package org.matrix.android.sdk.internal.crypto.model import org.matrix.android.sdk.internal.crypto.crosssigning.DeviceTrustLevel import org.matrix.android.sdk.internal.crypto.model.rest.DeviceKeys import org.matrix.android.sdk.internal.crypto.model.rest.UnsignedDeviceInfo -import org.matrix.android.sdk.internal.crypto.store.db.model.CryptoMapper -import org.matrix.android.sdk.internal.crypto.store.db.model.DeviceInfoEntity data class CryptoDeviceInfo( val deviceId: String, @@ -77,7 +75,3 @@ data class CryptoDeviceInfo( internal fun CryptoDeviceInfo.toRest(): DeviceKeys { return CryptoInfoMapper.map(this) } - -internal fun CryptoDeviceInfo.toEntity(): DeviceInfoEntity { - return CryptoMapper.mapToEntity(this) -} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/IMXCryptoStore.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/IMXCryptoStore.kt index 181bd94cc7..3d12e74fcd 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/IMXCryptoStore.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/IMXCryptoStore.kt @@ -475,7 +475,7 @@ internal interface IMXCryptoStore { fun getGossipingEvents(): List fun setDeviceKeysUploaded(uploaded: Boolean) - fun getDeviceKeysUploaded(): Boolean + fun areDeviceKeysUploaded(): Boolean fun tidyUpDataBase() fun logDbUsageInfo() } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/RealmCryptoStore.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/RealmCryptoStore.kt index 9ae93d61eb..d997998836 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/RealmCryptoStore.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/RealmCryptoStore.kt @@ -51,7 +51,6 @@ import org.matrix.android.sdk.internal.crypto.model.OutboundGroupSessionWrapper import org.matrix.android.sdk.internal.crypto.model.event.RoomKeyWithHeldContent import org.matrix.android.sdk.internal.crypto.model.rest.DeviceInfo import org.matrix.android.sdk.internal.crypto.model.rest.RoomKeyRequestBody -import org.matrix.android.sdk.internal.crypto.model.toEntity import org.matrix.android.sdk.internal.crypto.store.IMXCryptoStore import org.matrix.android.sdk.internal.crypto.store.PrivateKeysInfo import org.matrix.android.sdk.internal.crypto.store.SavedKeyBackupKeyInfo @@ -280,24 +279,37 @@ internal class RealmCryptoStore @Inject constructor( override fun storeUserDevices(userId: String, devices: Map?) { doRealmTransaction(realmConfiguration) { realm -> if (devices == null) { + Timber.d("Remove user $userId") // Remove the user UserEntity.delete(realm, userId) } else { - UserEntity.getOrCreate(realm, userId) - .let { u -> - // Add the devices - val currentKnownDevices = u.devices.toList() - val new = devices.map { entry -> entry.value.toEntity() } - new.forEach { entity -> - // Maintain first time seen - val existing = currentKnownDevices.firstOrNull { it.deviceId == entity.deviceId && it.identityKey == entity.identityKey } - entity.firstTimeSeenLocalTs = existing?.firstTimeSeenLocalTs ?: System.currentTimeMillis() - realm.insertOrUpdate(entity) - } - // Ensure all other devices are deleted - u.devices.clearWith { it.deleteOnCascade() } - u.devices.addAll(new) + val userEntity = UserEntity.getOrCreate(realm, userId) + // First delete the removed devices + val deviceIds = devices.keys + userEntity.devices.toTypedArray().iterator().let { + while (it.hasNext()) { + val deviceInfoEntity = it.next() + if (deviceInfoEntity.deviceId !in deviceIds) { + Timber.d("Remove device ${deviceInfoEntity.deviceId} of user $userId") + deviceInfoEntity.deleteOnCascade() } + } + } + // Then update existing devices or add new one + devices.values.forEach { cryptoDeviceInfo -> + val existingDeviceInfoEntity = userEntity.devices.firstOrNull { it.deviceId == cryptoDeviceInfo.deviceId } + if (existingDeviceInfoEntity == null) { + // Add the device + Timber.d("Add device ${cryptoDeviceInfo.deviceId} of user $userId") + val newEntity = CryptoMapper.mapToEntity(cryptoDeviceInfo) + newEntity.firstTimeSeenLocalTs = System.currentTimeMillis() + userEntity.devices.add(newEntity) + } else { + // Update the device + Timber.d("Update device ${cryptoDeviceInfo.deviceId} of user $userId") + CryptoMapper.updateDeviceInfoEntity(existingDeviceInfoEntity, cryptoDeviceInfo) + } + } } } } @@ -937,7 +949,7 @@ internal class RealmCryptoStore @Inject constructor( } } - override fun getDeviceKeysUploaded(): Boolean { + override fun areDeviceKeysUploaded(): Boolean { return doWithRealm(realmConfiguration) { it.where().findFirst()?.deviceKeysSentToServer } ?: false diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/RealmCryptoStoreMigration.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/RealmCryptoStoreMigration.kt index 8e77f5b823..2846be9932 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/RealmCryptoStoreMigration.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/RealmCryptoStoreMigration.kt @@ -55,7 +55,7 @@ internal object RealmCryptoStoreMigration : RealmMigration { // 0, 1, 2: legacy Riot-Android // 3: migrate to RiotX schema // 4, 5, 6, 7, 8, 9: migrations from RiotX (which was previously 1, 2, 3, 4, 5, 6) - const val CRYPTO_STORE_SCHEMA_VERSION = 12L + const val CRYPTO_STORE_SCHEMA_VERSION = 13L private fun RealmObjectSchema.addFieldIfNotExists(fieldName: String, fieldType: Class<*>): RealmObjectSchema { if (!hasField(fieldName)) { @@ -93,6 +93,7 @@ internal object RealmCryptoStoreMigration : RealmMigration { if (oldVersion <= 9) migrateTo10(realm) if (oldVersion <= 10) migrateTo11(realm) if (oldVersion <= 11) migrateTo12(realm) + if (oldVersion <= 12) migrateTo13(realm) } private fun migrateTo1Legacy(realm: DynamicRealm) { @@ -497,4 +498,60 @@ internal object RealmCryptoStoreMigration : RealmMigration { realm.schema.get("CryptoRoomEntity") ?.addRealmObjectField(CryptoRoomEntityFields.OUTBOUND_SESSION_INFO.`$`, outboundEntitySchema) } + + // Version 13L delete unreferenced TrustLevelEntity + private fun migrateTo13(realm: DynamicRealm) { + Timber.d("Step 12 -> 13") + + // Use a trick to do that... Ref: https://stackoverflow.com/questions/55221366 + val trustLevelEntitySchema = realm.schema.get("TrustLevelEntity") + + /* + Creating a new temp field called isLinked which is set to true for those which are + references by other objects. Rest of them are set to false. Then removing all + those which are false and hence duplicate and unnecessary. Then removing the temp field + isLinked + */ + var mainCounter = 0 + var deviceInfoCounter = 0 + var keyInfoCounter = 0 + val deleteCounter: Int + + trustLevelEntitySchema + ?.addField("isLinked", Boolean::class.java) + ?.transform { obj -> + // Setting to false for all by default + obj.set("isLinked", false) + mainCounter++ + } + + realm.schema.get("DeviceInfoEntity")?.transform { obj -> + // Setting to true for those which are referenced in DeviceInfoEntity + deviceInfoCounter++ + obj.getObject("trustLevelEntity")?.set("isLinked", true) + } + + realm.schema.get("KeyInfoEntity")?.transform { obj -> + // Setting to true for those which are referenced in KeyInfoEntity + keyInfoCounter++ + obj.getObject("trustLevelEntity")?.set("isLinked", true) + } + + // Removing all those which are set as false + realm.where("TrustLevelEntity") + .equalTo("isLinked", false) + .findAll() + .also { deleteCounter = it.size } + .deleteAllFromRealm() + + trustLevelEntitySchema?.removeField("isLinked") + + Timber.w("TrustLevelEntity cleanup: $mainCounter entities") + Timber.w("TrustLevelEntity cleanup: $deviceInfoCounter entities referenced in DeviceInfoEntities") + Timber.w("TrustLevelEntity cleanup: $keyInfoCounter entities referenced in KeyInfoEntity") + Timber.w("TrustLevelEntity cleanup: $deleteCounter entities deleted!") + if (mainCounter != deviceInfoCounter + keyInfoCounter + deleteCounter) { + Timber.e("TrustLevelEntity cleanup: Something is not correct...") + } + } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/model/CryptoMapper.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/model/CryptoMapper.kt index 37d1441690..7ba986699a 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/model/CryptoMapper.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/model/CryptoMapper.kt @@ -44,23 +44,32 @@ object CryptoMapper { )) internal fun mapToEntity(deviceInfo: CryptoDeviceInfo): DeviceInfoEntity { - return DeviceInfoEntity( - primaryKey = DeviceInfoEntity.createPrimaryKey(deviceInfo.userId, deviceInfo.deviceId), - userId = deviceInfo.userId, - deviceId = deviceInfo.deviceId, - algorithmListJson = listMigrationAdapter.toJson(deviceInfo.algorithms), - keysMapJson = mapMigrationAdapter.toJson(deviceInfo.keys), - signatureMapJson = mapMigrationAdapter.toJson(deviceInfo.signatures), - isBlocked = deviceInfo.isBlocked, - trustLevelEntity = deviceInfo.trustLevel?.let { - TrustLevelEntity( - crossSignedVerified = it.crossSigningVerified, - locallyVerified = it.locallyVerified - ) - }, - // We store the device name if present now - unsignedMapJson = deviceInfo.unsigned?.deviceDisplayName - ) + return DeviceInfoEntity(primaryKey = DeviceInfoEntity.createPrimaryKey(deviceInfo.userId, deviceInfo.deviceId)) + .also { updateDeviceInfoEntity(it, deviceInfo) } + } + + internal fun updateDeviceInfoEntity(entity: DeviceInfoEntity, deviceInfo: CryptoDeviceInfo) { + entity.userId = deviceInfo.userId + entity.deviceId = deviceInfo.deviceId + entity.algorithmListJson = listMigrationAdapter.toJson(deviceInfo.algorithms) + entity.keysMapJson = mapMigrationAdapter.toJson(deviceInfo.keys) + entity.signatureMapJson = mapMigrationAdapter.toJson(deviceInfo.signatures) + entity.isBlocked = deviceInfo.isBlocked + val deviceInfoTrustLevel = deviceInfo.trustLevel + if (deviceInfoTrustLevel == null) { + entity.trustLevelEntity?.deleteFromRealm() + entity.trustLevelEntity = null + } else { + if (entity.trustLevelEntity == null) { + // Create a new TrustLevelEntity object + entity.trustLevelEntity = TrustLevelEntity() + } + // Update the existing TrustLevelEntity object + entity.trustLevelEntity?.crossSignedVerified = deviceInfoTrustLevel.crossSigningVerified + entity.trustLevelEntity?.locallyVerified = deviceInfoTrustLevel.locallyVerified + } + // We store the device name if present now + entity.unsignedMapJson = deviceInfo.unsigned?.deviceDisplayName } internal fun mapToModel(deviceInfoEntity: DeviceInfoEntity): CryptoDeviceInfo { diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/verification/VerificationTransportToDevice.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/verification/VerificationTransportToDevice.kt index 0dbbe656c7..45f8143937 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/verification/VerificationTransportToDevice.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/verification/VerificationTransportToDevice.kt @@ -68,7 +68,7 @@ internal class VerificationTransportToDevice( contentMap.setObject(otherUserId, it, keyReq) } sendToDeviceTask - .configureWith(SendToDeviceTask.Params(MessageType.MSGTYPE_VERIFICATION_REQUEST, contentMap, localId)) { + .configureWith(SendToDeviceTask.Params(MessageType.MSGTYPE_VERIFICATION_REQUEST, contentMap)) { this.callback = object : MatrixCallback { override fun onSuccess(data: Unit) { Timber.v("## verification [$tx.transactionId] send toDevice request success") @@ -124,7 +124,7 @@ internal class VerificationTransportToDevice( contentMap.setObject(tx.otherUserId, tx.otherDeviceId, toSendToDeviceObject) sendToDeviceTask - .configureWith(SendToDeviceTask.Params(type, contentMap, tx.transactionId)) { + .configureWith(SendToDeviceTask.Params(type, contentMap)) { this.callback = object : MatrixCallback { override fun onSuccess(data: Unit) { Timber.v("## SAS verification [$tx.transactionId] toDevice type '$type' success.") @@ -155,7 +155,7 @@ internal class VerificationTransportToDevice( val contentMap = MXUsersDevicesMap() contentMap.setObject(otherUserId, otherUserDeviceId, cancelMessage) sendToDeviceTask - .configureWith(SendToDeviceTask.Params(EventType.KEY_VERIFICATION_DONE, contentMap, transactionId)) { + .configureWith(SendToDeviceTask.Params(EventType.KEY_VERIFICATION_DONE, contentMap)) { this.callback = object : MatrixCallback { override fun onSuccess(data: Unit) { onDone?.invoke() @@ -176,7 +176,7 @@ internal class VerificationTransportToDevice( val contentMap = MXUsersDevicesMap() contentMap.setObject(otherUserId, otherUserDeviceId, cancelMessage) sendToDeviceTask - .configureWith(SendToDeviceTask.Params(EventType.KEY_VERIFICATION_CANCEL, contentMap, transactionId)) { + .configureWith(SendToDeviceTask.Params(EventType.KEY_VERIFICATION_CANCEL, contentMap)) { this.callback = object : MatrixCallback { override fun onSuccess(data: Unit) { Timber.v("## SAS verification [$transactionId] canceled for reason ${code.value}") diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/mapper/HomeServerCapabilitiesMapper.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/mapper/HomeServerCapabilitiesMapper.kt index 2575cdef26..8b6d263f8c 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/mapper/HomeServerCapabilitiesMapper.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/mapper/HomeServerCapabilitiesMapper.kt @@ -18,6 +18,7 @@ package org.matrix.android.sdk.internal.database.mapper import org.matrix.android.sdk.api.extensions.tryOrNull import org.matrix.android.sdk.api.session.homeserver.HomeServerCapabilities +import org.matrix.android.sdk.api.session.homeserver.RoomCapabilitySupport import org.matrix.android.sdk.api.session.homeserver.RoomVersionCapabilities import org.matrix.android.sdk.api.session.homeserver.RoomVersionInfo import org.matrix.android.sdk.api.session.homeserver.RoomVersionStatus @@ -45,19 +46,28 @@ internal object HomeServerCapabilitiesMapper { roomVersionsJson ?: return null return tryOrNull { - MoshiProvider.providesMoshi().adapter(RoomVersions::class.java).fromJson(roomVersionsJson)?.let { + MoshiProvider.providesMoshi().adapter(RoomVersions::class.java).fromJson(roomVersionsJson)?.let { roomVersions -> RoomVersionCapabilities( - defaultRoomVersion = it.default ?: DefaultRoomVersionService.DEFAULT_ROOM_VERSION, - supportedVersion = it.available.entries.map { entry -> - RoomVersionInfo( - version = entry.key, - status = if (entry.value == "stable") { - RoomVersionStatus.STABLE - } else { - RoomVersionStatus.UNSTABLE - } - ) - } + defaultRoomVersion = roomVersions.default ?: DefaultRoomVersionService.DEFAULT_ROOM_VERSION, + supportedVersion = roomVersions.available?.entries?.map { entry -> + RoomVersionInfo(entry.key, RoomVersionStatus.STABLE + .takeIf { entry.value == "stable" } + ?: RoomVersionStatus.UNSTABLE) + }.orEmpty(), + capabilities = roomVersions.roomCapabilities?.entries?.mapNotNull { entry -> + (entry.value as? Map<*, *>)?.let { + val preferred = it["preferred"] as? String ?: return@mapNotNull null + val support = (it["support"] as? List<*>)?.filterIsInstance() + entry.key to RoomCapabilitySupport(preferred, support.orEmpty()) + } + }?.toMap() + // Just for debug purpose +// ?: mapOf( +// HomeServerCapabilities.ROOM_CAP_RESTRICTED to RoomCapabilitySupport( +// preferred = null, +// support = listOf("org.matrix.msc3083") +// ) +// ) ) } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/DefaultFileService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/DefaultFileService.kt index a284d976d0..414c018074 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/DefaultFileService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/DefaultFileService.kt @@ -25,6 +25,7 @@ import kotlinx.coroutines.completeWith import kotlinx.coroutines.withContext import okhttp3.OkHttpClient import okhttp3.Request +import org.matrix.android.sdk.api.failure.Failure import org.matrix.android.sdk.api.session.content.ContentUrlResolver import org.matrix.android.sdk.api.session.file.FileService import org.matrix.android.sdk.internal.crypto.attachments.ElementToDecrypt @@ -33,6 +34,7 @@ import org.matrix.android.sdk.internal.di.SessionDownloadsDirectory import org.matrix.android.sdk.internal.di.UnauthenticatedWithCertificateWithProgress import org.matrix.android.sdk.internal.session.download.DownloadProgressInterceptor.Companion.DOWNLOAD_PROGRESS_INTERCEPTOR_HEADER import org.matrix.android.sdk.internal.util.MatrixCoroutineDispatchers +import org.matrix.android.sdk.internal.util.file.AtomicFileCreator import org.matrix.android.sdk.internal.util.md5 import org.matrix.android.sdk.internal.util.writeToFile import timber.log.Timber @@ -96,6 +98,9 @@ internal class DefaultFileService @Inject constructor( } } + var atomicFileDownload: AtomicFileCreator? = null + var atomicFileDecrypt: AtomicFileCreator? = null + if (existingDownload != null) { // FIXME If the first downloader cancels then we'll unfortunately be cancelled too. return existingDownload.await() @@ -120,19 +125,30 @@ internal class DefaultFileService @Inject constructor( .header(DOWNLOAD_PROGRESS_INTERCEPTOR_HEADER, url) .build() - val response = okHttpClient.newCall(request).execute() + val response = try { + okHttpClient.newCall(request).execute() + } catch (failure: Throwable) { + throw if (failure is IOException) { + Failure.NetworkConnection(failure) + } else { + failure + } + } if (!response.isSuccessful) { - throw IOException() + throw Failure.NetworkConnection(IOException()) } - val source = response.body?.source() ?: throw IOException() + val source = response.body?.source() ?: throw Failure.NetworkConnection(IOException()) Timber.v("Response size ${response.body?.contentLength()} - Stream available: ${!source.exhausted()}") // Write the file to cache (encrypted version if the file is encrypted) - writeToFile(source.inputStream(), cachedFiles.file) + // Write to a part file first, so if we abort before done, we don't have a broken cached file + val atomicFileCreator = AtomicFileCreator(cachedFiles.file).also { atomicFileDownload = it } + writeToFile(source.inputStream(), atomicFileCreator.partFile) response.close() + atomicFileCreator.commit() } else { Timber.v("## FileService: cache hit for $url") } @@ -145,8 +161,10 @@ internal class DefaultFileService @Inject constructor( Timber.v("## FileService: decrypt file") // Ensure the parent folder exists cachedFiles.decryptedFile.parentFile?.mkdirs() + // Write to a part file first, so if we abort before done, we don't have a broken cached file + val atomicFileCreator = AtomicFileCreator(cachedFiles.decryptedFile).also { atomicFileDecrypt = it } val decryptSuccess = cachedFiles.file.inputStream().use { inputStream -> - cachedFiles.decryptedFile.outputStream().buffered().use { outputStream -> + atomicFileCreator.partFile.outputStream().buffered().use { outputStream -> MXEncryptedAttachments.decryptAttachment( inputStream, elementToDecrypt, @@ -154,6 +172,7 @@ internal class DefaultFileService @Inject constructor( ) } } + atomicFileCreator.commit() if (!decryptSuccess) { throw IllegalStateException("Decryption error") } @@ -174,6 +193,11 @@ internal class DefaultFileService @Inject constructor( } toNotify?.completeWith(result) + result.onFailure { + atomicFileDownload?.cancel() + atomicFileDecrypt?.cancel() + } + return result.getOrThrow() } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/call/CallEventProcessor.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/call/CallEventProcessor.kt index 473adeb8d2..bdc254fc99 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/call/CallEventProcessor.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/call/CallEventProcessor.kt @@ -20,11 +20,14 @@ import io.realm.Realm import org.matrix.android.sdk.api.session.events.model.Event import org.matrix.android.sdk.api.session.events.model.EventType import org.matrix.android.sdk.internal.database.model.EventInsertType +import org.matrix.android.sdk.api.logger.LoggerTag import org.matrix.android.sdk.internal.session.EventInsertLiveProcessor import org.matrix.android.sdk.internal.session.SessionScope import timber.log.Timber import javax.inject.Inject +private val loggerTag = LoggerTag("CallEventProcessor", LoggerTag.VOIP) + @SessionScope internal class CallEventProcessor @Inject constructor(private val callSignalingHandler: CallSignalingHandler) : EventInsertLiveProcessor { @@ -71,14 +74,8 @@ internal class CallEventProcessor @Inject constructor(private val callSignalingH } private fun dispatchToCallSignalingHandlerIfNeeded(event: Event) { - val now = System.currentTimeMillis() event.roomId ?: return Unit.also { - Timber.w("Event with no room id ${event.eventId}") - } - val age = now - (event.ageLocalTs ?: now) - if (age > 40_000) { - // Too old to ring? - return + Timber.tag(loggerTag.value).w("Event with no room id ${event.eventId}") } callSignalingHandler.onCallEvent(event) } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/call/CallSignalingHandler.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/call/CallSignalingHandler.kt index b0901af719..59058bf976 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/call/CallSignalingHandler.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/call/CallSignalingHandler.kt @@ -16,6 +16,7 @@ package org.matrix.android.sdk.internal.session.call +import org.matrix.android.sdk.api.logger.LoggerTag import org.matrix.android.sdk.api.session.call.CallListener import org.matrix.android.sdk.api.session.call.CallState import org.matrix.android.sdk.api.session.call.MxCall @@ -36,6 +37,9 @@ import org.matrix.android.sdk.internal.session.SessionScope import timber.log.Timber import javax.inject.Inject +private val loggerTag = LoggerTag("CallSignalingHandler", LoggerTag.VOIP) +private const val MAX_AGE_TO_RING = 40_000 + @SessionScope internal class CallSignalingHandler @Inject constructor(private val activeCallHandler: ActiveCallHandler, private val mxCallFactory: MxCallFactory, @@ -111,12 +115,12 @@ internal class CallSignalingHandler @Inject constructor(private val activeCallHa return } if (call.isOutgoing) { - Timber.v("Got selectAnswer for an outbound call: ignoring") + Timber.tag(loggerTag.value).v("Got selectAnswer for an outbound call: ignoring") return } val selectedPartyId = content.selectedPartyId if (selectedPartyId == null) { - Timber.w("Got nonsensical select_answer with null selected_party_id: ignoring") + Timber.tag(loggerTag.value).w("Got nonsensical select_answer with null selected_party_id: ignoring") return } callListenersDispatcher.onCallSelectAnswerReceived(content) @@ -130,7 +134,7 @@ internal class CallSignalingHandler @Inject constructor(private val activeCallHa return } if (call.opponentPartyId != null && !call.partyIdsMatches(content)) { - Timber.v("Ignoring candidates from party ID ${content.partyId} we have chosen party ID ${call.opponentPartyId}") + Timber.tag(loggerTag.value).v("Ignoring candidates from party ID ${content.partyId} we have chosen party ID ${call.opponentPartyId}") return } callListenersDispatcher.onCallIceCandidateReceived(call, content) @@ -163,10 +167,10 @@ internal class CallSignalingHandler @Inject constructor(private val activeCallHa // party ID must match (our chosen partner hanging up the call) or be undefined (we haven't chosen // a partner yet but we're treating the hangup as a reject as per VoIP v0) if (call.opponentPartyId != null && !call.partyIdsMatches(content)) { - Timber.v("Ignoring hangup from party ID ${content.partyId} we have chosen party ID ${call.opponentPartyId}") + Timber.tag(loggerTag.value).v("Ignoring hangup from party ID ${content.partyId} we have chosen party ID ${call.opponentPartyId}") return } - if (call.state != CallState.Terminated) { + if (call.state !is CallState.Ended) { activeCallHandler.removeCall(content.callId) callListenersDispatcher.onCallHangupReceived(content) } @@ -180,12 +184,18 @@ internal class CallSignalingHandler @Inject constructor(private val activeCallHa if (event.roomId == null || event.senderId == null) { return } + val now = System.currentTimeMillis() + val age = now - (event.ageLocalTs ?: now) + if (age > MAX_AGE_TO_RING) { + Timber.tag(loggerTag.value).w("Call invite is too old to ring.") + return + } val content = event.getClearContent().toModel() ?: return content.callId ?: return if (invitedCallIds.contains(content.callId)) { // Call is already known, maybe due to fast lane. Ignore - Timber.d("Ignoring already known call invite") + Timber.tag(loggerTag.value).d("Ignoring already known call invite") return } val incomingCall = mxCallFactory.createIncomingCall( @@ -214,7 +224,8 @@ internal class CallSignalingHandler @Inject constructor(private val activeCallHa callListenersDispatcher.onCallManagedByOtherSession(content.callId) } else { if (call.opponentPartyId != null) { - Timber.v("Ignoring answer from party ID ${content.partyId} we already have an answer from ${call.opponentPartyId}") + Timber.tag(loggerTag.value) + .v("Ignoring answer from party ID ${content.partyId} we already have an answer from ${call.opponentPartyId}") return } mxCallFactory.updateOutgoingCallWithOpponentData(call, event.senderId, content, content.capabilities) @@ -231,7 +242,7 @@ internal class CallSignalingHandler @Inject constructor(private val activeCallHa activeCallHandler.getCallWithId(it) } if (currentCall == null) { - Timber.v("Call with id $callId is null") + Timber.tag(loggerTag.value).v("Call with id $callId is null") } return currentCall } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/call/DefaultCallSignalingService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/call/DefaultCallSignalingService.kt index da1f84cc89..4a949e21a6 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/call/DefaultCallSignalingService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/call/DefaultCallSignalingService.kt @@ -21,7 +21,6 @@ import org.matrix.android.sdk.api.session.call.CallSignalingService import org.matrix.android.sdk.api.session.call.MxCall import org.matrix.android.sdk.api.session.call.TurnServerResponse import org.matrix.android.sdk.internal.session.SessionScope -import timber.log.Timber import javax.inject.Inject @SessionScope @@ -51,7 +50,6 @@ internal class DefaultCallSignalingService @Inject constructor( } override fun getCallWithId(callId: String): MxCall? { - Timber.v("## VOIP getCallWithId $callId all calls ${activeCallHandler.getActiveCallsLiveData().value?.map { it.callId }}") return activeCallHandler.getCallWithId(callId) } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/call/model/MxCallImpl.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/call/model/MxCallImpl.kt index f101685a4b..9fc84e6fe5 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/call/model/MxCallImpl.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/call/model/MxCallImpl.kt @@ -17,6 +17,7 @@ package org.matrix.android.sdk.internal.session.call.model import org.matrix.android.sdk.api.MatrixConfiguration +import org.matrix.android.sdk.api.logger.LoggerTag import org.matrix.android.sdk.api.session.call.CallIdGenerator import org.matrix.android.sdk.api.session.call.CallState import org.matrix.android.sdk.api.session.call.MxCall @@ -38,6 +39,7 @@ import org.matrix.android.sdk.api.session.room.model.call.CallRejectContent import org.matrix.android.sdk.api.session.room.model.call.CallReplacesContent import org.matrix.android.sdk.api.session.room.model.call.CallSelectAnswerContent import org.matrix.android.sdk.api.session.room.model.call.CallSignalingContent +import org.matrix.android.sdk.api.session.room.model.call.EndCallReason import org.matrix.android.sdk.api.session.room.model.call.SdpType import org.matrix.android.sdk.api.util.Optional import org.matrix.android.sdk.internal.session.call.DefaultCallSignalingService @@ -47,6 +49,8 @@ import org.matrix.android.sdk.internal.session.room.send.queue.EventSenderProces import timber.log.Timber import java.math.BigDecimal +private val loggerTag = LoggerTag("MxCallImpl", LoggerTag.VOIP) + internal class MxCallImpl( override val callId: String, override val isOutgoing: Boolean, @@ -93,7 +97,7 @@ internal class MxCallImpl( try { it.onStateUpdate(this) } catch (failure: Throwable) { - Timber.d("dispatchStateChange failed for call $callId : ${failure.localizedMessage}") + Timber.tag(loggerTag.value).d("dispatchStateChange failed for call $callId : ${failure.localizedMessage}") } } } @@ -109,7 +113,7 @@ internal class MxCallImpl( override fun offerSdp(sdpString: String) { if (!isOutgoing) return - Timber.v("## VOIP offerSdp $callId") + Timber.tag(loggerTag.value).v("offerSdp $callId") state = CallState.Dialing CallInviteContent( callId = callId, @@ -124,7 +128,7 @@ internal class MxCallImpl( } override fun sendLocalCallCandidates(candidates: List) { - Timber.v("Send local call canditates $callId: $candidates") + Timber.tag(loggerTag.value).v("Send local call canditates $callId: $candidates") CallCandidatesContent( callId = callId, partyId = ourPartyId, @@ -141,11 +145,11 @@ internal class MxCallImpl( override fun reject() { if (opponentVersion < 1) { - Timber.v("Opponent version is less than 1 ($opponentVersion): sending hangup instead of reject") - hangUp() + Timber.tag(loggerTag.value).v("Opponent version is less than 1 ($opponentVersion): sending hangup instead of reject") + hangUp(EndCallReason.USER_HANGUP) return } - Timber.v("## VOIP reject $callId") + Timber.tag(loggerTag.value).v("reject $callId") CallRejectContent( callId = callId, partyId = ourPartyId, @@ -153,24 +157,24 @@ internal class MxCallImpl( ) .let { createEventAndLocalEcho(type = EventType.CALL_REJECT, roomId = roomId, content = it.toContent()) } .also { eventSenderProcessor.postEvent(it) } - state = CallState.Terminated + state = CallState.Ended(reason = EndCallReason.USER_HANGUP) } - override fun hangUp(reason: CallHangupContent.Reason?) { - Timber.v("## VOIP hangup $callId") + override fun hangUp(reason: EndCallReason?) { + Timber.tag(loggerTag.value).v("hangup $callId") CallHangupContent( callId = callId, partyId = ourPartyId, - reason = reason ?: CallHangupContent.Reason.USER_HANGUP, + reason = reason, version = MxCall.VOIP_PROTO_VERSION.toString() ) .let { createEventAndLocalEcho(type = EventType.CALL_HANGUP, roomId = roomId, content = it.toContent()) } .also { eventSenderProcessor.postEvent(it) } - state = CallState.Terminated + state = CallState.Ended(reason) } override fun accept(sdpString: String) { - Timber.v("## VOIP accept $callId") + Timber.tag(loggerTag.value).v("accept $callId") if (isOutgoing) return state = CallState.Answering CallAnswerContent( @@ -185,7 +189,7 @@ internal class MxCallImpl( } override fun negotiate(sdpString: String, type: SdpType) { - Timber.v("## VOIP negotiate $callId") + Timber.tag(loggerTag.value).v("negotiate $callId") CallNegotiateContent( callId = callId, partyId = ourPartyId, @@ -198,7 +202,7 @@ internal class MxCallImpl( } override fun selectAnswer() { - Timber.v("## VOIP select answer $callId") + Timber.tag(loggerTag.value).v("select answer $callId") if (isOutgoing) return state = CallState.Answering CallSelectAnswerContent( @@ -219,7 +223,7 @@ internal class MxCallImpl( val profileInfo = try { getProfileInfoTask.execute(profileInfoParams) } catch (failure: Throwable) { - Timber.v("Fail fetching profile info of $targetUserId while transferring call") + Timber.tag(loggerTag.value).v("Fail fetching profile info of $targetUserId while transferring call") null } CallReplacesContent( diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/homeserver/GetCapabilitiesResult.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/homeserver/GetCapabilitiesResult.kt index c4bc09a233..1fe4f9d90a 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 @@ -70,7 +70,22 @@ internal data class RoomVersions( * Required. A detailed description of the room versions the server supports. */ @Json(name = "available") - val available: JsonDict + val available: JsonDict? = null, + + /** + * "room_capabilities": { + * "knock" : { + * "preferred": "7", + * "support" : ["7"] + * }, + * "restricted" : { + * "preferred": "9", + * "support" : ["8", "9"] + * } + * } + */ + @Json(name = "room_capabilities") + val roomCapabilities: JsonDict? = null ) // The spec says: If not present, the client should assume that password changes are possible via the API 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 38f6b08b43..4e8abcf784 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/notification/DefaultPushRuleService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/notification/DefaultPushRuleService.kt @@ -113,8 +113,8 @@ internal class DefaultPushRuleService @Inject constructor( addPushRuleTask.execute(AddPushRuleTask.Params(kind, pushRule)) } - override suspend fun updatePushRuleActions(kind: RuleKind, oldPushRule: PushRule, newPushRule: PushRule) { - updatePushRuleActionsTask.execute(UpdatePushRuleActionsTask.Params(kind, oldPushRule, newPushRule)) + override suspend fun updatePushRuleActions(kind: RuleKind, ruleId: String, enable: Boolean, actions: List?) { + updatePushRuleActionsTask.execute(UpdatePushRuleActionsTask.Params(kind, ruleId, enable, actions)) } override suspend fun removePushRule(kind: RuleKind, pushRule: PushRule) { diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/permalinks/ViaParameterFinder.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/permalinks/ViaParameterFinder.kt index 82565d8118..21f55bbc42 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 @@ -17,17 +17,24 @@ package org.matrix.android.sdk.internal.session.permalinks import org.matrix.android.sdk.api.MatrixPatterns.getDomain +import org.matrix.android.sdk.api.query.QueryStringValue +import org.matrix.android.sdk.api.session.events.model.EventType +import org.matrix.android.sdk.api.session.events.model.toModel import org.matrix.android.sdk.api.session.room.members.roomMemberQueryParams import org.matrix.android.sdk.api.session.room.model.Membership +import org.matrix.android.sdk.api.session.room.model.PowerLevelsContent +import org.matrix.android.sdk.api.session.room.powerlevels.PowerLevelsHelper import org.matrix.android.sdk.internal.di.UserId import org.matrix.android.sdk.internal.session.room.RoomGetter +import org.matrix.android.sdk.internal.session.room.state.StateEventDataSource import java.net.URLEncoder import javax.inject.Inject import javax.inject.Provider internal class ViaParameterFinder @Inject constructor( @UserId private val userId: String, - private val roomGetterProvider: Provider + private val roomGetterProvider: Provider, + private val stateEventDataSource: StateEventDataSource ) { fun computeViaParams(roomId: String, max: Int): List { @@ -70,4 +77,28 @@ internal class ViaParameterFinder @Inject constructor( .orEmpty() .toSet() } + + fun computeViaParamsForRestricted(roomId: String, max: Int): List { + val userThatCanInvite = roomGetterProvider.get().getRoom(roomId) + ?.getRoomMembers(roomMemberQueryParams { memberships = listOf(Membership.JOIN) }) + ?.map { it.userId } + ?.filter { userCanInvite(userId, roomId) } + .orEmpty() + .toSet() + + return userThatCanInvite.map { it.getDomain() } + .groupBy { it } + .mapValues { it.value.size } + .toMutableMap() + .let { map -> map.keys.sortedByDescending { map[it] } } + .take(max) + } + + fun userCanInvite(userId: String, roomId: String): Boolean { + val powerLevelsHelper = stateEventDataSource.getStateEvent(roomId, EventType.STATE_ROOM_POWER_LEVELS, QueryStringValue.NoCondition) + ?.content?.toModel() + ?.let { PowerLevelsHelper(it) } + + return powerLevelsHelper?.isUserAbleToInvite(userId) ?: false + } } 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 2a24aee892..b8dbabd09e 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/UpdatePushRuleActionsTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/UpdatePushRuleActionsTask.kt @@ -15,8 +15,9 @@ */ package org.matrix.android.sdk.internal.session.pushers +import org.matrix.android.sdk.api.pushrules.Action import org.matrix.android.sdk.api.pushrules.RuleKind -import org.matrix.android.sdk.api.pushrules.rest.PushRule +import org.matrix.android.sdk.api.pushrules.toJson import org.matrix.android.sdk.internal.network.GlobalErrorReceiver import org.matrix.android.sdk.internal.network.executeRequest import org.matrix.android.sdk.internal.task.Task @@ -25,8 +26,9 @@ import javax.inject.Inject internal interface UpdatePushRuleActionsTask : Task { data class Params( val kind: RuleKind, - val oldPushRule: PushRule, - val newPushRule: PushRule + val ruleId: String, + val enable: Boolean, + val actions: List? ) } @@ -36,20 +38,14 @@ internal class DefaultUpdatePushRuleActionsTask @Inject constructor( ) : UpdatePushRuleActionsTask { override suspend fun execute(params: UpdatePushRuleActionsTask.Params) { - if (params.oldPushRule.enabled != params.newPushRule.enabled) { - // First change enabled state executeRequest(globalErrorReceiver) { - pushRulesApi.updateEnableRuleStatus(params.kind.value, params.newPushRule.ruleId, params.newPushRule.enabled) + pushRulesApi.updateEnableRuleStatus(params.kind.value, params.ruleId, enable = params.enable) } - } - - if (params.newPushRule.enabled) { - // Also ensure the actions are up to date - val body = mapOf("actions" to params.newPushRule.actions) - - executeRequest(globalErrorReceiver) { - pushRulesApi.updateRuleActions(params.kind.value, params.newPushRule.ruleId, body) + if (params.actions != null) { + val body = mapOf("actions" to params.actions.toJson()) + executeRequest(globalErrorReceiver) { + pushRulesApi.updateRuleActions(params.kind.value, params.ruleId, body) + } } - } } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/create/CreateRoomBodyBuilder.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/create/CreateRoomBodyBuilder.kt index 86d2b70da1..9bb3899f2f 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/create/CreateRoomBodyBuilder.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/create/CreateRoomBodyBuilder.kt @@ -17,16 +17,10 @@ package org.matrix.android.sdk.internal.session.room.create import org.matrix.android.sdk.api.extensions.tryOrNull -import org.matrix.android.sdk.api.session.crypto.crosssigning.CrossSigningService import org.matrix.android.sdk.api.session.events.model.Event import org.matrix.android.sdk.api.session.events.model.EventType -import org.matrix.android.sdk.api.session.events.model.toContent import org.matrix.android.sdk.api.session.identity.IdentityServiceError import org.matrix.android.sdk.api.session.identity.toMedium -import org.matrix.android.sdk.api.session.room.model.GuestAccess -import org.matrix.android.sdk.api.session.room.model.RoomHistoryVisibility -import org.matrix.android.sdk.api.session.room.model.RoomJoinRules -import org.matrix.android.sdk.api.session.room.model.RoomJoinRulesContent import org.matrix.android.sdk.api.session.room.model.create.CreateRoomParams import org.matrix.android.sdk.api.util.MimeTypes import org.matrix.android.sdk.internal.crypto.DeviceListManager @@ -45,7 +39,6 @@ import javax.inject.Inject internal class CreateRoomBodyBuilder @Inject constructor( private val ensureIdentityTokenTask: EnsureIdentityTokenTask, - private val crossSigningService: CrossSigningService, private val deviceListManager: DeviceListManager, private val identityStore: IdentityStore, private val fileUploader: FileUploader, @@ -76,18 +69,18 @@ internal class CreateRoomBodyBuilder @Inject constructor( } } - if (params.joinRuleRestricted != null) { - params.roomVersion = "org.matrix.msc3083" - params.historyVisibility = params.historyVisibility ?: RoomHistoryVisibility.SHARED - params.guestAccess = params.guestAccess ?: GuestAccess.Forbidden - } - val initialStates = listOfNotNull( - buildEncryptionWithAlgorithmEvent(params), - buildHistoryVisibilityEvent(params), - buildAvatarEvent(params), - buildGuestAccess(params), - buildJoinRulesRestricted(params) - ) + params.featurePreset?.updateRoomParams(params) + + val initialStates = ( + listOfNotNull( + buildEncryptionWithAlgorithmEvent(params), + buildHistoryVisibilityEvent(params), + buildAvatarEvent(params), + buildGuestAccess(params) + ) + + params.featurePreset?.setupInitialStates().orEmpty() + + buildCustomInitialStates(params) + ) .takeIf { it.isNotEmpty() } return CreateRoomBody( @@ -95,7 +88,7 @@ internal class CreateRoomBodyBuilder @Inject constructor( roomAliasName = params.roomAliasName, name = params.name, topic = params.topic, - invitedUserIds = params.invitedUserIds.filter { it != userId }, + invitedUserIds = params.invitedUserIds.filter { it != userId }.takeIf { it.isNotEmpty() }, invite3pids = invite3pids, creationContent = params.creationContent.takeIf { it.isNotEmpty() }, initialStates = initialStates, @@ -103,10 +96,19 @@ internal class CreateRoomBodyBuilder @Inject constructor( isDirect = params.isDirect, powerLevelContentOverride = params.powerLevelContentOverride, roomVersion = params.roomVersion - ) } + private fun buildCustomInitialStates(params: CreateRoomParams): List { + return params.initialStates.map { + Event( + type = it.type, + stateKey = it.stateKey, + content = it.content + ) + } + } + private suspend fun buildAvatarEvent(params: CreateRoomParams): Event? { return params.avatarUri?.let { avatarUri -> // First upload the image, ignoring any error @@ -148,20 +150,6 @@ internal class CreateRoomBodyBuilder @Inject constructor( } } - private fun buildJoinRulesRestricted(params: CreateRoomParams): Event? { - return params.joinRuleRestricted - ?.let { allowList -> - Event( - type = EventType.STATE_ROOM_JOIN_RULES, - stateKey = "", - content = RoomJoinRulesContent( - _joinRules = RoomJoinRules.RESTRICTED.value, - allowList = allowList - ).toContent() - ) - } - } - /** * Add the crypto algorithm to the room creation parameters. */ diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/DefaultSendService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/DefaultSendService.kt index 092fec4d72..a64b903947 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 @@ -184,7 +184,8 @@ internal class DefaultSendService @AssistedInject constructor( mimeType = messageContent.mimeType, name = messageContent.body, queryUri = Uri.parse(messageContent.url), - type = ContentAttachmentData.Type.AUDIO + type = ContentAttachmentData.Type.AUDIO, + waveform = messageContent.audioWaveformInfo?.waveform ) localEchoRepository.updateSendState(localEcho.eventId, roomId, SendState.UNSENT) internalSendMedia(listOf(localEcho.root), attachmentData, true) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/LocalEchoEventFactory.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/LocalEchoEventFactory.kt index f505b13b33..c610326a94 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/LocalEchoEventFactory.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/LocalEchoEventFactory.kt @@ -29,6 +29,7 @@ import org.matrix.android.sdk.api.session.events.model.RelationType import org.matrix.android.sdk.api.session.events.model.UnsignedData import org.matrix.android.sdk.api.session.events.model.toContent import org.matrix.android.sdk.api.session.room.model.message.AudioInfo +import org.matrix.android.sdk.api.session.room.model.message.AudioWaveformInfo import org.matrix.android.sdk.api.session.room.model.message.FileInfo import org.matrix.android.sdk.api.session.room.model.message.ImageInfo import org.matrix.android.sdk.api.session.room.model.message.MessageAudioContent @@ -74,6 +75,7 @@ internal class LocalEchoEventFactory @Inject constructor( private val markdownParser: MarkdownParser, private val textPillsUtils: TextPillsUtils, private val thumbnailExtractor: ThumbnailExtractor, + private val waveformSanitizer: WaveFormSanitizer, private val localEchoRepository: LocalEchoRepository, private val permalinkFactory: PermalinkFactory ) { @@ -289,14 +291,21 @@ internal class LocalEchoEventFactory @Inject constructor( } private fun createAudioEvent(roomId: String, attachment: ContentAttachmentData): Event { + val isVoiceMessage = attachment.waveform != null val content = MessageAudioContent( msgType = MessageType.MSGTYPE_AUDIO, body = attachment.name ?: "audio", audioInfo = AudioInfo( + duration = attachment.duration?.toInt(), mimeType = attachment.getSafeMimeType()?.takeIf { it.isNotBlank() }, size = attachment.size ), - url = attachment.queryUri.toString() + url = attachment.queryUri.toString(), + audioWaveformInfo = if (!isVoiceMessage) null else AudioWaveformInfo( + duration = attachment.duration?.toInt(), + waveform = waveformSanitizer.sanitize(attachment.waveform) + ), + voiceMessageIndicator = if (!isVoiceMessage) null else emptyMap() ) return createMessageEvent(roomId, content) } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/WaveFormSanitizer.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/WaveFormSanitizer.kt new file mode 100644 index 0000000000..78a03f3775 --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/WaveFormSanitizer.kt @@ -0,0 +1,86 @@ +/* + * Copyright (c) 2021 The Matrix.org Foundation C.I.C. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.matrix.android.sdk.internal.session.room.send + +import timber.log.Timber +import javax.inject.Inject +import kotlin.math.abs +import kotlin.math.ceil + +internal class WaveFormSanitizer @Inject constructor() { + private companion object { + const val MIN_NUMBER_OF_VALUES = 30 + const val MAX_NUMBER_OF_VALUES = 120 + + const val MAX_VALUE = 1024 + } + + /** + * The array should have no less than 30 elements and no more than 120. + * List of integers between zero and 1024, inclusive. + */ + fun sanitize(waveForm: List?): List? { + if (waveForm.isNullOrEmpty()) { + return null + } + + // Limit the number of items + val sizeInRangeList = mutableListOf() + when { + waveForm.size < MIN_NUMBER_OF_VALUES -> { + // Repeat the same value to have at least 30 items + val repeatTimes = ceil(MIN_NUMBER_OF_VALUES / waveForm.size.toDouble()).toInt() + waveForm.map { value -> + repeat(repeatTimes) { + sizeInRangeList.add(value) + } + } + } + waveForm.size > MAX_NUMBER_OF_VALUES -> { + val keepOneOf = ceil(waveForm.size.toDouble() / MAX_NUMBER_OF_VALUES).toInt() + waveForm.mapIndexed { idx, value -> + if (idx % keepOneOf == 0) { + sizeInRangeList.add(value) + } + } + } + else -> { + sizeInRangeList.addAll(waveForm) + } + } + + // OK, ensure all items are positive + val positiveList = sizeInRangeList.map { + abs(it) + } + + // Ensure max is not above MAX_VALUE + val max = positiveList.maxOrNull() ?: MAX_VALUE + + val finalList = if (max > MAX_VALUE) { + // Reduce the values + positiveList.map { + it * MAX_VALUE / max + } + } else { + positiveList + } + + Timber.d("Sanitize from ${waveForm.size} items to ${finalList.size} items. Max value was $max") + return finalList + } +} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/state/DefaultStateService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/state/DefaultStateService.kt index ff2afb5d61..7eed22f65f 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/state/DefaultStateService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/state/DefaultStateService.kt @@ -19,8 +19,8 @@ package org.matrix.android.sdk.internal.session.room.state import android.net.Uri import androidx.lifecycle.LiveData import dagger.assisted.Assisted -import dagger.assisted.AssistedInject import dagger.assisted.AssistedFactory +import dagger.assisted.AssistedInject import org.matrix.android.sdk.api.query.QueryStringValue import org.matrix.android.sdk.api.session.events.model.Event import org.matrix.android.sdk.api.session.events.model.EventType @@ -29,17 +29,20 @@ import org.matrix.android.sdk.api.session.room.model.GuestAccess import org.matrix.android.sdk.api.session.room.model.RoomCanonicalAliasContent import org.matrix.android.sdk.api.session.room.model.RoomHistoryVisibility import org.matrix.android.sdk.api.session.room.model.RoomJoinRules +import org.matrix.android.sdk.api.session.room.model.RoomJoinRulesAllowEntry +import org.matrix.android.sdk.api.session.room.model.RoomJoinRulesContent import org.matrix.android.sdk.api.session.room.state.StateService import org.matrix.android.sdk.api.util.JsonDict import org.matrix.android.sdk.api.util.MimeTypes import org.matrix.android.sdk.api.util.Optional import org.matrix.android.sdk.internal.session.content.FileUploader -import java.lang.UnsupportedOperationException +import org.matrix.android.sdk.internal.session.permalinks.ViaParameterFinder internal class DefaultStateService @AssistedInject constructor(@Assisted private val roomId: String, private val stateEventDataSource: StateEventDataSource, private val sendStateTask: SendStateTask, - private val fileUploader: FileUploader + private val fileUploader: FileUploader, + private val viaParameterFinder: ViaParameterFinder ) : StateService { @AssistedFactory @@ -126,12 +129,19 @@ internal class DefaultStateService @AssistedInject constructor(@Assisted private ) } - override suspend fun updateJoinRule(joinRules: RoomJoinRules?, guestAccess: GuestAccess?) { + override suspend fun updateJoinRule(joinRules: RoomJoinRules?, guestAccess: GuestAccess?, allowList: List?) { if (joinRules != null) { - if (joinRules == RoomJoinRules.RESTRICTED) throw UnsupportedOperationException("No yet supported") + val body = if (joinRules == RoomJoinRules.RESTRICTED) { + RoomJoinRulesContent( + _joinRules = RoomJoinRules.RESTRICTED.value, + allowList = allowList + ).toContent() + } else { + mapOf("join_rule" to joinRules) + } sendStateEvent( eventType = EventType.STATE_ROOM_JOIN_RULES, - body = mapOf("join_rule" to joinRules), + body = body, stateKey = null ) } @@ -160,4 +170,20 @@ internal class DefaultStateService @AssistedInject constructor(@Assisted private stateKey = null ) } + + override suspend fun setJoinRulePublic() { + updateJoinRule(RoomJoinRules.PUBLIC, null) + } + + override suspend fun setJoinRuleInviteOnly() { + updateJoinRule(RoomJoinRules.INVITE, null) + } + + override suspend fun setJoinRuleRestricted(allowList: List) { + // we need to compute correct via parameters and check if PL are correct + val allowEntries = allowList.map { spaceId -> + RoomJoinRulesAllowEntry(spaceId, viaParameterFinder.computeViaParamsForRestricted(spaceId, 3)) + } + updateJoinRule(RoomJoinRules.RESTRICTED, null, allowEntries) + } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/summary/RoomSummaryDataSource.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/summary/RoomSummaryDataSource.kt index bff1af60ca..0b8c6df806 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/summary/RoomSummaryDataSource.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/summary/RoomSummaryDataSource.kt @@ -247,10 +247,10 @@ internal class RoomSummaryDataSource @Inject constructor(@SessionDatabase privat queryParams.roomCategoryFilter?.let { when (it) { - RoomCategoryFilter.ONLY_DM -> query.equalTo(RoomSummaryEntityFields.IS_DIRECT, true) - RoomCategoryFilter.ONLY_ROOMS -> query.equalTo(RoomSummaryEntityFields.IS_DIRECT, false) + RoomCategoryFilter.ONLY_DM -> query.equalTo(RoomSummaryEntityFields.IS_DIRECT, true) + RoomCategoryFilter.ONLY_ROOMS -> query.equalTo(RoomSummaryEntityFields.IS_DIRECT, false) RoomCategoryFilter.ONLY_WITH_NOTIFICATIONS -> query.greaterThan(RoomSummaryEntityFields.NOTIFICATION_COUNT, 0) - RoomCategoryFilter.ALL -> { + RoomCategoryFilter.ALL -> { // nop } } @@ -274,15 +274,15 @@ internal class RoomSummaryDataSource @Inject constructor(@SessionDatabase privat query.equalTo(RoomSummaryEntityFields.ROOM_TYPE, it) } when (queryParams.roomCategoryFilter) { - RoomCategoryFilter.ONLY_DM -> query.equalTo(RoomSummaryEntityFields.IS_DIRECT, true) - RoomCategoryFilter.ONLY_ROOMS -> query.equalTo(RoomSummaryEntityFields.IS_DIRECT, false) + RoomCategoryFilter.ONLY_DM -> query.equalTo(RoomSummaryEntityFields.IS_DIRECT, true) + RoomCategoryFilter.ONLY_ROOMS -> query.equalTo(RoomSummaryEntityFields.IS_DIRECT, false) RoomCategoryFilter.ONLY_WITH_NOTIFICATIONS -> query.greaterThan(RoomSummaryEntityFields.NOTIFICATION_COUNT, 0) - RoomCategoryFilter.ALL -> Unit // nop + RoomCategoryFilter.ALL -> Unit // nop } // Timber.w("VAL: activeSpaceId : ${queryParams.activeSpaceId}") when (queryParams.activeSpaceFilter) { - is ActiveSpaceFilter.ActiveSpace -> { + is ActiveSpaceFilter.ActiveSpace -> { // It's annoying but for now realm java does not support querying in primitive list :/ // https://github.com/realm/realm-java/issues/5361 if (queryParams.activeSpaceFilter.currentSpaceId == null) { @@ -300,8 +300,8 @@ internal class RoomSummaryDataSource @Inject constructor(@SessionDatabase privat } } - if (queryParams.activeGroupId != null) { - query.contains(RoomSummaryEntityFields.GROUP_IDS, queryParams.activeGroupId!!) + queryParams.activeGroupId?.let { activeGroupId -> + query.contains(RoomSummaryEntityFields.GROUP_IDS, activeGroupId) } return query } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/util/file/AtomicFileCreator.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/util/file/AtomicFileCreator.kt new file mode 100644 index 0000000000..ca10c0ed0f --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/util/file/AtomicFileCreator.kt @@ -0,0 +1,42 @@ +/* + * Copyright (c) 2021 The Matrix.org Foundation C.I.C. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.matrix.android.sdk.internal.util.file + +import timber.log.Timber +import java.io.File + +internal class AtomicFileCreator(private val file: File) { + val partFile = File(file.parentFile, "${file.name}.part") + + init { + if (file.exists()) { + Timber.w("## AtomicFileCreator: target file ${file.path} exists, it should not happen.") + } + if (partFile.exists()) { + Timber.d("## AtomicFileCreator: discard aborted part file ${partFile.path}") + // No need to delete the file, we will overwrite it + } + } + + fun cancel() { + partFile.delete() + } + + fun commit() { + partFile.renameTo(file) + } +} diff --git a/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/session/room/send/WaveFormSanitizerTest.kt b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/session/room/send/WaveFormSanitizerTest.kt new file mode 100644 index 0000000000..23c8aeb76b --- /dev/null +++ b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/session/room/send/WaveFormSanitizerTest.kt @@ -0,0 +1,123 @@ +/* + * Copyright (c) 2021 The Matrix.org Foundation C.I.C. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.matrix.android.sdk.internal.session.room.send + +import org.amshove.kluent.shouldBe +import org.amshove.kluent.shouldBeInRange +import org.junit.Test + +class WaveFormSanitizerTest { + + private val waveFormSanitizer = WaveFormSanitizer() + + @Test + fun sanitizeNull() { + waveFormSanitizer.sanitize(null) shouldBe null + } + + @Test + fun sanitizeEmpty() { + waveFormSanitizer.sanitize(emptyList()) shouldBe null + } + + @Test + fun sanitizeSingleton() { + val result = waveFormSanitizer.sanitize(listOf(1))!! + result.size shouldBe 30 + checkResult(result) + } + + @Test + fun sanitize29() { + val list = generateSequence { 1 }.take(29).toList() + val result = waveFormSanitizer.sanitize(list)!! + checkResult(result) + } + + @Test + fun sanitize30() { + val list = generateSequence { 1 }.take(30).toList() + val result = waveFormSanitizer.sanitize(list)!! + result.size shouldBe 30 + checkResult(result) + } + + @Test + fun sanitize31() { + val list = generateSequence { 1 }.take(31).toList() + val result = waveFormSanitizer.sanitize(list)!! + checkResult(result) + } + + @Test + fun sanitize119() { + val list = generateSequence { 1 }.take(119).toList() + val result = waveFormSanitizer.sanitize(list)!! + checkResult(result) + } + + @Test + fun sanitize120() { + val list = generateSequence { 1 }.take(120).toList() + val result = waveFormSanitizer.sanitize(list)!! + result.size shouldBe 120 + checkResult(result) + } + + @Test + fun sanitize121() { + val list = generateSequence { 1 }.take(121).toList() + val result = waveFormSanitizer.sanitize(list)!! + checkResult(result) + } + + @Test + fun sanitize1024() { + val list = generateSequence { 1 }.take(1024).toList() + val result = waveFormSanitizer.sanitize(list)!! + checkResult(result) + } + + @Test + fun sanitizeNegative() { + val list = generateSequence { -1 }.take(30).toList() + val result = waveFormSanitizer.sanitize(list)!! + checkResult(result) + } + + @Test + fun sanitizeMaxValue() { + val list = generateSequence { 1025 }.take(30).toList() + val result = waveFormSanitizer.sanitize(list)!! + checkResult(result) + } + + @Test + fun sanitizeNegativeMaxValue() { + val list = generateSequence { -1025 }.take(30).toList() + val result = waveFormSanitizer.sanitize(list)!! + checkResult(result) + } + + private fun checkResult(result: List) { + result.forEach { + it shouldBeInRange 0..1024 + } + + result.size shouldBeInRange 30..120 + } +} From a0b7de4de51c57578518ec214195e625e8b85941 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Mon, 30 Aug 2021 14:40:25 +0200 Subject: [PATCH 04/17] Fix warning (will have to be done on Element too) --- .../src/androidTest/AndroidManifest.xml | 18 ++++++++++++++++++ .../src/main/AndroidManifest.xml | 9 --------- 2 files changed, 18 insertions(+), 9 deletions(-) create mode 100644 matrix-sdk-android/src/androidTest/AndroidManifest.xml diff --git a/matrix-sdk-android/src/androidTest/AndroidManifest.xml b/matrix-sdk-android/src/androidTest/AndroidManifest.xml new file mode 100644 index 0000000000..274bd8c87b --- /dev/null +++ b/matrix-sdk-android/src/androidTest/AndroidManifest.xml @@ -0,0 +1,18 @@ + + + + + + + + + + diff --git a/matrix-sdk-android/src/main/AndroidManifest.xml b/matrix-sdk-android/src/main/AndroidManifest.xml index 220a168f60..de0731422c 100644 --- a/matrix-sdk-android/src/main/AndroidManifest.xml +++ b/matrix-sdk-android/src/main/AndroidManifest.xml @@ -10,15 +10,6 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 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 0000000000..b11a538949 --- /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 6e07223ac7..7817351e53 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 da176491c6..a8cbc160dd 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 d14de30c90..74855b8630 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 f156a5eb64..0fe341cad6 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 9ebac8766a..03a4d41988 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 a1744a0dae..5911414c25 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 521b5805bd..301cdea461 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 ) + @Suppress("EXPERIMENTAL_API_USAGE") private fun createPublicSpace(session: Session, spaceName: String, childInfo: List> @@ -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 0ba61e5890..3149a0218b 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 ef770ea1d2..50c84da02b 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 73b0fe0a7c..1bc8636103 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 4534368679..1d0acf38fa 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?) - 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> } 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 4c01588b03..5b14e97d5e 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 54a1e896ae..b9d0c0ad2c 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, 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 ae546b6cec..1485ec478b 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): Map + /** + * 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 b3fbdcf1a2..85291cf0f6 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 ) : 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 347a3bb531..005a2edae7 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): 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 { 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 b7377df1b3..5d26b21349 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 = 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 36da242527..e721abd6a0 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 7b87bc34d2..16298b8ba9 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 -) + @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 33f402cad3..871b299f93 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? = 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 8cd2a0538d..7d3109fb6e 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, 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 d576f1057a..aa05eb7189 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? = null + val waveform: List? = 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 3bae6126e0..207050be7d 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?, 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 0000000000..ecc3eb5224 --- /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, + val childrenState: List, + 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 e5288e4045..bcc36b579a 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> + limit: Int? = null, + from: String? = null, + // when paginating, pass back the m.space.child state events + knownStateList: List? = 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 0c33cfa1e6..b55f90cef0 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 7e92ff3e88..0ec020bc46 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>, + // 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() 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 4a0a274f4f..e8640d5011 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 c86f2be0a3..ccdb5ab137 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(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(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 e8d567b944..2e26720abb 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() 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 8c68057056..c5c6d26f79 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 d997998836..3c8353e83e 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 41a5118be0..c6af9b0cd1 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 { data class Params( @@ -31,7 +31,7 @@ internal interface SendToDeviceTask : Task { val eventType: String, // the content to send. Map from user_id to device_id to content dictionary. val contentMap: MXUsersDevicesMap, - // 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 0000000000..3106d5820c --- /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 88aa432fb3..758c7aa5b9 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(realmConfiguration) { - override val query = Monarchy.Query { - 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) { @@ -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 28ae4d8bfd..aa96ca5e1a 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 c32c019625..0cf431c340 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 c9edbcd889..4dc8712afb 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::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 f4426207be..5cfd306d2f 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 0bf62a19fe..57e24cf88f 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() @@ -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 99c12255cd..361a306d4f 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 2116063626..8a03102527 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 7a06c2129c..7294ce4abf 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.process(sortOrder: RoomSortOrder): RealmQuery { 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 1fe4f9d90a..b36d05b6c0 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 48870b86b7..fdb6caf53f 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 e9e4d17e3a..99bd740463 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 0000000000..d491af33ca --- /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 { + data class Params( + val token: String, + val url: String, + val privateKey: String + ) +} + +internal class DefaultSign3pidInvitationTask @Inject constructor( + @Unauthenticated + private val okHttpClient: Lazy, + 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 0000000000..6998257263 --- /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 0000000000..27a3f3209f --- /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, + /** 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 4e8abcf784..65974151c8 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> { + // 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 21f55bbc42..dff82cb42e 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 { 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 0000000000..5d620ee7aa --- /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 d8f0ce12d5..4fed4302c7 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? = 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 daf9397ce8..994b4860c6 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 23d0515f41..bae893608b 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 { 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 b8dbabd09e..33589dc55b 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 9d7a46bede..3fe1614678 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 632ea4c450..f69949cbb6 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) { 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 18ece60629..535fa9df71 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, - @Body params: Map): 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 c04c899e18..794970705c 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 de6a71e581..518f0a0a6d 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 2ecacf335b..7528f80cc2 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 562b25683b..209a904fad 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 { data class Params( val roomIdOrAlias: String, val reason: String?, - val viaServers: List = emptyList() + val viaServers: List = emptyList(), + val thirdPartySigned : SignInvitationResult? = null ) } @@ -59,12 +62,16 @@ internal class DefaultJoinRoomTask @Inject constructor( return } roomChangeMembershipStateDataSource.updateState(params.roomIdOrAlias, ChangeMembershipState.Joining) + val extraParams = mutableMapOf().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 de049d7538..9cea1fe425 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 2efea7f118..5bad334afc 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 ) @@ -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 a64b903947..6dbb71e096 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 e98e5646af..13095fbd58 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 7eed22f65f..4ec27976a2 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) { // 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 842c9d3aba..89a3533946 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 3517f26c5d..721dae0b1b 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 233eef45f8..8a6bbc18fd 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?, 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() - ?: 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() +// ?: 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 0c5c0416f9..7be4cdcda9 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> { - return resolveSpaceInfoTask.execute(ResolveSpaceInfoTask.Params.withId(spaceId, suggestedOnly, autoJoinedOnly)).let { response -> + limit: Int?, + from: String?, + knownStateList: List?): 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()?.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()?.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 e9d5ba5193..7eeaed4ff6 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 d2be49b70b..2a396d6ee7 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 { 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 0fcc95fdb3..edd10bc2ef 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 5021ff638f..e0f273d0c2 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? = 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 013db1c286..0000000000 --- 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 20d63c8814..7d22dce96b 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? = 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? = null + @Json(name = "rooms") val rooms: List? = 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 f6b156a6fb..5cbaaa45c4 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 1df62e94e8..44d879f05d 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 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 c3586bcea7..830e666c95 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 ea10a32f3e..9bb2bfc9b1 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() + // Map of roomId to directUserId + val directChatsToCheck = mutableMapOf() } 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 12b77c706b..db1100d76c 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) { @@ -30,4 +37,33 @@ internal class SyncResponsePostTreatmentAggregatorHandler @Inject constructor( ephemeralTemporaryStore.delete(it) } } + + private suspend fun updateDirectUserIds(directUserIdsToUpdate: Map) { + 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 b8d987d500..110e77813d 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) { 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 41173dea96..7c73f1fed0 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> + +/** + * Returns a new [MutableMap] with all elements of this collection. + */ +internal fun DirectMessagesContent.toMutable(): MutableMap> { + 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 a9e5089774..3703057643 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> { + 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 5912dc7b53..dfe4b6b810 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?, 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 74b6c03d93..3572a1a546 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() @@ -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() From 7f51c3e2ee2bfe9942f5f46d43c133f85c2fe59f Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Wed, 8 Sep 2021 22:30:00 +0200 Subject: [PATCH 13/17] Changelog for 1.2.1 --- CHANGES.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/CHANGES.md b/CHANGES.md index 24b8c9dc1b..6bf63456a9 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,5 +1,13 @@ Please also refer to the Changelog of Element Android: https://github.com/vector-im/element-android/blob/main/CHANGES.md +Changes in Matrix-SDK 1.2.1 (2021-09-08) +=================================================== + +Imported from Element 1.2.1. (https://github.com/vector-im/element-android/releases/tag/v1.2.1) +Still an issue with Jitpack: https://github.com/jitpack/jitpack.io/issues/4721 +We are working to make the library available in MavenCentral. In the mean time, you can get the library from GitHub here: +https://github.com/matrix-org/matrix-android-sdk2/releases/tag/v1.2.1 + Changes in Matrix-SDK 1.2.0 (2021-09-08) =================================================== From 73b09c95d00b98135ccdf6e5c8a8fe737a7f9808 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Thu, 9 Sep 2021 21:53:58 +0200 Subject: [PATCH 14/17] Publish the SDK on MavenCentral --- build.gradle | 10 +++++++++- gradle.properties | 26 ++++++++++++++++++++++++++ matrix-sdk-android/build.gradle | 12 +----------- 3 files changed, 36 insertions(+), 12 deletions(-) diff --git a/build.gradle b/build.gradle index c8a95ea2ec..01c59b7357 100644 --- a/build.gradle +++ b/build.gradle @@ -10,11 +10,12 @@ buildscript { maven { url "https://plugins.gradle.org/m2/" } + mavenCentral() } dependencies { classpath 'com.android.tools.build:gradle:7.0.2' classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" - + classpath 'com.vanniktech:gradle-maven-publish-plugin:0.17.0' // NOTE: Do not place your application dependencies here; they belong // in the individual module build.gradle files } @@ -42,6 +43,13 @@ allprojects { kotlinOptions.allWarningsAsErrors = false } + plugins.withId("com.vanniktech.maven.publish") { + // Publish on s01.oss.sonatype.org + //https://github.com/vanniktech/gradle-maven-publish-plugin#where-to-upload-to + mavenPublish { + sonatypeHost = "S01" + } + } } task clean(type: Delete) { diff --git a/gradle.properties b/gradle.properties index 99fd9d64fd..5d59532efc 100644 --- a/gradle.properties +++ b/gradle.properties @@ -21,3 +21,29 @@ vector.httpLogLevel=NONE # Note: to debug, you can put and uncomment the following lines in the file ~/.gradle/gradle.properties to override the value above #vector.debugPrivateData=true #vector.httpLogLevel=BODY + +# Maven publication +# Ref: https://github.com/vanniktech/gradle-maven-publish-plugin +GROUP=org.matrix.android +POM_ARTIFACT_ID=matrix-android-sdk2 +VERSION_NAME=1.2.1 + +POM_PACKAGING=aar + +POM_NAME=Matrix Android SDK 2 +POM_DESCRIPTION=An Android SDK to connect to a Matrix homeserver. +POM_INCEPTION_YEAR=2021 +POM_URL=https://github.com/matrix-org/matrix-android-sdk2 + +POM_LICENSE_NAME=The Apache Software License, Version 2.0 +POM_LICENCE_URL=https://www.apache.org/licenses/LICENSE-2.0.txt +POM_LICENCE_DIST=repo + +POM_SCM_URL=https://github.com/matrix-org/matrix-android-sdk2 +POM_SCM_CONNECTION=scm:git:git://github.com/matrix-org/matrix-android-sdk2.git +POM_SCM_DEV_CONNECTION=scm:git:ssh://git@github.com/matrix-org/matrix-android-sdk2.git + +POM_DEVELOPER_ID=matrixdev +POM_DEVELOPER_NAME=matrixdev +POM_DEVELOPER_URL=https://github.com/matrix-org/ +POM_DEVELOPER_EMAIL=android@element.io diff --git a/matrix-sdk-android/build.gradle b/matrix-sdk-android/build.gradle index 81136f43ae..7c59413049 100644 --- a/matrix-sdk-android/build.gradle +++ b/matrix-sdk-android/build.gradle @@ -3,7 +3,7 @@ apply plugin: 'kotlin-android' apply plugin: 'kotlin-kapt' apply plugin: 'kotlin-parcelize' apply plugin: 'realm-android' -apply plugin: 'maven-publish' +apply plugin: "com.vanniktech.maven.publish" buildscript { repositories { @@ -193,13 +193,3 @@ dependencies { androidTestUtil 'androidx.test:orchestrator:1.4.0' } - -project.afterEvaluate { - publishing { - publications { - release(MavenPublication) { - from components.release - } - } - } -} From e7a196105d32929f8cd9c37459d482c547fbdecb Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Thu, 9 Sep 2021 22:01:03 +0200 Subject: [PATCH 15/17] Update README file --- README.md | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 3b27a8b83d..5c08dc614d 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -[![Latest version](https://img.shields.io/jitpack/v/github/matrix-org/matrix-android-sdk2)](https://jitpack.io/#matrix-org/matrix-android-sdk2) [![Build status](https://badge.buildkite.com/1f5af13987f4e309ae7d6d09e818e19a5a709008c249238c1a.svg?branch=main)](https://buildkite.com/matrix-dot-org/matrix-android-sdk2) +![Latest version](https://img.shields.io/maven-central/v/org.matrix.android/matrix-android-sdk2) [![Build status](https://badge.buildkite.com/1f5af13987f4e309ae7d6d09e818e19a5a709008c249238c1a.svg?branch=main)](https://buildkite.com/matrix-dot-org/matrix-android-sdk2) # matrix-android-sdk2 @@ -23,12 +23,10 @@ Please open any issue in the Element Android project: [Create an issue](https:// To integrate the SDK to your application, add the following gradle dependency to the build.gradle of your application module: ```gradle -implementation 'com.github.matrix-org:matrix-android-sdk2:v0.0.1' +implementation 'org.matrix.android:matrix-android-sdk2:1.2.1' ``` -Latest version: [![Latest version](https://img.shields.io/jitpack/v/github/matrix-org/matrix-android-sdk2)](https://jitpack.io/#matrix-org/matrix-android-sdk2) - -You need to add Jitpack as a repository in your main build.gradle file. Please follow instructions here: https://jitpack.io/#matrix-org/matrix-android-sdk2 +Latest version: ![Latest version](https://img.shields.io/maven-central/v/org.matrix.android/matrix-android-sdk2) ### Sample application From fa1b690ee5284ca18e23fdad1d41464a682f2b74 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Mon, 13 Sep 2021 15:41:53 +0200 Subject: [PATCH 16/17] Import from Element 1.2.2 --- matrix-sdk-android/build.gradle | 2 +- .../algorithms/megolm/MXMegolmEncryption.kt | 10 ++++----- .../algorithms/megolm/SharedWithHelper.kt | 11 ++++++++-- .../internal/crypto/store/IMXCryptoStore.kt | 5 +++-- .../crypto/store/db/RealmCryptoStore.kt | 19 +++++++++++++--- .../store/db/RealmCryptoStoreMigration.kt | 20 ++++++++++++++++- .../store/db/model/SharedSessionEntity.kt | 1 + .../store/db/query/SharedSessionQueries.kt | 22 ++++++++++++++----- 8 files changed, 71 insertions(+), 19 deletions(-) diff --git a/matrix-sdk-android/build.gradle b/matrix-sdk-android/build.gradle index 7c59413049..e1e1383d02 100644 --- a/matrix-sdk-android/build.gradle +++ b/matrix-sdk-android/build.gradle @@ -22,7 +22,7 @@ android { minSdkVersion 21 targetSdkVersion 30 versionCode 1 - versionName "1.2.1" + versionName "1.2.2" // Multidex is useful for tests multiDexEnabled true testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/megolm/MXMegolmEncryption.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/megolm/MXMegolmEncryption.kt index a756444475..540280d8a2 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/megolm/MXMegolmEncryption.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/megolm/MXMegolmEncryption.kt @@ -170,7 +170,7 @@ internal class MXMegolmEncryption( val deviceIds = devicesInRoom.getUserDeviceIds(userId) for (deviceId in deviceIds!!) { val deviceInfo = devicesInRoom.getObject(userId, deviceId) - if (deviceInfo != null && !cryptoStore.getSharedSessionInfo(roomId, safeSession.sessionId, userId, deviceId).found) { + if (deviceInfo != null && !cryptoStore.getSharedSessionInfo(roomId, safeSession.sessionId, deviceInfo).found) { val devices = shareMap.getOrPut(userId) { ArrayList() } devices.add(deviceInfo) } @@ -270,8 +270,8 @@ internal class MXMegolmEncryption( // for dead devices on every message. val gossipingEventBuffer = arrayListOf() for ((userId, devicesToShareWith) in devicesByUser) { - for ((deviceId) in devicesToShareWith) { - session.sharedWithHelper.markedSessionAsShared(userId, deviceId, chainIndex) + for (deviceInfo in devicesToShareWith) { + session.sharedWithHelper.markedSessionAsShared(deviceInfo, chainIndex) gossipingEventBuffer.add( Event( type = EventType.ROOM_KEY, @@ -279,7 +279,7 @@ internal class MXMegolmEncryption( content = submap.apply { this["session_key"] = "" // we add a fake key for trail - this["_dest"] = "$userId|$deviceId" + this["_dest"] = "$userId|${deviceInfo.deviceId}" } )) } @@ -429,7 +429,7 @@ internal class MXMegolmEncryption( .also { Timber.w("## Crypto reshareKey: Device not found") } // Get the chain index of the key we previously sent this device - val wasSessionSharedWithUser = cryptoStore.getSharedSessionInfo(roomId, sessionId, userId, deviceId) + val wasSessionSharedWithUser = cryptoStore.getSharedSessionInfo(roomId, sessionId, deviceInfo) if (!wasSessionSharedWithUser.found) { // This session was never shared with this user // Send a room key with held diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/megolm/SharedWithHelper.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/megolm/SharedWithHelper.kt index f17168a6d2..a64e5af0d3 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/megolm/SharedWithHelper.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/megolm/SharedWithHelper.kt @@ -16,6 +16,7 @@ package org.matrix.android.sdk.internal.crypto.algorithms.megolm +import org.matrix.android.sdk.internal.crypto.model.CryptoDeviceInfo import org.matrix.android.sdk.internal.crypto.model.MXUsersDevicesMap import org.matrix.android.sdk.internal.crypto.store.IMXCryptoStore @@ -28,7 +29,13 @@ internal class SharedWithHelper( return cryptoStore.getSharedWithInfo(roomId, sessionId) } - fun markedSessionAsShared(userId: String, deviceId: String, chainIndex: Int) { - cryptoStore.markedSessionAsShared(roomId, sessionId, userId, deviceId, chainIndex) + fun markedSessionAsShared(deviceInfo: CryptoDeviceInfo, chainIndex: Int) { + cryptoStore.markedSessionAsShared( + roomId = roomId, + sessionId = sessionId, + userId = deviceInfo.userId, + deviceId = deviceInfo.deviceId, + deviceIdentityKey = deviceInfo.identityKey() ?: "", + chainIndex = chainIndex) } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/IMXCryptoStore.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/IMXCryptoStore.kt index 3d12e74fcd..238d06738c 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/IMXCryptoStore.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/IMXCryptoStore.kt @@ -450,7 +450,8 @@ internal interface IMXCryptoStore { fun addWithHeldMegolmSession(withHeldContent: RoomKeyWithHeldContent) fun getWithHeldMegolmSession(roomId: String, sessionId: String): RoomKeyWithHeldContent? - fun markedSessionAsShared(roomId: String?, sessionId: String, userId: String, deviceId: String, chainIndex: Int) + fun markedSessionAsShared(roomId: String?, sessionId: String, userId: String, deviceId: String, + deviceIdentityKey: String, chainIndex: Int) /** * Query for information on this session sharing history. @@ -459,7 +460,7 @@ internal interface IMXCryptoStore { * in this case chainIndex is not nullindicates the ratchet position. * In found is false, chainIndex is null */ - fun getSharedSessionInfo(roomId: String?, sessionId: String, userId: String, deviceId: String): SharedSessionResult + fun getSharedSessionInfo(roomId: String?, sessionId: String, deviceInfo: CryptoDeviceInfo): SharedSessionResult data class SharedSessionResult(val found: Boolean, val chainIndex: Int?) fun getSharedWithInfo(roomId: String?, sessionId: String): MXUsersDevicesMap 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 3c8353e83e..860bba7404 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 @@ -1681,7 +1681,12 @@ internal class RealmCryptoStore @Inject constructor( } } - override fun markedSessionAsShared(roomId: String?, sessionId: String, userId: String, deviceId: String, chainIndex: Int) { + override fun markedSessionAsShared(roomId: String?, + sessionId: String, + userId: String, + deviceId: String, + deviceIdentityKey: String, + chainIndex: Int) { doRealmTransaction(realmConfiguration) { realm -> SharedSessionEntity.create( realm = realm, @@ -1689,14 +1694,22 @@ internal class RealmCryptoStore @Inject constructor( sessionId = sessionId, userId = userId, deviceId = deviceId, + deviceIdentityKey = deviceIdentityKey, chainIndex = chainIndex ) } } - override fun getSharedSessionInfo(roomId: String?, sessionId: String, userId: String, deviceId: String): IMXCryptoStore.SharedSessionResult { + override fun getSharedSessionInfo(roomId: String?, sessionId: String, deviceInfo: CryptoDeviceInfo): IMXCryptoStore.SharedSessionResult { return doWithRealm(realmConfiguration) { realm -> - SharedSessionEntity.get(realm, roomId, sessionId, userId, deviceId)?.let { + SharedSessionEntity.get( + realm = realm, + roomId = roomId, + sessionId = sessionId, + userId = deviceInfo.userId, + deviceId = deviceInfo.deviceId, + deviceIdentityKey = deviceInfo.identityKey() + )?.let { IMXCryptoStore.SharedSessionResult(true, it.chainIndex) } ?: IMXCryptoStore.SharedSessionResult(false, null) } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/RealmCryptoStoreMigration.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/RealmCryptoStoreMigration.kt index 2846be9932..f73cbaf480 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/RealmCryptoStoreMigration.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/RealmCryptoStoreMigration.kt @@ -55,7 +55,7 @@ internal object RealmCryptoStoreMigration : RealmMigration { // 0, 1, 2: legacy Riot-Android // 3: migrate to RiotX schema // 4, 5, 6, 7, 8, 9: migrations from RiotX (which was previously 1, 2, 3, 4, 5, 6) - const val CRYPTO_STORE_SCHEMA_VERSION = 13L + const val CRYPTO_STORE_SCHEMA_VERSION = 14L private fun RealmObjectSchema.addFieldIfNotExists(fieldName: String, fieldType: Class<*>): RealmObjectSchema { if (!hasField(fieldName)) { @@ -94,6 +94,7 @@ internal object RealmCryptoStoreMigration : RealmMigration { if (oldVersion <= 10) migrateTo11(realm) if (oldVersion <= 11) migrateTo12(realm) if (oldVersion <= 12) migrateTo13(realm) + if (oldVersion <= 13) migrateTo14(realm) } private fun migrateTo1Legacy(realm: DynamicRealm) { @@ -554,4 +555,21 @@ internal object RealmCryptoStoreMigration : RealmMigration { Timber.e("TrustLevelEntity cleanup: Something is not correct...") } } + + // Version 14L Update the way we remember key sharing + private fun migrateTo14(realm: DynamicRealm) { + Timber.d("Step 13 -> 14") + realm.schema.get("SharedSessionEntity") + ?.addField(SharedSessionEntityFields.DEVICE_IDENTITY_KEY, String::class.java) + ?.addIndex(SharedSessionEntityFields.DEVICE_IDENTITY_KEY) + ?.transform { + val sharedUserId = it.getString(SharedSessionEntityFields.USER_ID) + val sharedDeviceId = it.getString(SharedSessionEntityFields.DEVICE_ID) + val knownDevice = realm.where("DeviceInfoEntity") + .equalTo(DeviceInfoEntityFields.USER_ID, sharedUserId) + .equalTo(DeviceInfoEntityFields.DEVICE_ID, sharedDeviceId) + .findFirst() + it.setString(SharedSessionEntityFields.DEVICE_IDENTITY_KEY, knownDevice?.getString(DeviceInfoEntityFields.IDENTITY_KEY)) + } + } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/model/SharedSessionEntity.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/model/SharedSessionEntity.kt index c0ed1ac409..e2ae512afd 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/model/SharedSessionEntity.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/model/SharedSessionEntity.kt @@ -30,6 +30,7 @@ internal open class SharedSessionEntity( @Index var sessionId: String? = null, @Index var userId: String? = null, @Index var deviceId: String? = null, + @Index var deviceIdentityKey: String? = null, var chainIndex: Int? = null ) : RealmObject() { diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/query/SharedSessionQueries.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/query/SharedSessionQueries.kt index fa37734fe5..39117512bb 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/query/SharedSessionQueries.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/query/SharedSessionQueries.kt @@ -16,15 +16,20 @@ package org.matrix.android.sdk.internal.crypto.store.db.query -import org.matrix.android.sdk.internal.crypto.MXCRYPTO_ALGORITHM_MEGOLM -import org.matrix.android.sdk.internal.crypto.store.db.model.SharedSessionEntity -import org.matrix.android.sdk.internal.crypto.store.db.model.SharedSessionEntityFields import io.realm.Realm import io.realm.RealmResults import io.realm.kotlin.createObject import io.realm.kotlin.where +import org.matrix.android.sdk.internal.crypto.MXCRYPTO_ALGORITHM_MEGOLM +import org.matrix.android.sdk.internal.crypto.store.db.model.SharedSessionEntity +import org.matrix.android.sdk.internal.crypto.store.db.model.SharedSessionEntityFields -internal fun SharedSessionEntity.Companion.get(realm: Realm, roomId: String?, sessionId: String, userId: String, deviceId: String) +internal fun SharedSessionEntity.Companion.get(realm: Realm, + roomId: String?, + sessionId: String, + userId: String, + deviceId: String, + deviceIdentityKey: String?) : SharedSessionEntity? { return realm.where() .equalTo(SharedSessionEntityFields.ROOM_ID, roomId) @@ -32,6 +37,7 @@ internal fun SharedSessionEntity.Companion.get(realm: Realm, roomId: String?, se .equalTo(SharedSessionEntityFields.ALGORITHM, MXCRYPTO_ALGORITHM_MEGOLM) .equalTo(SharedSessionEntityFields.USER_ID, userId) .equalTo(SharedSessionEntityFields.DEVICE_ID, deviceId) + .equalTo(SharedSessionEntityFields.DEVICE_IDENTITY_KEY, deviceIdentityKey) .findFirst() } @@ -44,7 +50,12 @@ internal fun SharedSessionEntity.Companion.get(realm: Realm, roomId: String?, se .findAll() } -internal fun SharedSessionEntity.Companion.create(realm: Realm, roomId: String?, sessionId: String, userId: String, deviceId: String, chainIndex: Int) +internal fun SharedSessionEntity.Companion.create(realm: Realm, roomId: String?, + sessionId: String, + userId: String, + deviceId: String, + deviceIdentityKey: String, + chainIndex: Int) : SharedSessionEntity { return realm.createObject().apply { this.roomId = roomId @@ -52,6 +63,7 @@ internal fun SharedSessionEntity.Companion.create(realm: Realm, roomId: String?, this.sessionId = sessionId this.userId = userId this.deviceId = deviceId + this.deviceIdentityKey = deviceIdentityKey this.chainIndex = chainIndex } } From 0c4bee46d848a02a923031773deabd5b520daacb Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Mon, 13 Sep 2021 15:43:21 +0200 Subject: [PATCH 17/17] Version 1.2.2 and Changelog --- CHANGES.md | 5 +++++ gradle.properties | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/CHANGES.md b/CHANGES.md index 6bf63456a9..fde0a6d9d3 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,5 +1,10 @@ Please also refer to the Changelog of Element Android: https://github.com/vector-im/element-android/blob/main/CHANGES.md +Changes in Matrix-SDK 1.2.2 (2021-09-13) +=================================================== + +Fix a security issue with message key sharing. See https://matrix.org/blog/2021/09/13/vulnerability-disclosure-key-sharing for details. + Changes in Matrix-SDK 1.2.1 (2021-09-08) =================================================== diff --git a/gradle.properties b/gradle.properties index 5d59532efc..16034b7377 100644 --- a/gradle.properties +++ b/gradle.properties @@ -26,7 +26,7 @@ vector.httpLogLevel=NONE # Ref: https://github.com/vanniktech/gradle-maven-publish-plugin GROUP=org.matrix.android POM_ARTIFACT_ID=matrix-android-sdk2 -VERSION_NAME=1.2.1 +VERSION_NAME=1.2.2 POM_PACKAGING=aar