diff --git a/CHANGES.md b/CHANGES.md
index a6221116b7..c40c66a0b2 100644
--- a/CHANGES.md
+++ b/CHANGES.md
@@ -1,3 +1,47 @@
+Changes in Element 1.1.7 (2021-XX-XX)
+===================================================
+
+Features ✨:
+ - Spaces beta
+
+Improvements 🙌:
+ - Add ability to install APK from directly from Element (#2381)
+ - Delete and react to stickers (#3250)
+
+Bugfix 🐛:
+ - Message states cosmetic changes (#3007)
+ - Fix exception in rxSingle (#3180)
+ - Do not invite the current user when creating a room (#3123)
+ - Fix color issues when the system theme is changed (#2738)
+ - Fix issues on Android 11 (#3067)
+ - Fix issue when opening encrypted files (#3186)
+ - Fix wording issue (#3242)
+ - Fix missing sender information after edits (#3184)
+
+Translations 🗣:
+ -
+
+SDK API changes ⚠️:
+ - RegistrationWizard.createAccount() parameters are now all optional, following Matrix spec (#3205)
+
+Build 🧱:
+ - Upgrade to gradle 7
+
+Test:
+ -
+
+Other changes:
+ - New store descriptions
+ - `master` branch has been renamed to `main`. To apply change to your dev environment, run:
+```sh
+git branch -m master main
+git fetch origin
+git branch -u origin/main main
+# And optionally
+git remote prune origin
+```
+ - Allow cleartext (non-SSL) connections to Matrix servers on LAN hosts (#3166)
+
Changes in Element 1.1.6 (2021-04-16)
===================================================
diff --git a/attachment-viewer/build.gradle b/attachment-viewer/build.gradle
index 5a8cce92e8..8db57a59af 100644
--- a/attachment-viewer/build.gradle
+++ b/attachment-viewer/build.gradle
@@ -69,7 +69,7 @@ dependencies {
implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
implementation 'androidx.core:core-ktx:1.3.2'
implementation 'androidx.appcompat:appcompat:1.2.0'
- implementation "androidx.recyclerview:recyclerview:1.2.0-rc01"
+ implementation "androidx.recyclerview:recyclerview:1.2.0"
implementation 'com.google.android.material:material:1.3.0'
}
\ No newline at end of file
diff --git a/build.gradle b/build.gradle
index b8da6c3864..25734e3b09 100644
--- a/build.gradle
+++ b/build.gradle
@@ -16,8 +16,8 @@ buildscript {
classpath 'com.google.gms:google-services:4.3.5'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
classpath 'org.sonarsource.scanner.gradle:sonarqube-gradle-plugin:3.1.1'
- classpath 'com.google.android.gms:oss-licenses-plugin:0.10.3'
- classpath "com.likethesalad.android:string-reference:1.2.1"
+ classpath 'com.google.android.gms:oss-licenses-plugin:0.10.4'
+ classpath "com.likethesalad.android:string-reference:1.2.2"
// NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files
@@ -51,7 +51,7 @@ allprojects {
}
}
maven {
- url "http://dl.bintray.com/piasy/maven"
+ url "https://dl.bintray.com/piasy/maven"
content {
includeGroupByRegex "com\\.github\\.piasy"
}
@@ -59,7 +59,7 @@ allprojects {
maven { url 'https://oss.sonatype.org/content/repositories/snapshots/' }
// Jitsi repo
maven {
- url "https://github.com/vector-im/jitsi_libre_maven/raw/master/android-sdk-3.1.0"
+ url "https://github.com/vector-im/jitsi_libre_maven/raw/main/android-sdk-3.1.0"
// Note: to test Jitsi release you can use a local file like this:
// url "file:///Users/bmarty/workspaces/jitsi_libre_maven/android-sdk-3.1.0"
}
diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties
index 6e61ea7487..9d174797f7 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=9af5c8e7e2cd1a3b0f694a4ac262b9f38c75262e74a9e8b5101af302a6beadd7
-distributionUrl=https\://services.gradle.org/distributions/gradle-6.8.3-all.zip
+distributionSha256Sum=81003f83b0056d20eedf48cddd4f52a9813163d4ba185bcf8abd34b8eeea4cbd
+distributionUrl=https\://services.gradle.org/distributions/gradle-7.0-all.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
diff --git a/matrix-sdk-android-rx/src/main/AndroidManifest.xml b/matrix-sdk-android-rx/src/main/AndroidManifest.xml
index f1bb42638f..5f399e9f84 100644
--- a/matrix-sdk-android-rx/src/main/AndroidManifest.xml
+++ b/matrix-sdk-android-rx/src/main/AndroidManifest.xml
@@ -1,2 +1 @@
-
+
diff --git a/matrix-sdk-android-rx/src/main/java/org/matrix/android/sdk/rx/RxCallbackBuilders.kt b/matrix-sdk-android-rx/src/main/java/org/matrix/android/sdk/rx/RxCallbackBuilders.kt
deleted file mode 100644
index ec30a31f6d..0000000000
--- a/matrix-sdk-android-rx/src/main/java/org/matrix/android/sdk/rx/RxCallbackBuilders.kt
+++ /dev/null
@@ -1,56 +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.rx
-
-import org.matrix.android.sdk.api.MatrixCallback
-import org.matrix.android.sdk.api.util.Cancelable
-import io.reactivex.Completable
-import io.reactivex.Single
-
-fun singleBuilder(builder: (MatrixCallback) -> Cancelable): Single = Single.create { emitter ->
- val callback = object : MatrixCallback {
- override fun onSuccess(data: T) {
- // Add `!!` to fix the warning:
- // "Type mismatch: type parameter with nullable bounds is used T is used where T was expected. This warning will become an error soon"
- emitter.onSuccess(data!!)
- }
-
- override fun onFailure(failure: Throwable) {
- emitter.tryOnError(failure)
- }
- }
- val cancelable = builder(callback)
- emitter.setCancellable {
- cancelable.cancel()
- }
-}
-
-fun completableBuilder(builder: (MatrixCallback) -> Cancelable): Completable = Completable.create { emitter ->
- val callback = object : MatrixCallback {
- override fun onSuccess(data: T) {
- emitter.onComplete()
- }
-
- override fun onFailure(failure: Throwable) {
- emitter.tryOnError(failure)
- }
- }
- val cancelable = builder(callback)
- emitter.setCancellable {
- cancelable.cancel()
- }
-}
diff --git a/matrix-sdk-android-rx/src/main/java/org/matrix/android/sdk/rx/RxSession.kt b/matrix-sdk-android-rx/src/main/java/org/matrix/android/sdk/rx/RxSession.kt
index 0d5b5ed821..67a35cac2e 100644
--- a/matrix-sdk-android-rx/src/main/java/org/matrix/android/sdk/rx/RxSession.kt
+++ b/matrix-sdk-android-rx/src/main/java/org/matrix/android/sdk/rx/RxSession.kt
@@ -39,6 +39,7 @@ import org.matrix.android.sdk.api.session.room.members.ChangeMembershipState
import org.matrix.android.sdk.api.session.room.model.RoomMemberSummary
import org.matrix.android.sdk.api.session.room.model.RoomSummary
import org.matrix.android.sdk.api.session.room.model.create.CreateRoomParams
+import org.matrix.android.sdk.api.session.space.SpaceSummaryQueryParams
import org.matrix.android.sdk.api.session.sync.SyncState
import org.matrix.android.sdk.api.session.user.model.User
import org.matrix.android.sdk.api.session.widgets.model.Widget
@@ -66,6 +67,13 @@ class RxSession(private val session: Session) {
}
}
+ fun liveSpaceSummaries(queryParams: SpaceSummaryQueryParams): Observable> {
+ return session.spaceService().getSpaceSummariesLive(queryParams).asObservable()
+ .startWithCallable {
+ session.spaceService().getSpaceSummaries(queryParams)
+ }
+ }
+
fun liveBreadcrumbs(queryParams: RoomSummaryQueryParams): Observable> {
return session.getBreadcrumbsLive(queryParams).asObservable()
.startWithCallable {
@@ -124,8 +132,8 @@ class RxSession(private val session: Session) {
.startWithCallable { session.getPendingThreePids() }
}
- fun createRoom(roomParams: CreateRoomParams): Single = singleBuilder {
- session.createRoom(roomParams, it)
+ fun createRoom(roomParams: CreateRoomParams): Single = rxSingle {
+ session.createRoom(roomParams)
}
fun searchUsersDirectory(search: String,
@@ -136,13 +144,13 @@ class RxSession(private val session: Session) {
fun joinRoom(roomIdOrAlias: String,
reason: String? = null,
- viaServers: List = emptyList()): Single = singleBuilder {
- session.joinRoom(roomIdOrAlias, reason, viaServers, it)
+ viaServers: List = emptyList()): Single = rxSingle {
+ session.joinRoom(roomIdOrAlias, reason, viaServers)
}
fun getRoomIdByAlias(roomAlias: String,
- searchOnServer: Boolean): Single> = singleBuilder {
- session.getRoomIdByAlias(roomAlias, searchOnServer, it)
+ searchOnServer: Boolean): Single> = rxSingle {
+ session.getRoomIdByAlias(roomAlias, searchOnServer)
}
fun getProfileInfo(userId: String): Single = rxSingle {
diff --git a/matrix-sdk-android/build.gradle b/matrix-sdk-android/build.gradle
index 1191e11b11..ea94d44346 100644
--- a/matrix-sdk-android/build.gradle
+++ b/matrix-sdk-android/build.gradle
@@ -115,7 +115,7 @@ dependencies {
def lifecycle_version = '2.2.0'
def arch_version = '2.1.0'
def markwon_version = '3.1.0'
- def daggerVersion = '2.33'
+ def daggerVersion = '2.35'
def work_version = '2.5.0'
def retrofit_version = '2.9.0'
@@ -169,7 +169,7 @@ dependencies {
implementation 'com.facebook.stetho:stetho-okhttp3:1.6.0'
// Phone number https://github.com/google/libphonenumber
- implementation 'com.googlecode.libphonenumber:libphonenumber:8.12.21'
+ implementation 'com.googlecode.libphonenumber:libphonenumber:8.12.22'
testImplementation 'junit:junit:4.13.2'
testImplementation 'org.robolectric:robolectric:4.5.1'
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 5815b23c06..da176491c6 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
@@ -66,8 +66,8 @@ class CryptoTestHelper(private val mTestHelper: CommonTestHelper) {
fun doE2ETestWithAliceInARoom(encryptedRoom: Boolean = true): CryptoTestData {
val aliceSession = mTestHelper.createAccount(TestConstants.USER_ALICE, defaultSessionParams)
- val roomId = mTestHelper.doSync {
- aliceSession.createRoom(CreateRoomParams().apply { name = "MyRoom" }, it)
+ val roomId = mTestHelper.runBlockingTest {
+ aliceSession.createRoom(CreateRoomParams().apply { name = "MyRoom" })
}
if (encryptedRoom) {
@@ -135,7 +135,7 @@ class CryptoTestHelper(private val mTestHelper: CommonTestHelper) {
bobRoomSummariesLive.observeForever(roomJoinedObserver)
}
- mTestHelper.doSync { bobSession.joinRoom(aliceRoomId, callback = it) }
+ mTestHelper.runBlockingTest { bobSession.joinRoom(aliceRoomId) }
mTestHelper.await(lock)
@@ -176,8 +176,8 @@ class CryptoTestHelper(private val mTestHelper: CommonTestHelper) {
room.invite(samSession.myUserId, null)
}
- mTestHelper.doSync {
- samSession.joinRoom(room.roomId, null, emptyList(), it)
+ mTestHelper.runBlockingTest {
+ samSession.joinRoom(room.roomId, null, emptyList())
}
return samSession
@@ -256,8 +256,8 @@ class CryptoTestHelper(private val mTestHelper: CommonTestHelper) {
}
fun createDM(alice: Session, bob: Session): String {
- val roomId = mTestHelper.doSync {
- alice.createDirectRoom(bob.myUserId, it)
+ val roomId = mTestHelper.runBlockingTest {
+ alice.createDirectRoom(bob.myUserId)
}
mTestHelper.waitWithLatch { latch ->
@@ -300,7 +300,7 @@ class CryptoTestHelper(private val mTestHelper: CommonTestHelper) {
bobRoomSummariesLive.observeForever(newRoomObserver)
}
- mTestHelper.doSync { bob.joinRoom(roomId, callback = it) }
+ mTestHelper.runBlockingTest { bob.joinRoom(roomId) }
}
return roomId
@@ -398,8 +398,8 @@ class CryptoTestHelper(private val mTestHelper: CommonTestHelper) {
val aliceSession = mTestHelper.createAccount(TestConstants.USER_ALICE, defaultSessionParams)
aliceSession.cryptoService().setWarnOnUnknownDevices(false)
- val roomId = mTestHelper.doSync {
- aliceSession.createRoom(CreateRoomParams().apply { name = "MyRoom" }, it)
+ val roomId = mTestHelper.runBlockingTest {
+ aliceSession.createRoom(CreateRoomParams().apply { name = "MyRoom" })
}
val room = aliceSession.getRoom(roomId)!!
@@ -412,7 +412,7 @@ class CryptoTestHelper(private val mTestHelper: CommonTestHelper) {
val session = mTestHelper.createAccount("User_$index", defaultSessionParams)
mTestHelper.runBlockingTest(timeout = 600_000) { room.invite(session.myUserId, null) }
println("TEST -> " + session.myUserId + " invited")
- mTestHelper.doSync { session.joinRoom(room.roomId, null, emptyList(), it) }
+ mTestHelper.runBlockingTest { session.joinRoom(room.roomId, null, emptyList()) }
println("TEST -> " + session.myUserId + " joined")
sessions.add(session)
}
diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/gossiping/KeyShareTests.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/gossiping/KeyShareTests.kt
index e6b364f3fb..40659cef11 100644
--- a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/gossiping/KeyShareTests.kt
+++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/gossiping/KeyShareTests.kt
@@ -71,13 +71,12 @@ class KeyShareTests : InstrumentedTest {
val aliceSession = mTestHelper.createAccount(TestConstants.USER_ALICE, SessionTestParams(true))
// Create an encrypted room and add a message
- val roomId = mTestHelper.doSync {
+ val roomId = mTestHelper.runBlockingTest {
aliceSession.createRoom(
CreateRoomParams().apply {
visibility = RoomDirectoryVisibility.PRIVATE
enableEncryption()
- },
- it
+ }
)
}
val room = aliceSession.getRoom(roomId)
@@ -332,13 +331,12 @@ class KeyShareTests : InstrumentedTest {
}
// Create an encrypted room and send a couple of messages
- val roomId = mTestHelper.doSync {
+ val roomId = mTestHelper.runBlockingTest {
aliceSession.createRoom(
CreateRoomParams().apply {
visibility = RoomDirectoryVisibility.PRIVATE
enableEncryption()
- },
- it
+ }
)
}
val roomAlicePov = aliceSession.getRoom(roomId)
@@ -371,8 +369,8 @@ class KeyShareTests : InstrumentedTest {
roomAlicePov.invite(bobSession.myUserId, null)
}
- mTestHelper.doSync {
- bobSession.joinRoom(roomAlicePov.roomId, null, emptyList(), it)
+ mTestHelper.runBlockingTest {
+ bobSession.joinRoom(roomAlicePov.roomId, null, emptyList())
}
// we want to discard alice outbound session
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
new file mode 100644
index 0000000000..278762671b
--- /dev/null
+++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/space/SpaceCreationTest.kt
@@ -0,0 +1,194 @@
+/*
+ * 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.session.space
+
+import kotlinx.coroutines.delay
+import kotlinx.coroutines.runBlocking
+import org.junit.FixMethodOrder
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+import org.junit.runners.MethodSorters
+import org.matrix.android.sdk.InstrumentedTest
+import org.matrix.android.sdk.api.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.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
+import org.matrix.android.sdk.api.session.room.model.RoomHistoryVisibilityContent
+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
+import kotlin.test.assertEquals
+import kotlin.test.assertNotNull
+
+@RunWith(JUnit4::class)
+@FixMethodOrder(MethodSorters.JVM)
+class SpaceCreationTest : InstrumentedTest {
+
+ private val commonTestHelper = CommonTestHelper(context())
+
+ @Test
+ fun createSimplePublicSpace() {
+ val session = commonTestHelper.createAccount("Hubble", SessionTestParams(true))
+ val roomName = "My Space"
+ val topic = "A public space for test"
+ val spaceId: String
+ runBlocking {
+ spaceId = session.spaceService().createSpace(roomName, topic, null, true)
+ // wait a bit to let the summary update it self :/
+ delay(400)
+ }
+
+ val syncedSpace = session.spaceService().getSpace(spaceId)
+ assertEquals(roomName, syncedSpace?.asRoom()?.roomSummary()?.name, "Room name should be set")
+ assertEquals(topic, syncedSpace?.asRoom()?.roomSummary()?.topic, "Room topic should be set")
+ // assertEquals(topic, syncedSpace.asRoom().roomSummary()?., "Room topic should be set")
+
+ assertNotNull(syncedSpace, "Space should be found by Id")
+ val creationEvent = syncedSpace.asRoom().getStateEvent(EventType.STATE_ROOM_CREATE)
+ val createContent = creationEvent?.content.toModel()
+ assertEquals(RoomType.SPACE, createContent?.type, "Room type should be space")
+
+ var powerLevelsContent: PowerLevelsContent? = null
+ commonTestHelper.waitWithLatch { latch ->
+ commonTestHelper.retryPeriodicallyWithLatch(latch) {
+ val toModel = syncedSpace.asRoom().getStateEvent(EventType.STATE_ROOM_POWER_LEVELS)?.content.toModel()
+ powerLevelsContent = toModel
+ toModel != null
+ }
+ }
+ assertEquals(100, powerLevelsContent?.eventsDefault, "Space-rooms should be created with a power level for events_default of 100")
+
+ val guestAccess = syncedSpace.asRoom().getStateEvent(EventType.STATE_ROOM_GUEST_ACCESS)?.content
+ ?.toModel()?.guestAccess
+
+ assertEquals(GuestAccess.CanJoin, guestAccess, "Public space room should be peekable by guest")
+
+ val historyVisibility = syncedSpace.asRoom().getStateEvent(EventType.STATE_ROOM_HISTORY_VISIBILITY)?.content
+ ?.toModel()?.historyVisibility
+
+ assertEquals(RoomHistoryVisibility.WORLD_READABLE, historyVisibility, "Public space room should be world readable")
+
+ commonTestHelper.signOutAndClose(session)
+ }
+
+ @Test
+ fun testJoinSimplePublicSpace() {
+ val aliceSession = commonTestHelper.createAccount("alice", SessionTestParams(true))
+ val bobSession = commonTestHelper.createAccount("bob", SessionTestParams(true))
+
+ val roomName = "My Space"
+ val topic = "A public space for test"
+ val spaceId: String
+ runBlocking {
+ spaceId = aliceSession.spaceService().createSpace(roomName, topic, null, true)
+ // wait a bit to let the summary update it self :/
+ delay(400)
+ }
+
+ // Try to join from bob, it's a public space no need to invite
+
+ val joinResult: JoinSpaceResult
+ runBlocking {
+ joinResult = bobSession.spaceService().joinSpace(spaceId)
+ }
+
+ assertEquals(JoinSpaceResult.Success, joinResult)
+
+ val spaceBobPov = bobSession.spaceService().getSpace(spaceId)
+ assertEquals(roomName, spaceBobPov?.asRoom()?.roomSummary()?.name, "Room name should be set")
+ assertEquals(topic, spaceBobPov?.asRoom()?.roomSummary()?.topic, "Room topic should be set")
+
+ commonTestHelper.signOutAndClose(aliceSession)
+ commonTestHelper.signOutAndClose(bobSession)
+ }
+
+ @Test
+ fun testSimplePublicSpaceWithChildren() {
+ val aliceSession = commonTestHelper.createAccount("alice", SessionTestParams(true))
+ val bobSession = commonTestHelper.createAccount("bob", SessionTestParams(true))
+
+ val roomName = "My Space"
+ val topic = "A public space for test"
+
+ val spaceId: String = runBlocking { aliceSession.spaceService().createSpace(roomName, topic, null, true) }
+ val syncedSpace = aliceSession.spaceService().getSpace(spaceId)
+
+ // create a room
+ val firstChild: String = runBlocking {
+ aliceSession.createRoom(CreateRoomParams().apply {
+ this.name = "FirstRoom"
+ this.topic = "Description of first room"
+ this.preset = CreateRoomPreset.PRESET_PUBLIC_CHAT
+ })
+ }
+
+ runBlocking {
+ syncedSpace?.addChildren(firstChild, listOf(aliceSession.sessionParams.homeServerHost ?: ""), "a", true)
+ }
+
+ val secondChild: String = runBlocking {
+ aliceSession.createRoom(CreateRoomParams().apply {
+ this.name = "SecondRoom"
+ this.topic = "Description of second room"
+ this.preset = CreateRoomPreset.PRESET_PUBLIC_CHAT
+ })
+ }
+
+ runBlocking {
+ syncedSpace?.addChildren(secondChild, listOf(aliceSession.sessionParams.homeServerHost ?: ""), "b", false)
+ }
+
+ // Try to join from bob, it's a public space no need to invite
+
+ val joinResult = runBlocking {
+ bobSession.spaceService().joinSpace(spaceId)
+ }
+
+ assertEquals(JoinSpaceResult.Success, joinResult)
+
+ val spaceBobPov = bobSession.spaceService().getSpace(spaceId)
+ assertEquals(roomName, spaceBobPov?.asRoom()?.roomSummary()?.name, "Room name should be set")
+ assertEquals(topic, spaceBobPov?.asRoom()?.roomSummary()?.topic, "Room topic should be set")
+
+ // check if bob has joined automatically the first room
+
+ val bobMembershipFirstRoom = bobSession.getRoom(firstChild)?.roomSummary()?.membership
+ assertEquals(Membership.JOIN, bobMembershipFirstRoom, "Bob should have joined this room")
+ RoomSummaryQueryParams.Builder()
+
+ val spaceSummaryBobPov = bobSession.spaceService().getSpaceSummaries(roomSummaryQueryParams {
+ this.roomId = QueryStringValue.Equals(spaceId)
+ this.memberships = listOf(Membership.JOIN)
+ }).firstOrNull()
+
+ assertEquals(2, spaceSummaryBobPov?.spaceChildren?.size ?: -1, "Unexpected number of children")
+
+ 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
new file mode 100644
index 0000000000..2fed7e338e
--- /dev/null
+++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/session/space/SpaceHierarchyTest.kt
@@ -0,0 +1,414 @@
+/*
+ * 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.session.space
+
+import android.util.Log
+import androidx.lifecycle.Observer
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.GlobalScope
+import kotlinx.coroutines.delay
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.runBlocking
+import org.junit.FixMethodOrder
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+import org.junit.runners.MethodSorters
+import org.matrix.android.sdk.InstrumentedTest
+import org.matrix.android.sdk.api.session.Session
+import org.matrix.android.sdk.api.session.room.model.RoomSummary
+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.roomSummaryQueryParams
+import org.matrix.android.sdk.common.CommonTestHelper
+import org.matrix.android.sdk.common.SessionTestParams
+import kotlin.test.assertEquals
+import kotlin.test.assertNotNull
+import kotlin.test.assertTrue
+
+@RunWith(JUnit4::class)
+@FixMethodOrder(MethodSorters.JVM)
+class SpaceHierarchyTest : InstrumentedTest {
+
+ private val commonTestHelper = CommonTestHelper(context())
+
+ @Test
+ fun createCanonicalChildRelation() {
+ val session = commonTestHelper.createAccount("John", SessionTestParams(true))
+ val spaceName = "My Space"
+ val topic = "A public space for test"
+ val spaceId: String
+ runBlocking {
+ spaceId = session.spaceService().createSpace(spaceName, topic, null, true)
+ // wait a bit to let the summary update it self :/
+ delay(400)
+ }
+
+ val syncedSpace = session.spaceService().getSpace(spaceId)
+
+ val roomId = runBlocking {
+ session.createRoom(CreateRoomParams().apply { name = "General" })
+ }
+
+ val viaServers = listOf(session.sessionParams.homeServerHost ?: "")
+
+ runBlocking {
+ syncedSpace!!.addChildren(roomId, viaServers, null, true)
+ }
+
+ runBlocking {
+ session.spaceService().setSpaceParent(roomId, spaceId, true, viaServers)
+ }
+
+ Thread.sleep(9000)
+
+ val parents = session.getRoom(roomId)?.roomSummary()?.spaceParents
+ val canonicalParents = session.getRoom(roomId)?.roomSummary()?.spaceParents?.filter { it.canonical == true }
+
+ parents?.forEach {
+ Log.d("## TEST", "parent : $it")
+ }
+
+ assertNotNull(parents)
+ assertEquals(1, parents.size)
+ assertEquals(spaceName, parents.first().roomSummary?.name)
+
+ assertNotNull(canonicalParents)
+ assertEquals(1, canonicalParents.size)
+ assertEquals(spaceName, canonicalParents.first().roomSummary?.name)
+ }
+
+ @Test
+ fun testCreateChildRelations() {
+ val session = commonTestHelper.createAccount("Jhon", SessionTestParams(true))
+ val spaceName = "My Space"
+ val topic = "A public space for test"
+ Log.d("## TEST", "Before")
+ val spaceId = runBlocking {
+ session.spaceService().createSpace(spaceName, topic, null, true)
+ }
+
+ Log.d("## TEST", "created space $spaceId ${Thread.currentThread()}")
+ val syncedSpace = session.spaceService().getSpace(spaceId)
+
+ val children = listOf("General" to true /*canonical*/, "Random" to false)
+
+ val roomIdList = children.map {
+ runBlocking {
+ session.createRoom(CreateRoomParams().apply { name = it.first })
+ } to it.second
+ }
+
+ val viaServers = listOf(session.sessionParams.homeServerHost ?: "")
+
+ runBlocking {
+ roomIdList.forEach { entry ->
+ syncedSpace!!.addChildren(entry.first, viaServers, null, true)
+ }
+ }
+
+ runBlocking {
+ roomIdList.forEach {
+ session.spaceService().setSpaceParent(it.first, spaceId, it.second, viaServers)
+ }
+ delay(400)
+ }
+
+ roomIdList.forEach {
+ val parents = session.getRoom(it.first)?.roomSummary()?.spaceParents
+ val canonicalParents = session.getRoom(it.first)?.roomSummary()?.spaceParents?.filter { it.canonical == true }
+
+ assertNotNull(parents)
+ assertEquals(1, parents.size, "Unexpected number of parent")
+ assertEquals(spaceName, parents.first().roomSummary?.name, "Unexpected parent name ")
+ assertEquals(if (it.second) 1 else 0, canonicalParents?.size ?: 0, "Parent of ${it.first} should be canonical ${it.second}")
+ }
+ }
+
+ @Test
+ fun testFilteringBySpace() {
+ val session = commonTestHelper.createAccount("John", SessionTestParams(true))
+
+ val spaceAInfo = createPublicSpace(session, "SpaceA", listOf(
+ Triple("A1", true /*auto-join*/, true/*canonical*/),
+ Triple("A2", true, true)
+ ))
+
+ val spaceBInfo = createPublicSpace(session, "SpaceB", listOf(
+ Triple("B1", true /*auto-join*/, true/*canonical*/),
+ Triple("B2", true, true),
+ Triple("B3", true, true)
+ ))
+
+ val spaceCInfo = createPublicSpace(session, "SpaceC", listOf(
+ Triple("C1", true /*auto-join*/, true/*canonical*/),
+ Triple("C2", true, true)
+ ))
+
+ // add C as a subspace of A
+ val spaceA = session.spaceService().getSpace(spaceAInfo.spaceId)
+ val viaServers = listOf(session.sessionParams.homeServerHost ?: "")
+ runBlocking {
+ spaceA!!.addChildren(spaceCInfo.spaceId, viaServers, null, true)
+ session.spaceService().setSpaceParent(spaceCInfo.spaceId, spaceAInfo.spaceId, true, viaServers)
+ }
+
+ // Create orphan rooms
+
+ val orphan1 = runBlocking {
+ session.createRoom(CreateRoomParams().apply { name = "O1" })
+ }
+ val orphan2 = runBlocking {
+ session.createRoom(CreateRoomParams().apply { name = "O2" })
+ }
+
+ val allRooms = session.getRoomSummaries(roomSummaryQueryParams { excludeType = listOf(RoomType.SPACE) })
+
+ assertEquals(9, allRooms.size, "Unexpected number of rooms")
+
+ val orphans = session.getFlattenRoomSummaryChildrenOf(null)
+
+ assertEquals(2, orphans.size, "Unexpected number of orphan rooms")
+ assertTrue(orphans.indexOfFirst { it.roomId == orphan1 } != -1, "O1 should be an orphan")
+ assertTrue(orphans.indexOfFirst { it.roomId == orphan2 } != -1, "O2 should be an orphan ${orphans.map { it.name }}")
+
+ val aChildren = session.getFlattenRoomSummaryChildrenOf(spaceAInfo.spaceId)
+
+ assertEquals(4, aChildren.size, "Unexpected number of flatten child rooms")
+ assertTrue(aChildren.indexOfFirst { it.name == "A1" } != -1, "A1 should be a child of A")
+ assertTrue(aChildren.indexOfFirst { it.name == "A2" } != -1, "A2 should be a child of A")
+ assertTrue(aChildren.indexOfFirst { it.name == "C1" } != -1, "CA should be a grand child of A")
+ assertTrue(aChildren.indexOfFirst { it.name == "C2" } != -1, "A1 should be a grand child of A")
+
+ // Add a non canonical child and check that it does not appear as orphan
+ val a3 = runBlocking {
+ session.createRoom(CreateRoomParams().apply { name = "A3" })
+ }
+ runBlocking {
+ spaceA!!.addChildren(a3, viaServers, null, false)
+ delay(400)
+ // here we do not set the parent!!
+ }
+
+ val orphansUpdate = session.getFlattenRoomSummaryChildrenOf(null)
+ assertEquals(2, orphansUpdate.size, "Unexpected number of orphan rooms ${orphansUpdate.map { it.name }}")
+ }
+
+ @Test
+ fun testBreakCycle() {
+ val session = commonTestHelper.createAccount("John", SessionTestParams(true))
+
+ val spaceAInfo = createPublicSpace(session, "SpaceA", listOf(
+ Triple("A1", true /*auto-join*/, true/*canonical*/),
+ Triple("A2", true, true)
+ ))
+
+ val spaceCInfo = createPublicSpace(session, "SpaceC", listOf(
+ Triple("C1", true /*auto-join*/, true/*canonical*/),
+ Triple("C2", true, true)
+ ))
+
+ // add C as a subspace of A
+ val spaceA = session.spaceService().getSpace(spaceAInfo.spaceId)
+ val viaServers = listOf(session.sessionParams.homeServerHost ?: "")
+ runBlocking {
+ spaceA!!.addChildren(spaceCInfo.spaceId, viaServers, null, true)
+ session.spaceService().setSpaceParent(spaceCInfo.spaceId, spaceAInfo.spaceId, true, viaServers)
+ }
+
+ // add back A as subspace of C
+ runBlocking {
+ val spaceC = session.spaceService().getSpace(spaceCInfo.spaceId)
+ spaceC!!.addChildren(spaceAInfo.spaceId, viaServers, null, true)
+ }
+
+ Thread.sleep(1000)
+
+ // A -> C -> A
+
+ val aChildren = session.getFlattenRoomSummaryChildrenOf(spaceAInfo.spaceId)
+
+ assertEquals(4, aChildren.size, "Unexpected number of flatten child rooms ${aChildren.map { it.name }}")
+ assertTrue(aChildren.indexOfFirst { it.name == "A1" } != -1, "A1 should be a child of A")
+ assertTrue(aChildren.indexOfFirst { it.name == "A2" } != -1, "A2 should be a child of A")
+ assertTrue(aChildren.indexOfFirst { it.name == "C1" } != -1, "CA should be a grand child of A")
+ assertTrue(aChildren.indexOfFirst { it.name == "C2" } != -1, "A1 should be a grand child of A")
+ }
+
+ @Test
+ fun testLiveFlatChildren() {
+ val session = commonTestHelper.createAccount("John", SessionTestParams(true))
+
+ val spaceAInfo = createPublicSpace(session, "SpaceA", listOf(
+ Triple("A1", true /*auto-join*/, true/*canonical*/),
+ Triple("A2", true, true)
+ ))
+
+ val spaceBInfo = createPublicSpace(session, "SpaceB", listOf(
+ Triple("B1", true /*auto-join*/, true/*canonical*/),
+ Triple("B2", true, true),
+ Triple("B3", true, true)
+ ))
+
+ // add B as a subspace of A
+ val spaceA = session.spaceService().getSpace(spaceAInfo.spaceId)
+ val viaServers = listOf(session.sessionParams.homeServerHost ?: "")
+ runBlocking {
+ spaceA!!.addChildren(spaceBInfo.spaceId, viaServers, null, true)
+ session.spaceService().setSpaceParent(spaceBInfo.spaceId, spaceAInfo.spaceId, true, viaServers)
+ }
+
+ val flatAChildren = runBlocking(Dispatchers.Main) {
+ session.getFlattenRoomSummaryChildrenOfLive(spaceAInfo.spaceId)
+ }
+
+ commonTestHelper.waitWithLatch { latch ->
+
+ val childObserver = object : Observer> {
+ override fun onChanged(children: List?) {
+// Log.d("## TEST", "Space A flat children update : ${children?.map { it.name }}")
+ System.out.println("## TEST | Space A flat children update : ${children?.map { it.name }}")
+ if (children?.indexOfFirst { it.name == "C1" } != -1
+ && children?.indexOfFirst { it.name == "C2" } != -1
+ ) {
+ // B1 has been added live!
+ latch.countDown()
+ flatAChildren.removeObserver(this)
+ }
+ }
+ }
+
+ val spaceCInfo = createPublicSpace(session, "SpaceC", listOf(
+ Triple("C1", true /*auto-join*/, true/*canonical*/),
+ Triple("C2", true, true)
+ ))
+
+ // add C as subspace of B
+ runBlocking {
+ val spaceB = session.spaceService().getSpace(spaceBInfo.spaceId)
+ spaceB!!.addChildren(spaceCInfo.spaceId, viaServers, null, true)
+ }
+
+ // C1 and C2 should be in flatten child of A now
+
+ GlobalScope.launch(Dispatchers.Main) { flatAChildren.observeForever(childObserver) }
+ }
+
+ // Test part one of the rooms
+
+ val bRoomId = spaceBInfo.roomIds.first()
+ val bRoom = session.getRoom(bRoomId)
+
+ commonTestHelper.waitWithLatch { latch ->
+
+ val childObserver = object : Observer> {
+ override fun onChanged(children: List?) {
+ System.out.println("## TEST | Space A flat children update : ${children?.map { it.name }}")
+ if (children?.any { it.roomId == bRoomId } == false) {
+ // B1 has been added live!
+ latch.countDown()
+ flatAChildren.removeObserver(this)
+ }
+ }
+ }
+
+ // part from b room
+ runBlocking {
+ bRoom!!.leave(null)
+ }
+ // The room should have disapear from flat children
+ GlobalScope.launch(Dispatchers.Main) { flatAChildren.observeForever(childObserver) }
+ }
+ }
+
+ data class TestSpaceCreationResult(
+ val spaceId: String,
+ val roomIds: List
+ )
+
+ private fun createPublicSpace(session: Session,
+ spaceName: String,
+ childInfo: List>
+ /** Name, auto-join, canonical*/
+ ): TestSpaceCreationResult {
+ val spaceId = runBlocking {
+ session.spaceService().createSpace(spaceName, "Test Topic", null, true)
+ }
+
+ val syncedSpace = session.spaceService().getSpace(spaceId)
+ val viaServers = listOf(session.sessionParams.homeServerHost ?: "")
+
+ val roomIds =
+ childInfo.map { entry ->
+ runBlocking {
+ session.createRoom(CreateRoomParams().apply { name = entry.first })
+ }
+ }
+
+ roomIds.forEachIndexed { index, roomId ->
+ runBlocking {
+ syncedSpace!!.addChildren(roomId, viaServers, null, childInfo[index].second)
+ val canonical = childInfo[index].third
+ if (canonical != null) {
+ session.spaceService().setSpaceParent(roomId, spaceId, canonical, viaServers)
+ }
+ }
+ }
+ return TestSpaceCreationResult(spaceId, roomIds)
+ }
+
+ @Test
+ fun testRootSpaces() {
+ val session = commonTestHelper.createAccount("John", SessionTestParams(true))
+
+ val spaceAInfo = createPublicSpace(session, "SpaceA", listOf(
+ Triple("A1", true /*auto-join*/, true/*canonical*/),
+ Triple("A2", true, true)
+ ))
+
+ val spaceBInfo = createPublicSpace(session, "SpaceB", listOf(
+ Triple("B1", true /*auto-join*/, true/*canonical*/),
+ Triple("B2", true, true),
+ Triple("B3", true, true)
+ ))
+
+ val spaceCInfo = createPublicSpace(session, "SpaceC", listOf(
+ Triple("C1", true /*auto-join*/, true/*canonical*/),
+ Triple("C2", true, true)
+ ))
+
+ val viaServers = listOf(session.sessionParams.homeServerHost ?: "")
+
+ // add C as subspace of B
+ runBlocking {
+ val spaceB = session.spaceService().getSpace(spaceBInfo.spaceId)
+ spaceB!!.addChildren(spaceCInfo.spaceId, viaServers, null, true)
+ }
+
+ Thread.sleep(2000)
+ // + A
+ // a1, a2
+ // + B
+ // b1, b2, b3
+ // + C
+ // + c1, c2
+
+ val rootSpaces = session.spaceService().getRootSpaceSummaries()
+
+ assertEquals(2, rootSpaces.size, "Unexpected number of root spaces ${rootSpaces.map { it.name }}")
+ }
+}
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/data/LoginFlowResult.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/data/LoginFlowResult.kt
index f1f9ba3916..7d1407c0d8 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/data/LoginFlowResult.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/data/LoginFlowResult.kt
@@ -16,12 +16,10 @@
package org.matrix.android.sdk.api.auth.data
-sealed class LoginFlowResult {
- data class Success(
- val supportedLoginTypes: List,
- val ssoIdentityProviders: List?,
- val isLoginAndRegistrationSupported: Boolean,
- val homeServerUrl: String,
- val isOutdatedHomeserver: Boolean
- ) : LoginFlowResult()
-}
+data class LoginFlowResult(
+ val supportedLoginTypes: List,
+ val ssoIdentityProviders: List?,
+ val isLoginAndRegistrationSupported: Boolean,
+ val homeServerUrl: String,
+ val isOutdatedHomeserver: Boolean
+)
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/registration/RegistrationWizard.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/registration/RegistrationWizard.kt
index 38a5a77291..f059bf26c4 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/registration/RegistrationWizard.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/registration/RegistrationWizard.kt
@@ -20,7 +20,9 @@ interface RegistrationWizard {
suspend fun getRegistrationFlow(): RegistrationResult
- suspend fun createAccount(userName: String, password: String, initialDeviceDisplayName: String?): RegistrationResult
+ suspend fun createAccount(userName: String?,
+ password: String?,
+ initialDeviceDisplayName: String?): RegistrationResult
suspend fun performReCaptcha(response: String): RegistrationResult
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/failure/Failure.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/failure/Failure.kt
index b241903364..8f1bbb6941 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/failure/Failure.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/failure/Failure.kt
@@ -32,7 +32,6 @@ import java.io.IOException
*/
sealed class Failure(cause: Throwable? = null) : Throwable(cause = cause) {
data class Unknown(val throwable: Throwable? = null) : Failure(throwable)
- data class Cancelled(val throwable: Throwable? = null) : Failure(throwable)
data class UnrecognizedCertificateFailure(val url: String, val fingerprint: Fingerprint) : Failure()
data class NetworkConnection(val ioException: IOException? = null) : Failure(ioException)
data class ServerError(val error: MatrixError, val httpCode: Int) : Failure(RuntimeException(error.toString()))
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/query/ActiveSpaceFilter.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/query/ActiveSpaceFilter.kt
new file mode 100644
index 0000000000..48619b9394
--- /dev/null
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/query/ActiveSpaceFilter.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.api.query
+
+sealed class ActiveSpaceFilter {
+ object None : ActiveSpaceFilter()
+ data class ActiveSpace(val currentSpaceId: String?) : ActiveSpaceFilter()
+ data class ExcludeSpace(val spaceId: String) : ActiveSpaceFilter()
+}
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 a15799d862..86252665a6 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
@@ -48,6 +48,7 @@ import org.matrix.android.sdk.api.session.search.SearchService
import org.matrix.android.sdk.api.session.securestorage.SecureStorageService
import org.matrix.android.sdk.api.session.securestorage.SharedSecretStorageService
import org.matrix.android.sdk.api.session.signout.SignOutService
+import org.matrix.android.sdk.api.session.space.SpaceService
import org.matrix.android.sdk.api.session.sync.FilterService
import org.matrix.android.sdk.api.session.sync.SyncState
import org.matrix.android.sdk.api.session.terms.TermsService
@@ -227,6 +228,11 @@ interface Session :
*/
fun thirdPartyService(): ThirdPartyService
+ /**
+ * Returns the space service associated with the session
+ */
+ fun spaceService(): SpaceService
+
/**
* Add a listener to the session.
* @param listener the listener to add.
@@ -249,13 +255,13 @@ interface Session :
/**
* A global session listener to get notified for some events.
*/
- interface Listener {
+ interface Listener : SessionLifecycleObserver {
/**
* Possible cases:
* - The access token is not valid anymore,
* - a M_CONSENT_NOT_GIVEN error has been received from the homeserver
*/
- fun onGlobalError(globalError: GlobalError)
+ fun onGlobalError(session: Session, globalError: GlobalError)
}
val sharedSecretStorageService: SharedSecretStorageService
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/SessionLifecycleObserver.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/SessionLifecycleObserver.kt
similarity index 80%
rename from matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/SessionLifecycleObserver.kt
rename to matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/SessionLifecycleObserver.kt
index cb37fbec75..b76e454e4b 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/SessionLifecycleObserver.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/SessionLifecycleObserver.kt
@@ -14,20 +14,19 @@
* limitations under the License.
*/
-package org.matrix.android.sdk.internal.session
+package org.matrix.android.sdk.api.session
import androidx.annotation.MainThread
/**
* This defines methods associated with some lifecycle events of a session.
- * A list of SessionLifecycle will be injected into [DefaultSession]
*/
-internal interface SessionLifecycleObserver {
+interface SessionLifecycleObserver {
/*
Called when the session is opened
*/
@MainThread
- fun onSessionStarted() {
+ fun onSessionStarted(session: Session) {
// noop
}
@@ -35,7 +34,7 @@ internal interface SessionLifecycleObserver {
Called when the session is cleared
*/
@MainThread
- fun onClearCache() {
+ fun onClearCache(session: Session) {
// noop
}
@@ -43,7 +42,7 @@ internal interface SessionLifecycleObserver {
Called when the session is closed
*/
@MainThread
- fun onSessionStopped() {
+ fun onSessionStopped(session: Session) {
// noop
}
}
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 a21a44efcf..7f64822e21 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
@@ -53,6 +53,12 @@ object EventType {
const val STATE_ROOM_GUEST_ACCESS = "m.room.guest_access"
const val STATE_ROOM_POWER_LEVELS = "m.room.power_levels"
+ // const val STATE_SPACE_CHILD = "m.space.child"
+ const val STATE_SPACE_CHILD = "org.matrix.msc1772.space.child"
+
+ // const val STATE_SPACE_PARENT = "m.space.parent"
+ const val STATE_SPACE_PARENT = "org.matrix.msc1772.space.parent"
+
/**
* Note that this Event has been deprecated, see
* - https://matrix.org/docs/spec/client_server/r0.6.1#historical-events
@@ -75,6 +81,7 @@ object EventType {
const val CALL_NEGOTIATE = "m.call.negotiate"
const val CALL_REJECT = "m.call.reject"
const val CALL_HANGUP = "m.call.hangup"
+
// 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/file/FileService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/file/FileService.kt
index adfdc2498e..23dc1e0ba8 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/file/FileService.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/file/FileService.kt
@@ -29,14 +29,19 @@ import java.io.File
*/
interface FileService {
- enum class FileState {
- IN_CACHE,
- DOWNLOADING,
- UNKNOWN
+ sealed class FileState {
+ /**
+ * The original file is in cache, but the decrypted files can be deleted for security reason.
+ * To decrypt the file again, call [downloadFile], the encrypted file will not be downloaded again
+ * @param decryptedFileInCache true if the decrypted file is available. Always true for clear files.
+ */
+ data class InCache(val decryptedFileInCache: Boolean) : FileState()
+ object Downloading : FileState()
+ object Unknown : FileState()
}
/**
- * Download a file.
+ * Download a file if necessary and ensure that if the file is encrypted, the file is decrypted.
* Result will be a decrypted file, stored in the cache folder. url parameter will be used to create unique filename to avoid name collision.
*/
suspend fun downloadFile(fileName: String,
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/pushers/PushersService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/pushers/PushersService.kt
index 9ea820f5b3..a5ec100f64 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/pushers/PushersService.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/pushers/PushersService.kt
@@ -66,12 +66,13 @@ interface PushersService {
/**
* Directly ask the push gateway to send a push to this device
+ * If successful, the push gateway has accepted the request. In this case, the app should receive a Push with the provided eventId.
+ * In case of error, PusherRejected will be thrown. In this case it means that the pushkey is not valid.
+ *
* @param url the push gateway url (full path)
* @param appId the application id
* @param pushkey the FCM token
* @param eventId the eventId which will be sent in the Push message. Use a fake eventId.
- * @param callback callback to know if the push gateway has accepted the request. In this case, the app should receive a Push with the provided eventId.
- * In case of error, PusherRejected failure can happen. In this case it means that the pushkey is not valid.
*/
suspend fun testPush(url: String,
appId: String,
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 257c83564e..f3eeb902a8 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
@@ -34,6 +34,7 @@ import org.matrix.android.sdk.api.session.room.timeline.TimelineService
import org.matrix.android.sdk.api.session.room.typing.TypingService
import org.matrix.android.sdk.api.session.room.uploads.UploadsService
import org.matrix.android.sdk.api.session.search.SearchResult
+import org.matrix.android.sdk.api.session.space.Space
import org.matrix.android.sdk.api.util.Optional
/**
@@ -91,4 +92,9 @@ interface Room :
beforeLimit: Int,
afterLimit: Int,
includeProfile: Boolean): SearchResult
+
+ /**
+ * Use this room as a Space, if the type is correct.
+ */
+ fun asSpace(): Space?
}
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 af5a8a8091..fdcd319c93 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
@@ -18,15 +18,14 @@ package org.matrix.android.sdk.api.session.room
import androidx.lifecycle.LiveData
import androidx.paging.PagedList
-import org.matrix.android.sdk.api.MatrixCallback
import org.matrix.android.sdk.api.session.events.model.Event
import org.matrix.android.sdk.api.session.room.members.ChangeMembershipState
+import org.matrix.android.sdk.api.session.room.model.Membership
import org.matrix.android.sdk.api.session.room.model.RoomMemberSummary
import org.matrix.android.sdk.api.session.room.model.RoomSummary
import org.matrix.android.sdk.api.session.room.model.create.CreateRoomParams
import org.matrix.android.sdk.api.session.room.peeking.PeekResult
import org.matrix.android.sdk.api.session.room.summary.RoomAggregateNotificationCount
-import org.matrix.android.sdk.api.util.Cancelable
import org.matrix.android.sdk.api.util.Optional
import org.matrix.android.sdk.internal.session.room.alias.RoomAliasDescription
@@ -38,22 +37,19 @@ interface RoomService {
/**
* Create a room asynchronously
*/
- fun createRoom(createRoomParams: CreateRoomParams,
- callback: MatrixCallback): Cancelable
+ suspend fun createRoom(createRoomParams: CreateRoomParams): String
/**
* Create a direct room asynchronously. This is a facility method to create a direct room with the necessary parameters
*/
- fun createDirectRoom(otherUserId: String,
- callback: MatrixCallback): Cancelable {
+ suspend fun createDirectRoom(otherUserId: String): String {
return createRoom(
CreateRoomParams()
.apply {
invitedUserIds.add(otherUserId)
setDirectMessage()
enableEncryptionIfInvitedUsersSupportIt = true
- },
- callback
+ }
)
}
@@ -63,10 +59,9 @@ interface RoomService {
* @param reason optional reason for joining the room
* @param viaServers the servers to attempt to join the room through. One of the servers must be participating in the room.
*/
- fun joinRoom(roomIdOrAlias: String,
- reason: String? = null,
- viaServers: List = emptyList(),
- callback: MatrixCallback): Cancelable
+ suspend fun joinRoom(roomIdOrAlias: String,
+ reason: String? = null,
+ viaServers: List = emptyList())
/**
* Get a room from a roomId
@@ -112,20 +107,18 @@ interface RoomService {
* Inform the Matrix SDK that a room is displayed.
* The SDK will update the breadcrumbs in the user account data
*/
- fun onRoomDisplayed(roomId: String): Cancelable
+ suspend fun onRoomDisplayed(roomId: String)
/**
* Mark all rooms as read
*/
- fun markAllAsRead(roomIds: List,
- callback: MatrixCallback): Cancelable
+ suspend fun markAllAsRead(roomIds: List)
/**
* Resolve a room alias to a room ID.
*/
- fun getRoomIdByAlias(roomAlias: String,
- searchOnServer: Boolean,
- callback: MatrixCallback>): Cancelable
+ suspend fun getRoomIdByAlias(roomAlias: String,
+ searchOnServer: Boolean): Optional
/**
* Delete a room alias
@@ -172,26 +165,28 @@ interface RoomService {
/**
* Get some state events about a room
*/
- fun getRoomState(roomId: String, callback: MatrixCallback>)
+ suspend fun getRoomState(roomId: String): List
/**
* Use this if you want to get information from a room that you are not yet in (or invited)
* It might be possible to get some information on this room if it is public or if guest access is allowed
* This call will try to gather some information on this room, but it could fail and get nothing more
*/
- fun peekRoom(roomIdOrAlias: String, callback: MatrixCallback)
+ suspend fun peekRoom(roomIdOrAlias: String): PeekResult
/**
* TODO Doc
*/
fun getPagedRoomSummariesLive(queryParams: RoomSummaryQueryParams,
- pagedListConfig: PagedList.Config = defaultPagedListConfig): LiveData>
+ pagedListConfig: PagedList.Config = defaultPagedListConfig,
+ sortOrder: RoomSortOrder = RoomSortOrder.ACTIVITY): LiveData>
/**
* TODO Doc
*/
fun getFilteredPagedRoomSummariesLive(queryParams: RoomSummaryQueryParams,
- pagedListConfig: PagedList.Config = defaultPagedListConfig): UpdatableFilterLivePageResult
+ pagedListConfig: PagedList.Config = defaultPagedListConfig,
+ sortOrder: RoomSortOrder = RoomSortOrder.ACTIVITY): UpdatableLivePageResult
/**
* TODO Doc
@@ -205,4 +200,12 @@ interface RoomService {
.setEnablePlaceholders(false)
.setPrefetchDistance(10)
.build()
+
+ fun getFlattenRoomSummaryChildrenOf(spaceId: String?, memberships: List = Membership.activeMemberships()) : List
+
+ /**
+ * Returns all the children of this space, as LiveData
+ */
+ fun getFlattenRoomSummaryChildrenOfLive(spaceId: String?,
+ memberships: List = Membership.activeMemberships()): LiveData>
}
diff --git a/vector/src/main/java/im/vector/app/features/grouplist/GroupListAction.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/RoomSortOrder.kt
similarity index 62%
rename from vector/src/main/java/im/vector/app/features/grouplist/GroupListAction.kt
rename to matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/RoomSortOrder.kt
index 4d974b8ce8..36da242527 100644
--- a/vector/src/main/java/im/vector/app/features/grouplist/GroupListAction.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/RoomSortOrder.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2019 New Vector Ltd
+ * 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.
@@ -12,14 +12,12 @@
* 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 im.vector.app.features.grouplist
+package org.matrix.android.sdk.api.session.room
-import im.vector.app.core.platform.VectorViewModelAction
-import org.matrix.android.sdk.api.session.group.model.GroupSummary
-
-sealed class GroupListAction : VectorViewModelAction {
- data class SelectGroup(val groupSummary: GroupSummary) : GroupListAction()
+enum class RoomSortOrder {
+ NAME,
+ ACTIVITY,
+ NONE
}
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 7e04ebb5f2..42dbecdaad 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
@@ -16,15 +16,35 @@
package org.matrix.android.sdk.api.session.room
+import org.matrix.android.sdk.api.query.ActiveSpaceFilter
import org.matrix.android.sdk.api.query.QueryStringValue
import org.matrix.android.sdk.api.query.RoomCategoryFilter
import org.matrix.android.sdk.api.query.RoomTagQueryFilter
import org.matrix.android.sdk.api.session.room.model.Membership
+import org.matrix.android.sdk.api.session.room.model.RoomType
+import org.matrix.android.sdk.api.session.space.SpaceSummaryQueryParams
fun roomSummaryQueryParams(init: (RoomSummaryQueryParams.Builder.() -> Unit) = {}): RoomSummaryQueryParams {
return RoomSummaryQueryParams.Builder().apply(init).build()
}
+fun spaceSummaryQueryParams(init: (RoomSummaryQueryParams.Builder.() -> Unit) = {}): SpaceSummaryQueryParams {
+ return RoomSummaryQueryParams.Builder()
+ .apply(init)
+ .apply {
+ includeType = listOf(RoomType.SPACE)
+ excludeType = null
+ roomCategoryFilter = RoomCategoryFilter.ONLY_ROOMS
+ }
+ .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]
@@ -35,7 +55,11 @@ data class RoomSummaryQueryParams(
val canonicalAlias: QueryStringValue,
val memberships: List,
val roomCategoryFilter: RoomCategoryFilter?,
- val roomTagQueryFilter: RoomTagQueryFilter?
+ val roomTagQueryFilter: RoomTagQueryFilter?,
+ val excludeType: List?,
+ val includeType: List?,
+ val activeSpaceId: ActiveSpaceFilter?,
+ var activeGroupId: String? = null
) {
class Builder {
@@ -46,6 +70,10 @@ data class RoomSummaryQueryParams(
var memberships: List = Membership.all()
var roomCategoryFilter: RoomCategoryFilter? = RoomCategoryFilter.ALL
var roomTagQueryFilter: RoomTagQueryFilter? = null
+ var excludeType: List? = listOf(RoomType.SPACE)
+ var includeType: List? = null
+ var activeSpaceId: ActiveSpaceFilter = ActiveSpaceFilter.None
+ var activeGroupId: String? = null
fun build() = RoomSummaryQueryParams(
roomId = roomId,
@@ -53,7 +81,11 @@ data class RoomSummaryQueryParams(
canonicalAlias = canonicalAlias,
memberships = memberships,
roomCategoryFilter = roomCategoryFilter,
- roomTagQueryFilter = roomTagQueryFilter
+ roomTagQueryFilter = roomTagQueryFilter,
+ excludeType = excludeType,
+ includeType = includeType,
+ activeSpaceId = activeSpaceId,
+ activeGroupId = activeGroupId
)
}
}
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/UpdatableFilterLivePageResult.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/UpdatableLivePageResult.kt
similarity index 72%
rename from matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/UpdatableFilterLivePageResult.kt
rename to matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/UpdatableLivePageResult.kt
index 71b3c665e7..b83f57f5ef 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/UpdatableFilterLivePageResult.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/UpdatableLivePageResult.kt
@@ -20,8 +20,16 @@ import androidx.lifecycle.LiveData
import androidx.paging.PagedList
import org.matrix.android.sdk.api.session.room.model.RoomSummary
-interface UpdatableFilterLivePageResult {
+interface UpdatableLivePageResult {
val livePagedList: LiveData>
- fun updateQuery(queryParams: RoomSummaryQueryParams)
+ fun updateQuery(builder: (RoomSummaryQueryParams) -> RoomSummaryQueryParams)
+
+ val liveBoundaries: LiveData
}
+
+data class ResultBoundaries(
+ val frontLoaded: Boolean = false,
+ val endLoaded: Boolean = false,
+ val zeroItemLoaded: Boolean = false
+)
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/alias/RoomAliasError.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/alias/RoomAliasError.kt
index d2cb7c58a9..1102eda11c 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/alias/RoomAliasError.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/alias/RoomAliasError.kt
@@ -17,7 +17,7 @@
package org.matrix.android.sdk.api.session.room.alias
sealed class RoomAliasError : Throwable() {
- object AliasEmpty : RoomAliasError()
+ object AliasIsBlank : RoomAliasError()
object AliasNotAvailable : RoomAliasError()
object AliasInvalid : RoomAliasError()
}
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/failure/CreateRoomFailure.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/failure/CreateRoomFailure.kt
index 208cdd4556..deab0ca3e7 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/failure/CreateRoomFailure.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/failure/CreateRoomFailure.kt
@@ -21,7 +21,7 @@ import org.matrix.android.sdk.api.failure.MatrixError
import org.matrix.android.sdk.api.session.room.alias.RoomAliasError
sealed class CreateRoomFailure : Failure.FeatureFailure() {
- object CreatedWithTimeout : CreateRoomFailure()
+ data class CreatedWithTimeout(val roomID: String) : CreateRoomFailure()
data class CreatedWithFederationFailure(val matrixError: MatrixError) : CreateRoomFailure()
data class AliasError(val aliasError: RoomAliasError) : CreateRoomFailure()
}
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/PowerLevelsContent.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/PowerLevelsContent.kt
index e778f5740d..5c46db7166 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/PowerLevelsContent.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/PowerLevelsContent.kt
@@ -28,43 +28,43 @@ data class PowerLevelsContent(
/**
* The level required to ban a user. Defaults to 50 if unspecified.
*/
- @Json(name = "ban") val ban: Int = Role.Moderator.value,
+ @Json(name = "ban") val ban: Int? = null,
/**
* The level required to kick a user. Defaults to 50 if unspecified.
*/
- @Json(name = "kick") val kick: Int = Role.Moderator.value,
+ @Json(name = "kick") val kick: Int? = null,
/**
* The level required to invite a user. Defaults to 50 if unspecified.
*/
- @Json(name = "invite") val invite: Int = Role.Moderator.value,
+ @Json(name = "invite") val invite: Int? = null,
/**
* The level required to redact an event. Defaults to 50 if unspecified.
*/
- @Json(name = "redact") val redact: Int = Role.Moderator.value,
+ @Json(name = "redact") val redact: Int? = null,
/**
* The default level required to send message events. Can be overridden by the events key. Defaults to 0 if unspecified.
*/
- @Json(name = "events_default") val eventsDefault: Int = Role.Default.value,
+ @Json(name = "events_default") val eventsDefault: Int? = null,
/**
* The level required to send specific event types. This is a mapping from event type to power level required.
*/
- @Json(name = "events") val events: Map = emptyMap(),
+ @Json(name = "events") val events: Map? = null,
/**
* The default power level for every user in the room, unless their user_id is mentioned in the users key. Defaults to 0 if unspecified.
*/
- @Json(name = "users_default") val usersDefault: Int = Role.Default.value,
+ @Json(name = "users_default") val usersDefault: Int? = null,
/**
* The power levels for specific users. This is a mapping from user_id to power level for that user.
*/
- @Json(name = "users") val users: Map = emptyMap(),
+ @Json(name = "users") val users: Map? = null,
/**
* The default level required to send state events. Can be overridden by the events key. Defaults to 50 if unspecified.
*/
- @Json(name = "state_default") val stateDefault: Int = Role.Moderator.value,
+ @Json(name = "state_default") val stateDefault: Int? = null,
/**
* The power level requirements for specific notification types. This is a mapping from key to power level for that notifications key.
*/
- @Json(name = "notifications") val notifications: Map = emptyMap()
+ @Json(name = "notifications") val notifications: Map? = null
) {
/**
* Return a copy of this content with a new power level for the specified user
@@ -74,7 +74,7 @@ data class PowerLevelsContent(
*/
fun setUserPowerLevel(userId: String, powerLevel: Int?): PowerLevelsContent {
return copy(
- users = users.toMutableMap().apply {
+ users = users.orEmpty().toMutableMap().apply {
if (powerLevel == null || powerLevel == usersDefault) {
remove(userId)
} else {
@@ -91,7 +91,7 @@ data class PowerLevelsContent(
* @return the level, default to Moderator if the key is not found
*/
fun notificationLevel(key: String): Int {
- return when (val value = notifications[key]) {
+ return when (val value = notifications.orEmpty()[key]) {
// the first implementation was a string value
is String -> value.toInt()
is Double -> value.toInt()
@@ -107,3 +107,12 @@ data class PowerLevelsContent(
const val NOTIFICATIONS_ROOM_KEY = "room"
}
}
+
+// Fallback to default value, defined in the Matrix specification
+fun PowerLevelsContent.banOrDefault() = ban ?: Role.Moderator.value
+fun PowerLevelsContent.kickOrDefault() = kick ?: Role.Moderator.value
+fun PowerLevelsContent.inviteOrDefault() = invite ?: Role.Moderator.value
+fun PowerLevelsContent.redactOrDefault() = redact ?: Role.Moderator.value
+fun PowerLevelsContent.eventsDefaultOrDefault() = eventsDefault ?: Role.Default.value
+fun PowerLevelsContent.usersDefaultOrDefault() = usersDefault ?: Role.Default.value
+fun PowerLevelsContent.stateDefaultOrDefault() = stateDefault ?: Role.Moderator.value
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/RoomGuestAccessContent.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/RoomGuestAccessContent.kt
index 0760c6f1b4..020e7ed39e 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/RoomGuestAccessContent.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/RoomGuestAccessContent.kt
@@ -40,7 +40,7 @@ data class RoomGuestAccessContent(
}
@JsonClass(generateAdapter = false)
-enum class GuestAccess {
- @Json(name = "can_join") CanJoin,
- @Json(name = "forbidden") Forbidden
+enum class GuestAccess(val value: String) {
+ @Json(name = "can_join") CanJoin("can_join"),
+ @Json(name = "forbidden") Forbidden("forbidden")
}
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/RoomHistoryVisibility.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/RoomHistoryVisibility.kt
index 06069f2646..e980be93ed 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/RoomHistoryVisibility.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/RoomHistoryVisibility.kt
@@ -16,35 +16,31 @@
package org.matrix.android.sdk.api.session.room.model
-import com.squareup.moshi.Json
-import com.squareup.moshi.JsonClass
-
/**
* Ref: https://matrix.org/docs/spec/client_server/latest#room-history-visibility
*/
-@JsonClass(generateAdapter = false)
enum class RoomHistoryVisibility {
/**
* All events while this is the m.room.history_visibility value may be shared by any
* participating homeserver with anyone, regardless of whether they have ever joined the room.
*/
- @Json(name = "world_readable") WORLD_READABLE,
+ WORLD_READABLE,
/**
* Previous events are always accessible to newly joined members. All events in the
* room are accessible, even those sent when the member was not a part of the room.
*/
- @Json(name = "shared") SHARED,
+ SHARED,
/**
* Events are accessible to newly joined members from the point they were invited onwards.
* Events stop being accessible when the member's state changes to something other than invite or join.
*/
- @Json(name = "invited") INVITED,
+ INVITED,
/**
* Events are accessible to newly joined members from the point they joined the room onwards.
* Events stop being accessible when the member's state changes to something other than join.
*/
- @Json(name = "joined") JOINED
+ JOINED
}
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/RoomJoinRules.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/RoomJoinRules.kt
index f3e8d357f3..a86301a276 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/RoomJoinRules.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/RoomJoinRules.kt
@@ -24,9 +24,10 @@ import com.squareup.moshi.JsonClass
* Enum for [RoomJoinRulesContent] : https://matrix.org/docs/spec/client_server/r0.4.0#m-room-join-rules
*/
@JsonClass(generateAdapter = false)
-enum class RoomJoinRules {
- @Json(name = "public") PUBLIC,
- @Json(name = "invite") INVITE,
- @Json(name = "knock") KNOCK,
- @Json(name = "private") PRIVATE
+enum class RoomJoinRules(val value: String) {
+ @Json(name = "public") PUBLIC("public"),
+ @Json(name = "invite") INVITE("invite"),
+ @Json(name = "knock") KNOCK("knock"),
+ @Json(name = "private") PRIVATE("private"),
+ @Json(name = "restricted") RESTRICTED("restricted")
}
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
new file mode 100644
index 0000000000..7b87bc34d2
--- /dev/null
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/RoomJoinRulesAllowEntry.kt
@@ -0,0 +1,33 @@
+/*
+ * 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
+
+import com.squareup.moshi.Json
+import com.squareup.moshi.JsonClass
+
+@JsonClass(generateAdapter = true)
+data class RoomJoinRulesAllowEntry(
+ /**
+ * space: The room ID of the space to check the membership of.
+ */
+ @Json(name = "space") val spaceID: String,
+ /**
+ * via: A list of servers which may be used to peek for membership of the space.
+ */
+ @Json(name = "via") val via: List
+)
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 8082486b22..33f402cad3 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
@@ -1,5 +1,6 @@
/*
* Copyright 2020 The Matrix.org Foundation C.I.C.
+ * Copyright 2021 The Matrix.org Foundation C.I.C.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -26,14 +27,19 @@ import timber.log.Timber
*/
@JsonClass(generateAdapter = true)
data class RoomJoinRulesContent(
- @Json(name = "join_rule") val _joinRules: String? = null
+ @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
+ */
+ @Json(name = "allow") val allowList: List? = null
) {
val joinRules: RoomJoinRules? = when (_joinRules) {
- "public" -> RoomJoinRules.PUBLIC
- "invite" -> RoomJoinRules.INVITE
- "knock" -> RoomJoinRules.KNOCK
+ "public" -> RoomJoinRules.PUBLIC
+ "invite" -> RoomJoinRules.INVITE
+ "knock" -> RoomJoinRules.KNOCK
"private" -> RoomJoinRules.PRIVATE
- else -> {
+ "restricted" -> RoomJoinRules.RESTRICTED
+ else -> {
Timber.w("Invalid value for RoomJoinRules: `$_joinRules`")
null
}
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/RoomSummary.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/RoomSummary.kt
index 367c157951..68c227afb3 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/RoomSummary.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/RoomSummary.kt
@@ -37,6 +37,7 @@ data class RoomSummary constructor(
val canonicalAlias: String? = null,
val aliases: List = emptyList(),
val isDirect: Boolean = false,
+ val directUserId: String? = null,
val joinedMembersCount: Int? = 0,
val invitedMembersCount: Int? = 0,
val latestPreviewableEvent: TimelineEvent? = null,
@@ -48,6 +49,7 @@ data class RoomSummary constructor(
val hasUnreadMessages: Boolean = false,
val hasUnreadContentMessages: Boolean = false,
val hasUnreadOriginalContentMessages: Boolean = false,
+ val unreadCount: Int? = 0,
val markedUnread: Boolean = false,
val tags: List = emptyList(),
val membership: Membership = Membership.NONE,
@@ -60,7 +62,11 @@ data class RoomSummary constructor(
val inviterId: String? = null,
val breadcrumbsIndex: Int = NOT_IN_BREADCRUMBS,
val roomEncryptionTrustLevel: RoomEncryptionTrustLevel? = null,
- val hasFailedSending: Boolean = false
+ val hasFailedSending: Boolean = false,
+ val roomType: String? = null,
+ val spaceParents: List? = null,
+ val spaceChildren: List? = null,
+ val flattenParentIds: List = emptyList()
) {
val isVersioned: Boolean
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/RoomThirdPartyInviteContent.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/RoomThirdPartyInviteContent.kt
index 56503e3e35..a8a2cfb68b 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/RoomThirdPartyInviteContent.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/RoomThirdPartyInviteContent.kt
@@ -47,7 +47,7 @@ data class RoomThirdPartyInviteContent(
/**
* Keys with which the token may be signed.
*/
- @Json(name = "public_keys") val publicKeys: List? = emptyList()
+ @Json(name = "public_keys") val publicKeys: List?
)
@JsonClass(generateAdapter = true)
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/RoomType.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/RoomType.kt
new file mode 100644
index 0000000000..b4932494f2
--- /dev/null
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/RoomType.kt
@@ -0,0 +1,23 @@
+/*
+ * 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
+
+object RoomType {
+
+ const val SPACE = "org.matrix.msc1772.space" // "m.space"
+// const val MESSAGING = "org.matrix.msc1840.messaging"
+}
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
new file mode 100644
index 0000000000..fd5fbf7bb0
--- /dev/null
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/SpaceChildInfo.kt
@@ -0,0 +1,33 @@
+/*
+ * 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
+
+data class SpaceChildInfo(
+ val childRoomId: String,
+ // We might not know this child at all,
+ // i.e we just know it exists but no info on type/name/etc..
+ val isKnown: Boolean,
+ val roomType: String?,
+ val name: String?,
+ val topic: String?,
+ val avatarUrl: String?,
+ val order: String?,
+ val activeMemberCount: Int?,
+ val autoJoin: Boolean,
+ val viaServers: List,
+ val parentRoomId: String?
+)
diff --git a/vector/src/main/java/im/vector/app/features/grouplist/GroupListViewState.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/SpaceParentInfo.kt
similarity index 58%
rename from vector/src/main/java/im/vector/app/features/grouplist/GroupListViewState.kt
rename to matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/SpaceParentInfo.kt
index 4abcff2f67..5ed81b0646 100644
--- a/vector/src/main/java/im/vector/app/features/grouplist/GroupListViewState.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/SpaceParentInfo.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2019 New Vector Ltd
+ * 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.
@@ -14,14 +14,11 @@
* limitations under the License.
*/
-package im.vector.app.features.grouplist
+package org.matrix.android.sdk.api.session.room.model
-import com.airbnb.mvrx.Async
-import com.airbnb.mvrx.MvRxState
-import com.airbnb.mvrx.Uninitialized
-import org.matrix.android.sdk.api.session.group.model.GroupSummary
-
-data class GroupListViewState(
- val asyncGroups: Async> = Uninitialized,
- val selectedGroup: GroupSummary? = null
-) : MvRxState
+data class SpaceParentInfo(
+ val parentId: String?,
+ val roomSummary: RoomSummary?,
+ val canonical: Boolean?,
+ val viaServers: List
+)
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 80e3741a0c..cd729832eb 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
@@ -18,13 +18,15 @@ package org.matrix.android.sdk.api.session.room.model.create
import android.net.Uri
import org.matrix.android.sdk.api.session.identity.ThreePid
+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
-class CreateRoomParams {
+open class CreateRoomParams {
/**
* A public visibility indicates that the room will be shown in the published room list.
* A private visibility will hide the room from the published room list.
@@ -68,6 +70,11 @@ class CreateRoomParams {
*/
val invite3pids = mutableListOf()
+ /**
+ * Initial Guest Access
+ */
+ var guestAccess: GuestAccess? = null
+
/**
* If set to true, when the room will be created, if cross-signing is enabled and we can get keys for every invited users,
* the encryption will be enabled on the created room
@@ -111,6 +118,17 @@ class CreateRoomParams {
}
}
+ var roomType: String? = null // RoomType.MESSAGING
+ set(value) {
+ field = value
+ if (value != null) {
+ creationContent[CREATION_CONTENT_KEY_ROOM_TYPE] = value
+ } else {
+ // This is the default value, we remove the field
+ creationContent.remove(CREATION_CONTENT_KEY_ROOM_TYPE)
+ }
+ }
+
/**
* The power level content to override in the default power level event
*/
@@ -136,7 +154,12 @@ class CreateRoomParams {
algorithm = MXCRYPTO_ALGORITHM_MEGOLM
}
+ var roomVersion: String? = null
+
+ var joinRuleRestricted: List? = null
+
companion object {
private const val CREATION_CONTENT_KEY_M_FEDERATE = "m.federate"
+ private const val CREATION_CONTENT_KEY_ROOM_TYPE = "org.matrix.msc1772.type"
}
}
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/create/RoomCreateContent.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/create/RoomCreateContent.kt
index 0b595b1b2b..f9d40d5652 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/create/RoomCreateContent.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/create/RoomCreateContent.kt
@@ -26,5 +26,7 @@ import com.squareup.moshi.JsonClass
data class RoomCreateContent(
@Json(name = "creator") val creator: String? = null,
@Json(name = "room_version") val roomVersion: String? = null,
- @Json(name = "predecessor") val predecessor: Predecessor? = null
+ @Json(name = "predecessor") val predecessor: Predecessor? = null,
+ // Defines the room type, see #RoomType (user extensible)
+ @Json(name = "org.matrix.msc1772.type") val type: String? = null
)
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/peeking/PeekResult.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/peeking/PeekResult.kt
index db70dadef3..888950dc12 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/peeking/PeekResult.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/peeking/PeekResult.kt
@@ -16,6 +16,8 @@
package org.matrix.android.sdk.api.session.room.peeking
+import org.matrix.android.sdk.api.util.MatrixItem
+
sealed class PeekResult {
data class Success(
val roomId: String,
@@ -24,7 +26,9 @@ sealed class PeekResult {
val topic: String?,
val avatarUrl: String?,
val numJoinedMembers: Int?,
- val viaServers: List
+ val roomType: String?,
+ val viaServers: List,
+ val someMembers: List?
) : PeekResult()
data class PeekingNotAllowed(
@@ -34,4 +38,6 @@ sealed class PeekResult {
) : PeekResult()
object UnknownAlias : PeekResult()
+
+ fun isSuccess() = this is Success
}
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/powerlevels/PowerLevelsHelper.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/powerlevels/PowerLevelsHelper.kt
index 4f1253c6df..99139723a8 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/powerlevels/PowerLevelsHelper.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/powerlevels/PowerLevelsHelper.kt
@@ -18,6 +18,13 @@
package org.matrix.android.sdk.api.session.room.powerlevels
import org.matrix.android.sdk.api.session.room.model.PowerLevelsContent
+import org.matrix.android.sdk.api.session.room.model.banOrDefault
+import org.matrix.android.sdk.api.session.room.model.eventsDefaultOrDefault
+import org.matrix.android.sdk.api.session.room.model.inviteOrDefault
+import org.matrix.android.sdk.api.session.room.model.kickOrDefault
+import org.matrix.android.sdk.api.session.room.model.redactOrDefault
+import org.matrix.android.sdk.api.session.room.model.stateDefaultOrDefault
+import org.matrix.android.sdk.api.session.room.model.usersDefaultOrDefault
/**
* This class is an helper around PowerLevelsContent.
@@ -31,9 +38,9 @@ class PowerLevelsHelper(private val powerLevelsContent: PowerLevelsContent) {
* @return the power level
*/
fun getUserPowerLevelValue(userId: String): Int {
- return powerLevelsContent.users.getOrElse(userId) {
- powerLevelsContent.usersDefault
- }
+ return powerLevelsContent.users
+ ?.get(userId)
+ ?: powerLevelsContent.usersDefaultOrDefault()
}
/**
@@ -45,7 +52,7 @@ class PowerLevelsHelper(private val powerLevelsContent: PowerLevelsContent) {
fun getUserRole(userId: String): Role {
val value = getUserPowerLevelValue(userId)
// I think we should use powerLevelsContent.usersDefault, but Ganfra told me that it was like that on riot-Web
- return Role.fromValue(value, powerLevelsContent.eventsDefault)
+ return Role.fromValue(value, powerLevelsContent.eventsDefaultOrDefault())
}
/**
@@ -59,11 +66,11 @@ class PowerLevelsHelper(private val powerLevelsContent: PowerLevelsContent) {
fun isUserAllowedToSend(userId: String, isState: Boolean, eventType: String?): Boolean {
return if (userId.isNotEmpty()) {
val powerLevel = getUserPowerLevelValue(userId)
- val minimumPowerLevel = powerLevelsContent.events[eventType]
+ val minimumPowerLevel = powerLevelsContent.events?.get(eventType)
?: if (isState) {
- powerLevelsContent.stateDefault
+ powerLevelsContent.stateDefaultOrDefault()
} else {
- powerLevelsContent.eventsDefault
+ powerLevelsContent.eventsDefaultOrDefault()
}
powerLevel >= minimumPowerLevel
} else false
@@ -76,7 +83,7 @@ class PowerLevelsHelper(private val powerLevelsContent: PowerLevelsContent) {
*/
fun isUserAbleToInvite(userId: String): Boolean {
val powerLevel = getUserPowerLevelValue(userId)
- return powerLevel >= powerLevelsContent.invite
+ return powerLevel >= powerLevelsContent.inviteOrDefault()
}
/**
@@ -86,7 +93,7 @@ class PowerLevelsHelper(private val powerLevelsContent: PowerLevelsContent) {
*/
fun isUserAbleToBan(userId: String): Boolean {
val powerLevel = getUserPowerLevelValue(userId)
- return powerLevel >= powerLevelsContent.ban
+ return powerLevel >= powerLevelsContent.banOrDefault()
}
/**
@@ -96,7 +103,7 @@ class PowerLevelsHelper(private val powerLevelsContent: PowerLevelsContent) {
*/
fun isUserAbleToKick(userId: String): Boolean {
val powerLevel = getUserPowerLevelValue(userId)
- return powerLevel >= powerLevelsContent.kick
+ return powerLevel >= powerLevelsContent.kickOrDefault()
}
/**
@@ -106,6 +113,6 @@ class PowerLevelsHelper(private val powerLevelsContent: PowerLevelsContent) {
*/
fun isUserAbleToRedact(userId: String): Boolean {
val powerLevel = getUserPowerLevelValue(userId)
- return powerLevel >= powerLevelsContent.redact
+ return powerLevel >= powerLevelsContent.redactOrDefault()
}
}
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/summary/RoomAggregateNotificationCount.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/summary/RoomAggregateNotificationCount.kt
index b11cbe6e46..ebd97e7688 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/summary/RoomAggregateNotificationCount.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/summary/RoomAggregateNotificationCount.kt
@@ -22,7 +22,7 @@ data class RoomAggregateNotificationCount(
val unreadCount: Int,
val markedUnreadCount: Int
) {
- val totalCount = notificationCount + highlightCount + markedUnreadCount
+ val totalCount = notificationCount + markedUnreadCount
val isHighlight = highlightCount > 0
val markedUnread = markedUnreadCount > 0
}
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/space/CreateSpaceParams.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/space/CreateSpaceParams.kt
new file mode 100644
index 0000000000..42e6584838
--- /dev/null
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/space/CreateSpaceParams.kt
@@ -0,0 +1,35 @@
+/*
+ * 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.space
+
+import org.matrix.android.sdk.api.session.room.model.PowerLevelsContent
+import org.matrix.android.sdk.api.session.room.model.RoomType
+import org.matrix.android.sdk.api.session.room.model.create.CreateRoomParams
+
+class CreateSpaceParams : CreateRoomParams() {
+
+ init {
+ // Space-rooms are distinguished from regular messaging rooms by the m.room.type of m.space
+ roomType = RoomType.SPACE
+
+ // Space-rooms should be created with a power level for events_default of 100,
+ // to prevent the rooms accidentally/maliciously clogging up with messages from random members of the space.
+ powerLevelContentOverride = PowerLevelsContent(
+ eventsDefault = 100
+ )
+ }
+}
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/space/JoinSpaceResult.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/space/JoinSpaceResult.kt
new file mode 100644
index 0000000000..e8c69977c6
--- /dev/null
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/space/JoinSpaceResult.kt
@@ -0,0 +1,27 @@
+/*
+ * 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
+
+sealed class JoinSpaceResult {
+ object Success : JoinSpaceResult()
+ data class Fail(val error: Throwable) : JoinSpaceResult()
+
+ /** Success fully joined the space, but failed to join all or some of it's rooms */
+ data class PartialSuccess(val failedRooms: Map) : JoinSpaceResult()
+
+ fun isSuccess() = this is Success || this is PartialSuccess
+}
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
new file mode 100644
index 0000000000..9dba4f90af
--- /dev/null
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/space/Space.kt
@@ -0,0 +1,50 @@
+/*
+ * 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.space
+
+import org.matrix.android.sdk.api.session.room.Room
+import org.matrix.android.sdk.api.session.room.model.RoomSummary
+
+interface Space {
+
+ fun asRoom(): Room
+
+ val spaceId: String
+
+ suspend fun leave(reason: String? = null)
+
+ /**
+ * A current snapshot of [RoomSummary] associated with the space
+ */
+ fun spaceSummary(): RoomSummary?
+
+ suspend fun addChildren(roomId: String,
+ viaServers: List,
+ order: String?,
+ autoJoin: Boolean = false,
+ suggested: Boolean? = false)
+
+ suspend fun removeChildren(roomId: String)
+
+ @Throws
+ suspend fun setChildrenOrder(roomId: String, order: String?)
+
+ @Throws
+ suspend fun setChildrenAutoJoin(roomId: String, autoJoin: Boolean)
+
+// fun getChildren() : List
+}
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
new file mode 100644
index 0000000000..fedf38fe06
--- /dev/null
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/space/SpaceService.kt
@@ -0,0 +1,87 @@
+/*
+ * 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.space
+
+import android.net.Uri
+import androidx.lifecycle.LiveData
+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
+
+interface SpaceService {
+
+ /**
+ * Create a space asynchronously
+ * @return the spaceId of the created space
+ */
+ suspend fun createSpace(params: CreateSpaceParams): String
+
+ /**
+ * Just a shortcut for space creation for ease of use
+ */
+ suspend fun createSpace(name: String, topic: String?, avatarUri: Uri?, isPublic: Boolean): String
+
+ /**
+ * Get a space from a roomId
+ * @param spaceId the roomId to look for.
+ * @return a space with spaceId or null if room type is not space
+ */
+ fun getSpace(spaceId: String): Space?
+
+ /**
+ * Try to resolve (peek) rooms and subspace in this space.
+ * Use this call get preview of children of this space, particularly useful to get a
+ * preview of rooms that you did not join yet.
+ */
+ suspend fun peekSpace(spaceId: String): SpacePeekResult
+
+ /**
+ * Get's information of a space by querying the server
+ */
+ suspend fun querySpaceChildren(spaceId: String,
+ suggestedOnly: Boolean? = null,
+ autoJoinedOnly: Boolean? = null): Pair>
+
+ /**
+ * Get a live list of space summaries. This list is refreshed as soon as the data changes.
+ * @return the [LiveData] of List[SpaceSummary]
+ */
+ fun getSpaceSummariesLive(queryParams: SpaceSummaryQueryParams): LiveData>
+
+ fun getSpaceSummaries(spaceSummaryQueryParams: SpaceSummaryQueryParams): List
+
+ suspend fun joinSpace(spaceIdOrAlias: String,
+ reason: String? = null,
+ viaServers: List = emptyList()): JoinSpaceResult
+
+ suspend fun rejectInvite(spaceId: String, reason: String?)
+
+// fun getSpaceParentsOfRoom(roomId: String) : List
+
+ /**
+ * Let this room declare that it has a parent.
+ * @param canonical true if it should be the main parent of this room
+ * In practice, well behaved rooms should only have one canonical parent, but given this is not enforced:
+ * if multiple are present the client should select the one with the lowest room ID, as determined via a lexicographic utf-8 ordering.
+ */
+ suspend fun setSpaceParent(childRoomId: String, parentSpaceId: String, canonical: Boolean, viaServers: List)
+
+ fun getRootSpaceSummaries(): List
+}
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
new file mode 100644
index 0000000000..0c33cfa1e6
--- /dev/null
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/space/model/SpaceChildContent.kt
@@ -0,0 +1,70 @@
+/*
+ * 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.space.model
+
+import com.squareup.moshi.Json
+import com.squareup.moshi.JsonClass
+
+/**
+ * "content": {
+ * "via": ["example.com"],
+ * "order": "abcd",
+ * "default": true
+ * }
+ */
+@JsonClass(generateAdapter = true)
+data class SpaceChildContent(
+ /**
+ * Key which gives a list of candidate servers that can be used to join the room
+ * Children where via is not present are ignored.
+ */
+ @Json(name = "via") val via: List? = null,
+ /**
+ * The order key is a string which is used to provide a default ordering of siblings in the room list.
+ * (Rooms are sorted based on a lexicographic ordering of order values; rooms with no order come last.
+ * orders which are not strings, or do not consist solely of ascii characters in the range \x20 (space) to \x7F (~),
+ * 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,
+
+ /**
+ * If `suggested` is set to `true`, that indicates that the child should be advertised to
+ * members of the space by the client. This could be done by showing them eagerly
+ * in the room list. This is should be ignored if `auto_join` is set to `true`.
+ */
+ @Json(name = "suggested") val suggested: Boolean? = false
+) {
+ /**
+ * Orders which are not strings, or do not consist solely of ascii characters in the range \x20 (space) to \x7F (~),
+ * or consist of more than 50 characters, are forbidden and should be ignored if received.)
+ */
+ fun validOrder(): String? {
+ return order
+ ?.takeIf { it.length <= 50 }
+ ?.takeIf { ORDER_VALID_CHAR_REGEX.matches(it) }
+ }
+
+ companion object {
+ private val ORDER_VALID_CHAR_REGEX = "[ -~]+".toRegex()
+ }
+}
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/space/model/SpaceParentContent.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/space/model/SpaceParentContent.kt
new file mode 100644
index 0000000000..871a494914
--- /dev/null
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/space/model/SpaceParentContent.kt
@@ -0,0 +1,48 @@
+/*
+ * 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.space.model
+
+import com.squareup.moshi.Json
+import com.squareup.moshi.JsonClass
+
+/**
+ * Rooms can claim parents via the m.space.parent state event.
+ * {
+ * "type": "m.space.parent",
+ * "state_key": "!space:example.com",
+ * "content": {
+ * "via": ["example.com"],
+ * "canonical": true,
+ * }
+ * }
+ */
+@JsonClass(generateAdapter = true)
+data class SpaceParentContent(
+ /**
+ * Key which gives a list of candidate servers that can be used to join the parent.
+ * Parents where via is not present are ignored.
+ */
+ @Json(name = "via") val via: List? = null,
+ /**
+ * Canonical determines whether this is the main parent for the space.
+ * When a user joins a room with a canonical parent, clients may switch to view the room
+ * in the context of that space, peeking into it in order to find other rooms and group them together.
+ * In practice, well behaved rooms should only have one canonical parent, but given this is not enforced:
+ * if multiple are present the client should select the one with the lowest room ID, as determined via a lexicographic utf-8 ordering.
+ */
+ @Json(name = "canonical") val canonical: Boolean? = false
+)
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/util/MatrixItem.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/util/MatrixItem.kt
index db229a6453..7b2fae86ef 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/util/MatrixItem.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/util/MatrixItem.kt
@@ -20,6 +20,7 @@ import org.matrix.android.sdk.BuildConfig
import org.matrix.android.sdk.api.session.group.model.GroupSummary
import org.matrix.android.sdk.api.session.room.model.RoomMemberSummary
import org.matrix.android.sdk.api.session.room.model.RoomSummary
+import org.matrix.android.sdk.api.session.room.model.SpaceChildInfo
import org.matrix.android.sdk.api.session.room.model.roomdirectory.PublicRoom
import org.matrix.android.sdk.api.session.room.sender.SenderInfo
import org.matrix.android.sdk.api.session.user.model.User
@@ -157,3 +158,5 @@ fun PublicRoom.toMatrixItem() = MatrixItem.RoomItem(roomId, name ?: getPrimaryAl
fun RoomMemberSummary.toMatrixItem() = MatrixItem.UserItem(userId, displayName, avatarUrl)
fun SenderInfo.toMatrixItem() = MatrixItem.UserItem(userId, disambiguatedDisplayName, avatarUrl)
+
+fun SpaceChildInfo.toMatrixItem() = MatrixItem.RoomItem(childRoomId, name, avatarUrl)
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 c74999b4ab..182b37f2ad 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
@@ -22,6 +22,7 @@ import org.matrix.android.sdk.api.extensions.orFalse
object MimeTypes {
const val Any: String = "*/*"
const val OctetStream = "application/octet-stream"
+ const val Apk = "application/vnd.android.package-archive"
const val Images = "image/*"
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 e26286ad2f..46256f4b81 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
@@ -144,16 +144,14 @@ internal class DefaultAuthenticationService @Inject constructor(
}
return result.fold(
{
- if (it is LoginFlowResult.Success) {
- // The homeserver exists and up to date, keep the config
- // Homeserver url may have been changed, if it was a Riot url
- val alteredHomeServerConnectionConfig = homeServerConnectionConfig.copy(
- homeServerUri = Uri.parse(it.homeServerUrl)
- )
+ // The homeserver exists and up to date, keep the config
+ // Homeserver url may have been changed, if it was a Riot url
+ val alteredHomeServerConnectionConfig = homeServerConnectionConfig.copy(
+ homeServerUri = Uri.parse(it.homeServerUrl)
+ )
- pendingSessionData = PendingSessionData(alteredHomeServerConnectionConfig)
- .also { data -> pendingSessionStore.savePendingSessionData(data) }
- }
+ pendingSessionData = PendingSessionData(alteredHomeServerConnectionConfig)
+ .also { data -> pendingSessionStore.savePendingSessionData(data) }
it
},
{
@@ -307,12 +305,12 @@ internal class DefaultAuthenticationService @Inject constructor(
val loginFlowResponse = executeRequest(null) {
authAPI.getLoginFlows()
}
- return LoginFlowResult.Success(
- loginFlowResponse.flows.orEmpty().mapNotNull { it.type },
- loginFlowResponse.flows.orEmpty().firstOrNull { it.type == LoginFlowTypes.SSO }?.ssoIdentityProvider,
- versions.isLoginAndRegistrationSupportedBySdk(),
- homeServerUrl,
- !versions.isSupportedBySdk()
+ return LoginFlowResult(
+ supportedLoginTypes = loginFlowResponse.flows.orEmpty().mapNotNull { it.type },
+ ssoIdentityProviders = loginFlowResponse.flows.orEmpty().firstOrNull { it.type == LoginFlowTypes.SSO }?.ssoIdentityProvider,
+ isLoginAndRegistrationSupported = versions.isLoginAndRegistrationSupportedBySdk(),
+ homeServerUrl = homeServerUrl,
+ isOutdatedHomeserver = !versions.isSupportedBySdk()
)
}
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/registration/DefaultRegistrationWizard.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/registration/DefaultRegistrationWizard.kt
index 4a3d53a8fc..4a156e74cd 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/registration/DefaultRegistrationWizard.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/auth/registration/DefaultRegistrationWizard.kt
@@ -66,8 +66,8 @@ internal class DefaultRegistrationWizard(
return performRegistrationRequest(params)
}
- override suspend fun createAccount(userName: String,
- password: String,
+ override suspend fun createAccount(userName: String?,
+ password: String?,
initialDeviceDisplayName: String?): RegistrationResult {
val params = RegistrationParams(
username = userName,
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 7eebbd9b2c..4004294d97 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
@@ -44,7 +44,7 @@ data class CryptoDeviceInfo(
*/
fun fingerprint(): String? {
return keys
- ?.takeIf { !deviceId.isBlank() }
+ ?.takeIf { deviceId.isNotBlank() }
?.get("ed25519:$deviceId")
}
@@ -53,7 +53,7 @@ data class CryptoDeviceInfo(
*/
fun identityKey(): String? {
return keys
- ?.takeIf { !deviceId.isBlank() }
+ ?.takeIf { deviceId.isNotBlank() }
?.get("curve25519:$deviceId")
}
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 3c651c27a0..00b8bde5d9 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
@@ -103,7 +103,7 @@ data class MXDeviceInfo(
*/
fun fingerprint(): String? {
return keys
- ?.takeIf { !deviceId.isBlank() }
+ ?.takeIf { deviceId.isNotBlank() }
?.get("ed25519:$deviceId")
}
@@ -112,7 +112,7 @@ data class MXDeviceInfo(
*/
fun identityKey(): String? {
return keys
- ?.takeIf { !deviceId.isBlank() }
+ ?.takeIf { deviceId.isNotBlank() }
?.get("curve25519:$deviceId")
}
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/DatabaseCleaner.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/DatabaseCleaner.kt
index f11ecc5d75..ee58880eb8 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/DatabaseCleaner.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/DatabaseCleaner.kt
@@ -20,6 +20,7 @@ import io.realm.Realm
import io.realm.RealmConfiguration
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
+import org.matrix.android.sdk.api.session.Session
import org.matrix.android.sdk.internal.database.helper.nextDisplayIndex
import org.matrix.android.sdk.internal.database.model.ChunkEntity
import org.matrix.android.sdk.internal.database.model.ChunkEntityFields
@@ -29,7 +30,7 @@ import org.matrix.android.sdk.internal.database.model.TimelineEventEntity
import org.matrix.android.sdk.internal.database.model.TimelineEventEntityFields
import org.matrix.android.sdk.internal.database.model.deleteOnCascade
import org.matrix.android.sdk.internal.di.SessionDatabase
-import org.matrix.android.sdk.internal.session.SessionLifecycleObserver
+import org.matrix.android.sdk.api.session.SessionLifecycleObserver
import org.matrix.android.sdk.internal.session.room.timeline.PaginationDirection
import org.matrix.android.sdk.internal.task.TaskExecutor
import timber.log.Timber
@@ -47,7 +48,7 @@ private const val MIN_NUMBER_OF_EVENTS_BY_CHUNK = 300
internal class DatabaseCleaner @Inject constructor(@SessionDatabase private val realmConfiguration: RealmConfiguration,
private val taskExecutor: TaskExecutor) : SessionLifecycleObserver {
- override fun onSessionStarted() {
+ override fun onSessionStarted(session: Session) {
taskExecutor.executorScope.launch(Dispatchers.Default) {
awaitTransaction(realmConfiguration) { realm ->
val allRooms = realm.where(RoomEntity::class.java).findAll()
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/RealmLiveEntityObserver.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/RealmLiveEntityObserver.kt
index 2a0cd963b2..c602ed7075 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/RealmLiveEntityObserver.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/RealmLiveEntityObserver.kt
@@ -17,7 +17,7 @@
package org.matrix.android.sdk.internal.database
import com.zhuinden.monarchy.Monarchy
-import org.matrix.android.sdk.internal.session.SessionLifecycleObserver
+import org.matrix.android.sdk.api.session.SessionLifecycleObserver
import org.matrix.android.sdk.internal.util.createBackgroundHandler
import io.realm.Realm
import io.realm.RealmChangeListener
@@ -28,6 +28,7 @@ import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.android.asCoroutineDispatcher
import kotlinx.coroutines.cancelChildren
+import org.matrix.android.sdk.api.session.Session
import java.util.concurrent.atomic.AtomicBoolean
import java.util.concurrent.atomic.AtomicReference
@@ -46,7 +47,7 @@ internal abstract class RealmLiveEntityObserver(protected val r
private val backgroundRealm = AtomicReference()
private lateinit var results: AtomicReference>
- override fun onSessionStarted() {
+ override fun onSessionStarted(session: Session) {
if (isStarted.compareAndSet(false, true)) {
BACKGROUND_HANDLER.post {
val realm = Realm.getInstance(realmConfiguration)
@@ -58,7 +59,7 @@ internal abstract class RealmLiveEntityObserver(protected val r
}
}
- override fun onSessionStopped() {
+ override fun onSessionStopped(session: Session) {
if (isStarted.compareAndSet(true, false)) {
BACKGROUND_HANDLER.post {
results.getAndSet(null).removeAllChangeListeners()
@@ -70,7 +71,7 @@ internal abstract class RealmLiveEntityObserver(protected val r
}
}
- override fun onClearCache() {
+ override fun onClearCache(session: Session) {
observerScope.coroutineContext.cancelChildren()
}
}
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/RealmSessionProvider.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/RealmSessionProvider.kt
index f8d5d323a5..52fbabb49f 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/RealmSessionProvider.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/RealmSessionProvider.kt
@@ -20,8 +20,9 @@ import android.os.Looper
import androidx.annotation.MainThread
import com.zhuinden.monarchy.Monarchy
import io.realm.Realm
+import org.matrix.android.sdk.api.session.Session
import org.matrix.android.sdk.internal.di.SessionDatabase
-import org.matrix.android.sdk.internal.session.SessionLifecycleObserver
+import org.matrix.android.sdk.api.session.SessionLifecycleObserver
import org.matrix.android.sdk.internal.session.SessionScope
import javax.inject.Inject
import kotlin.concurrent.getOrSet
@@ -44,14 +45,14 @@ internal class RealmSessionProvider @Inject constructor(@SessionDatabase private
}
@MainThread
- override fun onSessionStarted() {
+ override fun onSessionStarted(session: Session) {
realmThreadLocal.getOrSet {
Realm.getInstance(monarchy.realmConfiguration)
}
}
@MainThread
- override fun onSessionStopped() {
+ override fun onSessionStopped(session: Session) {
realmThreadLocal.get()?.close()
realmThreadLocal.remove()
}
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 aa7896eb99..88ebfc9237 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,7 +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.events.model.EventType
+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
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
@@ -31,6 +34,9 @@ import org.matrix.android.sdk.internal.database.model.RoomMembersLoadStatusType
import org.matrix.android.sdk.internal.database.model.RoomSummaryEntityFields
import org.matrix.android.sdk.internal.database.model.RoomTagEntityFields
import org.matrix.android.sdk.internal.database.model.TimelineEventEntityFields
+import org.matrix.android.sdk.internal.database.model.SpaceChildSummaryEntityFields
+import org.matrix.android.sdk.internal.database.model.SpaceParentSummaryEntityFields
+import org.matrix.android.sdk.internal.di.MoshiProvider
import timber.log.Timber
import javax.inject.Inject
@@ -39,10 +45,10 @@ class RealmSessionStoreMigration @Inject constructor() : RealmMigration {
companion object {
// SC-specific DB changes on top of Element
// 1: added markedUnread field
- const val SESSION_STORE_SCHEMA_SC_VERSION = 1L
+ const val SESSION_STORE_SCHEMA_SC_VERSION = 2L
const val SESSION_STORE_SCHEMA_SC_VERSION_OFFSET = (1L shl 12)
- const val SESSION_STORE_SCHEMA_VERSION = 9L +
+ const val SESSION_STORE_SCHEMA_VERSION = 10L +
SESSION_STORE_SCHEMA_SC_VERSION * SESSION_STORE_SCHEMA_SC_VERSION_OFFSET
}
@@ -61,8 +67,10 @@ class RealmSessionStoreMigration @Inject constructor() : RealmMigration {
if (oldVersion <= 6) migrateTo7(realm)
if (oldVersion <= 7) migrateTo8(realm)
if (oldVersion <= 8) migrateTo9(realm)
+ if (oldVersion <= 9) migrateTo10(realm)
if (oldScVersion <= 0) migrateToSc1(realm)
+ if (oldScVersion <= 1) migrateToSc2(realm)
}
// SC Version 1L added markedUnread
@@ -72,6 +80,13 @@ class RealmSessionStoreMigration @Inject constructor() : RealmMigration {
?.addField(RoomSummaryEntityFields.MARKED_UNREAD, Boolean::class.java)
}
+ // SC Version 2L added unreadCount
+ private fun migrateToSc2(realm: DynamicRealm) {
+ Timber.d("Step SC 1 -> 2")
+ realm.schema.get("RoomSummaryEntity")
+ ?.addField(RoomSummaryEntityFields.UNREAD_COUNT, Int::class.java)
+ }
+
private fun migrateTo1(realm: DynamicRealm) {
@@ -194,7 +209,6 @@ class RealmSessionStoreMigration @Inject constructor() : RealmMigration {
?.addIndex(RoomSummaryEntityFields.IS_SERVER_NOTICE)
?.transform { obj ->
-
val isFavorite = obj.getList(RoomSummaryEntityFields.TAGS.`$`).any {
it.getString(RoomTagEntityFields.TAG_NAME) == RoomTag.ROOM_TAG_FAVOURITE
}
@@ -214,4 +228,44 @@ class RealmSessionStoreMigration @Inject constructor() : RealmMigration {
}
}
}
+
+ fun migrateTo10(realm: DynamicRealm) {
+ Timber.d("Step 9 -> 10")
+ realm.schema.create("SpaceChildSummaryEntity")
+ ?.addField(SpaceChildSummaryEntityFields.ORDER, String::class.java)
+ ?.addField(SpaceChildSummaryEntityFields.CHILD_ROOM_ID, String::class.java)
+ ?.addField(SpaceChildSummaryEntityFields.AUTO_JOIN, Boolean::class.java)
+ ?.setNullable(SpaceChildSummaryEntityFields.AUTO_JOIN, true)
+ ?.addRealmObjectField(SpaceChildSummaryEntityFields.CHILD_SUMMARY_ENTITY.`$`, realm.schema.get("RoomSummaryEntity")!!)
+ ?.addRealmListField(SpaceChildSummaryEntityFields.VIA_SERVERS.`$`, String::class.java)
+
+ realm.schema.create("SpaceParentSummaryEntity")
+ ?.addField(SpaceParentSummaryEntityFields.PARENT_ROOM_ID, String::class.java)
+ ?.addField(SpaceParentSummaryEntityFields.CANONICAL, Boolean::class.java)
+ ?.setNullable(SpaceParentSummaryEntityFields.CANONICAL, true)
+ ?.addRealmObjectField(SpaceParentSummaryEntityFields.PARENT_SUMMARY_ENTITY.`$`, realm.schema.get("RoomSummaryEntity")!!)
+ ?.addRealmListField(SpaceParentSummaryEntityFields.VIA_SERVERS.`$`, String::class.java)
+
+ val creationContentAdapter = MoshiProvider.providesMoshi().adapter(RoomCreateContent::class.java)
+ realm.schema.get("RoomSummaryEntity")
+ ?.addField(RoomSummaryEntityFields.ROOM_TYPE, String::class.java)
+ ?.addField(RoomSummaryEntityFields.FLATTEN_PARENT_IDS, String::class.java)
+ ?.addField(RoomSummaryEntityFields.GROUP_IDS, String::class.java)
+ ?.transform { obj ->
+
+ val creationEvent = realm.where("CurrentStateEventEntity")
+ .equalTo(CurrentStateEventEntityFields.ROOM_ID, obj.getString(RoomSummaryEntityFields.ROOM_ID))
+ .equalTo(CurrentStateEventEntityFields.TYPE, EventType.STATE_ROOM_CREATE)
+ .findFirst()
+
+ val roomType = creationEvent?.getObject(CurrentStateEventEntityFields.ROOT.`$`)
+ ?.getString(EventEntityFields.CONTENT)?.let {
+ creationContentAdapter.fromJson(it)?.type
+ }
+
+ obj.setString(RoomSummaryEntityFields.ROOM_TYPE, roomType)
+ }
+ ?.addRealmListField(RoomSummaryEntityFields.PARENTS.`$`, realm.schema.get("SpaceParentSummaryEntity")!!)
+ ?.addRealmListField(RoomSummaryEntityFields.CHILDREN.`$`, realm.schema.get("SpaceChildSummaryEntity")!!)
+ }
}
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 33958f75e8..5f057fd643 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
@@ -17,6 +17,8 @@
package org.matrix.android.sdk.internal.database.mapper
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.SpaceParentInfo
import org.matrix.android.sdk.api.session.room.model.tag.RoomTag
import org.matrix.android.sdk.internal.database.model.RoomSummaryEntity
import org.matrix.android.sdk.internal.session.typing.DefaultTypingUsersTracker
@@ -49,6 +51,7 @@ internal class RoomSummaryMapper @Inject constructor(private val timelineEventMa
topic = roomSummaryEntity.topic ?: "",
avatarUrl = roomSummaryEntity.avatarUrl ?: "",
isDirect = roomSummaryEntity.isDirect,
+ directUserId = roomSummaryEntity.directUserId,
latestPreviewableEvent = latestEvent,
latestPreviewableContentEvent = latestContentEvent,
latestPreviewableOriginalContentEvent = latestOriginalContentEvent,
@@ -74,7 +77,32 @@ internal class RoomSummaryMapper @Inject constructor(private val timelineEventMa
breadcrumbsIndex = roomSummaryEntity.breadcrumbsIndex,
roomEncryptionTrustLevel = roomSummaryEntity.roomEncryptionTrustLevel,
inviterId = roomSummaryEntity.inviterId,
- hasFailedSending = roomSummaryEntity.hasFailedSending
+ hasFailedSending = roomSummaryEntity.hasFailedSending,
+ roomType = roomSummaryEntity.roomType,
+ spaceParents = roomSummaryEntity.parents.map { relationInfoEntity ->
+ SpaceParentInfo(
+ parentId = relationInfoEntity.parentRoomId,
+ roomSummary = relationInfoEntity.parentSummaryEntity?.let { map(it) },
+ canonical = relationInfoEntity.canonical ?: false,
+ viaServers = relationInfoEntity.viaServers.toList()
+ )
+ },
+ spaceChildren = roomSummaryEntity.children.map {
+ SpaceChildInfo(
+ childRoomId = it.childRoomId ?: "",
+ isKnown = it.childSummaryEntity != null,
+ roomType = it.childSummaryEntity?.roomType,
+ name = it.childSummaryEntity?.name,
+ topic = it.childSummaryEntity?.topic,
+ avatarUrl = it.childSummaryEntity?.avatarUrl,
+ activeMemberCount = it.childSummaryEntity?.joinedMembersCount,
+ order = it.order,
+ autoJoin = it.autoJoin ?: false,
+ viaServers = it.viaServers.toList(),
+ parentRoomId = roomSummaryEntity.roomId
+ )
+ },
+ flattenParentIds = roomSummaryEntity.flattenParentIds?.split("|") ?: emptyList()
)
}
}
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/RoomEntity.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/RoomEntity.kt
index 3ff2532604..58297776f0 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/RoomEntity.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/RoomEntity.kt
@@ -43,6 +43,5 @@ internal open class RoomEntity(@PrimaryKey var roomId: String = "",
set(value) {
membersLoadStatusStr = value.name
}
-
companion object
}
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/RoomSummaryEntity.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/RoomSummaryEntity.kt
index 3fc3c92557..6a242748f8 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/RoomSummaryEntity.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/RoomSummaryEntity.kt
@@ -27,7 +27,10 @@ import org.matrix.android.sdk.api.session.room.model.VersioningState
import org.matrix.android.sdk.api.session.room.model.tag.RoomTag
internal open class RoomSummaryEntity(
- @PrimaryKey var roomId: String = ""
+ @PrimaryKey var roomId: String = "",
+ var roomType: String? = null,
+ var parents: RealmList = RealmList(),
+ var children: RealmList = RealmList()
) : RealmObject() {
var displayName: String? = ""
@@ -103,6 +106,17 @@ internal open class RoomSummaryEntity(
if (value != field) field = value
}
+ var unreadCount: Int = 0
+ set(value) {
+ if (value != field) field = value
+ }
+ get() {
+ if (field == 0 && hasUnreadOriginalContentMessages) {
+ return 1
+ }
+ return field
+ }
+
var readMarkerId: String? = null
set(value) {
if (value != field) field = value
@@ -229,6 +243,16 @@ internal open class RoomSummaryEntity(
if (value != field) field = value
}
+ var flattenParentIds: String? = null
+ set(value) {
+ if (value != field) field = value
+ }
+
+ var groupIds: String? = null
+ set(value) {
+ if (value != field) field = value
+ }
+
@Index
private var membershipStr: String = Membership.NONE.name
@@ -269,6 +293,5 @@ internal open class RoomSummaryEntity(
roomEncryptionTrustLevelStr = value?.name
}
}
-
companion object
}
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/SessionRealmModule.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/SessionRealmModule.kt
index 6e6096cf8a..72ae512fa5 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/SessionRealmModule.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/SessionRealmModule.kt
@@ -61,6 +61,8 @@ import io.realm.annotations.RealmModule
CurrentStateEventEntity::class,
UserAccountDataEntity::class,
ScalarTokenEntity::class,
- WellknownIntegrationManagerConfigEntity::class
+ WellknownIntegrationManagerConfigEntity::class,
+ SpaceChildSummaryEntity::class,
+ SpaceParentSummaryEntity::class
])
internal class SessionRealmModule
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/SpaceChildSummaryEntity.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/SpaceChildSummaryEntity.kt
new file mode 100644
index 0000000000..982c9ece6a
--- /dev/null
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/SpaceChildSummaryEntity.kt
@@ -0,0 +1,44 @@
+/*
+ * 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.database.model
+
+import io.realm.RealmList
+import io.realm.RealmObject
+
+/**
+ * Decorates room summary with space related information.
+ */
+internal open class SpaceChildSummaryEntity(
+// var isSpace: Boolean = false,
+
+ var order: String? = null,
+
+ var autoJoin: Boolean? = null,
+
+ var childRoomId: String? = null,
+ // Link to the actual space summary if it is known locally
+ var childSummaryEntity: RoomSummaryEntity? = null,
+
+ var viaServers: RealmList = RealmList()
+// var owner: RoomSummaryEntity? = null,
+
+// var level: Int = 0
+
+) : RealmObject() {
+
+ companion object
+}
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/SpaceParentSummaryEntity.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/SpaceParentSummaryEntity.kt
new file mode 100644
index 0000000000..30517717f4
--- /dev/null
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/SpaceParentSummaryEntity.kt
@@ -0,0 +1,45 @@
+/*
+ * 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.database.model
+
+import io.realm.RealmList
+import io.realm.RealmObject
+
+/**
+ * Decorates room summary with space related information.
+ */
+internal open class SpaceParentSummaryEntity(
+ /**
+ * Determines whether this is the main parent for the space
+ * When a user joins a room with a canonical parent, clients may switch to view the room in the context of that space,
+ * peeking into it in order to find other rooms and group them together.
+ * In practice, well behaved rooms should only have one canonical parent, but given this is not enforced:
+ * if multiple are present the client should select the one with the lowest room ID,
+ * as determined via a lexicographic utf-8 ordering.
+ */
+ var canonical: Boolean? = null,
+
+ var parentRoomId: String? = null,
+ // Link to the actual space summary if it is known locally
+ var parentSummaryEntity: RoomSummaryEntity? = null,
+
+ var viaServers: RealmList = RealmList()
+
+) : RealmObject() {
+
+ companion object
+}
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/network/Request.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/network/Request.kt
index 0246bae024..e045cebd3e 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/network/Request.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/network/Request.kt
@@ -88,8 +88,8 @@ internal suspend inline fun executeRequest(globalErrorReceiver: GlobalErr
throw when (exception) {
is IOException -> Failure.NetworkConnection(exception)
is Failure.ServerError,
- is Failure.OtherServerError -> exception
- is CancellationException -> Failure.Cancelled(exception)
+ is Failure.OtherServerError,
+ is CancellationException -> exception
else -> Failure.Unknown(exception)
}
}
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
new file mode 100644
index 0000000000..7a06c2129c
--- /dev/null
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/query/QueryRoomOrderProcessor.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.internal.query
+
+import io.realm.RealmQuery
+import io.realm.Sort
+import org.matrix.android.sdk.api.session.room.RoomSortOrder
+import org.matrix.android.sdk.internal.database.model.RoomSummaryEntity
+import org.matrix.android.sdk.internal.database.model.RoomSummaryEntityFields
+
+internal fun RealmQuery.process(sortOrder: RoomSortOrder): RealmQuery {
+ when (sortOrder) {
+ RoomSortOrder.NAME -> {
+ sort(RoomSummaryEntityFields.DISPLAY_NAME, Sort.ASCENDING)
+ }
+ RoomSortOrder.ACTIVITY -> {
+ sort(RoomSummaryEntityFields.LAST_ACTIVITY_TIME, Sort.DESCENDING)
+ }
+ RoomSortOrder.NONE -> {
+ }
+ }
+ return this
+}
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/query/QueryStringValueProcessor.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/query/QueryStringValueProcessor.kt
index 899024458a..fd33682231 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/query/QueryStringValueProcessor.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/query/QueryStringValueProcessor.kt
@@ -16,10 +16,10 @@
package org.matrix.android.sdk.internal.query
-import org.matrix.android.sdk.api.query.QueryStringValue
import io.realm.Case
import io.realm.RealmObject
import io.realm.RealmQuery
+import org.matrix.android.sdk.api.query.QueryStringValue
import timber.log.Timber
fun RealmQuery.process(field: String, queryStringValue: QueryStringValue): RealmQuery {
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 d05ee48c1b..891858d857 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
@@ -219,7 +219,7 @@ internal class DefaultFileService @Inject constructor(
fileName: String,
mimeType: String?,
elementToDecrypt: ElementToDecrypt?): Boolean {
- return fileState(mxcUrl, fileName, mimeType, elementToDecrypt) == FileService.FileState.IN_CACHE
+ return fileState(mxcUrl, fileName, mimeType, elementToDecrypt) is FileService.FileState.InCache
}
internal data class CachedFiles(
@@ -256,12 +256,17 @@ internal class DefaultFileService @Inject constructor(
fileName: String,
mimeType: String?,
elementToDecrypt: ElementToDecrypt?): FileService.FileState {
- mxcUrl ?: return FileService.FileState.UNKNOWN
- if (getFiles(mxcUrl, fileName, mimeType, elementToDecrypt != null).file.exists()) return FileService.FileState.IN_CACHE
+ mxcUrl ?: return FileService.FileState.Unknown
+ val files = getFiles(mxcUrl, fileName, mimeType, elementToDecrypt != null)
+ if (files.file.exists()) {
+ return FileService.FileState.InCache(
+ decryptedFileInCache = files.getClearFile().exists()
+ )
+ }
val isDownloading = synchronized(ongoing) {
ongoing[mxcUrl] != null
}
- return if (isDownloading) FileService.FileState.DOWNLOADING else FileService.FileState.UNKNOWN
+ return if (isDownloading) FileService.FileState.Downloading else FileService.FileState.Unknown
}
/**
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 821a9cba8c..53e13c14ec 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
@@ -19,13 +19,15 @@ package org.matrix.android.sdk.internal.session
import androidx.annotation.MainThread
import dagger.Lazy
import io.realm.RealmConfiguration
+import kotlinx.coroutines.NonCancellable
+import kotlinx.coroutines.withContext
import okhttp3.OkHttpClient
import org.matrix.android.sdk.api.auth.data.SessionParams
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.initsync.InitialSyncProgressService
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.cache.CacheService
@@ -38,6 +40,7 @@ import org.matrix.android.sdk.api.session.file.ContentDownloadStateTracker
import org.matrix.android.sdk.api.session.file.FileService
import org.matrix.android.sdk.api.session.group.GroupService
import org.matrix.android.sdk.api.session.homeserver.HomeServerCapabilitiesService
+import org.matrix.android.sdk.api.session.initsync.InitialSyncProgressService
import org.matrix.android.sdk.api.session.integrationmanager.IntegrationManagerService
import org.matrix.android.sdk.api.session.media.MediaService
import org.matrix.android.sdk.api.session.permalinks.PermalinkService
@@ -49,6 +52,7 @@ import org.matrix.android.sdk.api.session.search.SearchService
import org.matrix.android.sdk.api.session.securestorage.SecureStorageService
import org.matrix.android.sdk.api.session.securestorage.SharedSecretStorageService
import org.matrix.android.sdk.api.session.signout.SignOutService
+import org.matrix.android.sdk.api.session.space.SpaceService
import org.matrix.android.sdk.api.session.sync.FilterService
import org.matrix.android.sdk.api.session.terms.TermsService
import org.matrix.android.sdk.api.session.thirdparty.ThirdPartyService
@@ -120,6 +124,7 @@ internal class DefaultSession @Inject constructor(
private val integrationManagerService: IntegrationManagerService,
private val thirdPartyService: Lazy,
private val callSignalingService: Lazy,
+ private val spaceService: Lazy,
@UnauthenticatedWithCertificate
private val unauthenticatedWithCertificateOkHttpClient: Lazy
) : Session,
@@ -159,7 +164,12 @@ internal class DefaultSession @Inject constructor(
isOpen = true
cryptoService.get().ensureDevice()
uiHandler.post {
- lifecycleObservers.forEach { it.onSessionStarted() }
+ lifecycleObservers.forEach {
+ it.onSessionStarted(this)
+ }
+ sessionListeners.dispatch {
+ it.onSessionStarted(this)
+ }
}
globalErrorHandler.listener = this
}
@@ -200,7 +210,10 @@ internal class DefaultSession @Inject constructor(
stopSync()
// timelineEventDecryptor.destroy()
uiHandler.post {
- lifecycleObservers.forEach { it.onSessionStopped() }
+ lifecycleObservers.forEach { it.onSessionStopped(this) }
+ sessionListeners.dispatch {
+ it.onSessionStopped(this)
+ }
}
cryptoService.get().close()
isOpen = false
@@ -225,14 +238,23 @@ internal class DefaultSession @Inject constructor(
stopSync()
stopAnyBackgroundSync()
uiHandler.post {
- lifecycleObservers.forEach { it.onClearCache() }
+ lifecycleObservers.forEach {
+ it.onClearCache(this)
+ }
+ sessionListeners.dispatch {
+ it.onClearCache(this)
+ }
+ }
+ withContext(NonCancellable) {
+ cacheService.get().clearCache()
}
- cacheService.get().clearCache()
workManagerProvider.cancelAllWorks()
}
override fun onGlobalError(globalError: GlobalError) {
- sessionListeners.dispatchGlobalError(globalError)
+ sessionListeners.dispatch {
+ it.onGlobalError(this, globalError)
+ }
}
override fun contentUrlResolver() = contentUrlResolver
@@ -265,6 +287,8 @@ internal class DefaultSession @Inject constructor(
override fun thirdPartyService(): ThirdPartyService = thirdPartyService.get()
+ override fun spaceService(): SpaceService = spaceService.get()
+
override fun getOkHttpClient(): OkHttpClient {
return unauthenticatedWithCertificateOkHttpClient.get()
}
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/SessionComponent.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/SessionComponent.kt
index 7e1e3d0f70..541c877b1d 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/SessionComponent.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/SessionComponent.kt
@@ -52,6 +52,7 @@ import org.matrix.android.sdk.internal.session.room.send.RedactEventWorker
import org.matrix.android.sdk.internal.session.room.send.SendEventWorker
import org.matrix.android.sdk.internal.session.search.SearchModule
import org.matrix.android.sdk.internal.session.signout.SignOutModule
+import org.matrix.android.sdk.internal.session.space.SpaceModule
import org.matrix.android.sdk.internal.session.sync.SyncModule
import org.matrix.android.sdk.internal.session.sync.SyncTask
import org.matrix.android.sdk.internal.session.sync.SyncTokenStore
@@ -91,7 +92,8 @@ import org.matrix.android.sdk.internal.util.MatrixCoroutineDispatchers
FederationModule::class,
CallModule::class,
SearchModule::class,
- ThirdPartyModule::class
+ ThirdPartyModule::class,
+ SpaceModule::class
]
)
@SessionScope
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/SessionCoroutineScopeHolder.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/SessionCoroutineScopeHolder.kt
new file mode 100644
index 0000000000..82a8f79fd5
--- /dev/null
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/SessionCoroutineScopeHolder.kt
@@ -0,0 +1,43 @@
+/*
+ * 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
+
+import kotlinx.coroutines.CancellationException
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.SupervisorJob
+import kotlinx.coroutines.cancelChildren
+import javax.inject.Inject
+import org.matrix.android.sdk.api.session.Session
+import org.matrix.android.sdk.api.session.SessionLifecycleObserver
+
+@SessionScope
+internal class SessionCoroutineScopeHolder @Inject constructor(): SessionLifecycleObserver {
+
+ val scope: CoroutineScope = CoroutineScope(SupervisorJob())
+
+ override fun onSessionStopped(session: Session) {
+ scope.cancelChildren()
+ }
+
+ override fun onClearCache(session: Session) {
+ scope.cancelChildren()
+ }
+
+ private fun CoroutineScope.cancelChildren() {
+ coroutineContext.cancelChildren(CancellationException("Closing session"))
+ }
+}
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/SessionListeners.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/SessionListeners.kt
index 64f2d249f3..563ff4ada3 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/SessionListeners.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/SessionListeners.kt
@@ -16,7 +16,6 @@
package org.matrix.android.sdk.internal.session
-import org.matrix.android.sdk.api.failure.GlobalError
import org.matrix.android.sdk.api.session.Session
import javax.inject.Inject
@@ -36,10 +35,10 @@ internal class SessionListeners @Inject constructor() {
}
}
- fun dispatchGlobalError(globalError: GlobalError) {
+ fun dispatch(block: (Session.Listener) -> Unit) {
synchronized(listeners) {
listeners.forEach {
- it.onGlobalError(globalError)
+ block(it)
}
}
}
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 e61e4ecd89..63423b72c6 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
@@ -33,6 +33,7 @@ import org.matrix.android.sdk.api.auth.data.SessionParams
import org.matrix.android.sdk.api.auth.data.sessionId
import org.matrix.android.sdk.api.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.events.EventService
import org.matrix.android.sdk.api.session.homeserver.HomeServerCapabilitiesService
@@ -343,6 +344,10 @@ internal abstract class SessionModule {
@IntoSet
abstract fun bindRealmSessionProvider(provider: RealmSessionProvider): SessionLifecycleObserver
+ @Binds
+ @IntoSet
+ abstract fun bindSessionCoroutineScopeHolder(holder: SessionCoroutineScopeHolder): SessionLifecycleObserver
+
@Binds
@IntoSet
abstract fun bindEventSenderProcessorAsSessionLifecycleObserver(processor: EventSenderProcessorCoroutine): SessionLifecycleObserver
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 f5391d6cdb..475781ef01 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
@@ -36,7 +36,7 @@ import org.matrix.android.sdk.internal.di.AuthenticatedIdentity
import org.matrix.android.sdk.internal.di.UnauthenticatedWithCertificate
import org.matrix.android.sdk.internal.extensions.observeNotNull
import org.matrix.android.sdk.internal.network.RetrofitFactory
-import org.matrix.android.sdk.internal.session.SessionLifecycleObserver
+import org.matrix.android.sdk.api.session.SessionLifecycleObserver
import org.matrix.android.sdk.internal.session.SessionScope
import org.matrix.android.sdk.internal.session.identity.data.IdentityStore
import org.matrix.android.sdk.internal.session.openid.GetOpenIdTokenTask
@@ -51,6 +51,7 @@ import org.matrix.android.sdk.internal.util.ensureProtocol
import kotlinx.coroutines.withContext
import okhttp3.OkHttpClient
import org.matrix.android.sdk.api.extensions.orFalse
+import org.matrix.android.sdk.api.session.Session
import timber.log.Timber
import javax.inject.Inject
import javax.net.ssl.HttpsURLConnection
@@ -86,7 +87,7 @@ internal class DefaultIdentityService @Inject constructor(
private val listeners = mutableSetOf()
- override fun onSessionStarted() {
+ override fun onSessionStarted(session: Session) {
lifecycleRegistry.currentState = Lifecycle.State.STARTED
// Observe the account data change
accountDataDataSource
@@ -111,7 +112,7 @@ internal class DefaultIdentityService @Inject constructor(
}
}
- override fun onSessionStopped() {
+ override fun onSessionStopped(session: Session) {
lifecycleRegistry.currentState = Lifecycle.State.DESTROYED
}
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 e34615d269..3df9a00cc1 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
@@ -21,6 +21,7 @@ import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.LifecycleRegistry
import com.zhuinden.monarchy.Monarchy
import org.matrix.android.sdk.api.MatrixConfiguration
+import org.matrix.android.sdk.api.session.Session
import org.matrix.android.sdk.api.session.events.model.toModel
import org.matrix.android.sdk.api.session.integrationmanager.IntegrationManagerConfig
import org.matrix.android.sdk.api.session.integrationmanager.IntegrationManagerService
@@ -29,7 +30,7 @@ import org.matrix.android.sdk.api.session.widgets.model.WidgetType
import org.matrix.android.sdk.internal.database.model.WellknownIntegrationManagerConfigEntity
import org.matrix.android.sdk.internal.di.SessionDatabase
import org.matrix.android.sdk.internal.extensions.observeNotNull
-import org.matrix.android.sdk.internal.session.SessionLifecycleObserver
+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.UserAccountDataEvent
@@ -77,7 +78,7 @@ internal class IntegrationManager @Inject constructor(matrixConfiguration: Matri
currentConfigs.add(defaultConfig)
}
- override fun onSessionStarted() {
+ override fun onSessionStarted(session: Session) {
lifecycleRegistry.currentState = Lifecycle.State.STARTED
observeWellknownConfig()
accountDataDataSource
@@ -105,7 +106,7 @@ internal class IntegrationManager @Inject constructor(matrixConfiguration: Matri
}
}
- override fun onSessionStopped() {
+ override fun onSessionStopped(session: Session) {
lifecycleRegistry.currentState = Lifecycle.State.DESTROYED
}
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 1d8eb6c95e..c6059f84ea 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
@@ -24,6 +24,7 @@ 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
import org.matrix.android.sdk.api.session.room.model.RoomSummary
+import org.matrix.android.sdk.api.session.room.model.RoomType
import org.matrix.android.sdk.api.session.room.model.relation.RelationService
import org.matrix.android.sdk.api.session.room.notification.RoomPushRuleService
import org.matrix.android.sdk.api.session.room.read.ReadService
@@ -36,11 +37,13 @@ import org.matrix.android.sdk.api.session.room.timeline.TimelineService
import org.matrix.android.sdk.api.session.room.typing.TypingService
import org.matrix.android.sdk.api.session.room.uploads.UploadsService
import org.matrix.android.sdk.api.session.search.SearchResult
+import org.matrix.android.sdk.api.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.room.state.SendStateTask
import org.matrix.android.sdk.internal.session.room.summary.RoomSummaryDataSource
import org.matrix.android.sdk.internal.session.search.SearchTask
+import org.matrix.android.sdk.internal.session.space.DefaultSpace
import org.matrix.android.sdk.internal.util.awaitCallback
import java.security.InvalidParameterException
import javax.inject.Inject
@@ -148,4 +151,9 @@ internal class DefaultRoom @Inject constructor(override val roomId: String,
)
)
}
+
+ override fun asSpace(): Space? {
+ if (roomSummary()?.roomType != RoomType.SPACE) return null
+ return DefaultSpace(this, roomSummaryDataSource)
+ }
}
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 989c8cb788..3f614f7016 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
@@ -20,19 +20,19 @@ import androidx.lifecycle.LiveData
import androidx.lifecycle.Transformations
import androidx.paging.PagedList
import com.zhuinden.monarchy.Monarchy
-import org.matrix.android.sdk.api.MatrixCallback
import org.matrix.android.sdk.api.session.events.model.Event
import org.matrix.android.sdk.api.session.room.Room
import org.matrix.android.sdk.api.session.room.RoomService
+import org.matrix.android.sdk.api.session.room.RoomSortOrder
import org.matrix.android.sdk.api.session.room.RoomSummaryQueryParams
-import org.matrix.android.sdk.api.session.room.UpdatableFilterLivePageResult
+import org.matrix.android.sdk.api.session.room.UpdatableLivePageResult
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.api.session.room.model.RoomMemberSummary
import org.matrix.android.sdk.api.session.room.model.RoomSummary
import org.matrix.android.sdk.api.session.room.model.create.CreateRoomParams
import org.matrix.android.sdk.api.session.room.peeking.PeekResult
import org.matrix.android.sdk.api.session.room.summary.RoomAggregateNotificationCount
-import org.matrix.android.sdk.api.util.Cancelable
import org.matrix.android.sdk.api.util.Optional
import org.matrix.android.sdk.api.util.toOptional
import org.matrix.android.sdk.internal.database.mapper.asDomain
@@ -50,8 +50,6 @@ import org.matrix.android.sdk.internal.session.room.peeking.ResolveRoomStateTask
import org.matrix.android.sdk.internal.session.room.read.MarkAllRoomsReadTask
import org.matrix.android.sdk.internal.session.room.summary.RoomSummaryDataSource
import org.matrix.android.sdk.internal.session.user.accountdata.UpdateBreadcrumbsTask
-import org.matrix.android.sdk.internal.task.TaskExecutor
-import org.matrix.android.sdk.internal.task.configureWith
import org.matrix.android.sdk.internal.util.fetchCopied
import javax.inject.Inject
@@ -67,16 +65,11 @@ internal class DefaultRoomService @Inject constructor(
private val peekRoomTask: PeekRoomTask,
private val roomGetter: RoomGetter,
private val roomSummaryDataSource: RoomSummaryDataSource,
- private val roomChangeMembershipStateDataSource: RoomChangeMembershipStateDataSource,
- private val taskExecutor: TaskExecutor
+ private val roomChangeMembershipStateDataSource: RoomChangeMembershipStateDataSource
) : RoomService {
- override fun createRoom(createRoomParams: CreateRoomParams, callback: MatrixCallback): Cancelable {
- return createRoomTask
- .configureWith(createRoomParams) {
- this.callback = callback
- }
- .executeBy(taskExecutor)
+ override suspend fun createRoom(createRoomParams: CreateRoomParams): String {
+ return createRoomTask.executeRetry(createRoomParams, 3)
}
override fun getRoom(roomId: String): Room? {
@@ -99,14 +92,14 @@ internal class DefaultRoomService @Inject constructor(
return roomSummaryDataSource.getRoomSummariesLive(queryParams)
}
- override fun getPagedRoomSummariesLive(queryParams: RoomSummaryQueryParams, pagedListConfig: PagedList.Config)
+ override fun getPagedRoomSummariesLive(queryParams: RoomSummaryQueryParams, pagedListConfig: PagedList.Config, sortOrder: RoomSortOrder)
: LiveData> {
- return roomSummaryDataSource.getSortedPagedRoomSummariesLive(queryParams, pagedListConfig)
+ return roomSummaryDataSource.getSortedPagedRoomSummariesLive(queryParams, pagedListConfig, sortOrder)
}
- override fun getFilteredPagedRoomSummariesLive(queryParams: RoomSummaryQueryParams, pagedListConfig: PagedList.Config)
- : UpdatableFilterLivePageResult {
- return roomSummaryDataSource.getFilteredPagedRoomSummariesLive(queryParams, pagedListConfig)
+ override fun getFilteredPagedRoomSummariesLive(queryParams: RoomSummaryQueryParams, pagedListConfig: PagedList.Config, sortOrder: RoomSortOrder)
+ : UpdatableLivePageResult {
+ return roomSummaryDataSource.getUpdatablePagedRoomSummariesLive(queryParams, pagedListConfig, sortOrder)
}
override fun getNotificationCountForRooms(queryParams: RoomSummaryQueryParams, preferenceProvider: RoomSummary.RoomSummaryPreferenceProvider): RoomAggregateNotificationCount {
@@ -121,34 +114,20 @@ internal class DefaultRoomService @Inject constructor(
return roomSummaryDataSource.getBreadcrumbsLive(queryParams)
}
- override fun onRoomDisplayed(roomId: String): Cancelable {
- return updateBreadcrumbsTask
- .configureWith(UpdateBreadcrumbsTask.Params(roomId))
- .executeBy(taskExecutor)
+ override suspend fun onRoomDisplayed(roomId: String) {
+ updateBreadcrumbsTask.execute(UpdateBreadcrumbsTask.Params(roomId))
}
- override fun joinRoom(roomIdOrAlias: String, reason: String?, viaServers: List, callback: MatrixCallback): Cancelable {
- return joinRoomTask
- .configureWith(JoinRoomTask.Params(roomIdOrAlias, reason, viaServers)) {
- this.callback = callback
- }
- .executeBy(taskExecutor)
+ override suspend fun joinRoom(roomIdOrAlias: String, reason: String?, viaServers: List) {
+ joinRoomTask.execute(JoinRoomTask.Params(roomIdOrAlias, reason, viaServers))
}
- override fun markAllAsRead(roomIds: List, callback: MatrixCallback): Cancelable {
- return markAllRoomsReadTask
- .configureWith(MarkAllRoomsReadTask.Params(roomIds)) {
- this.callback = callback
- }
- .executeBy(taskExecutor)
+ override suspend fun markAllAsRead(roomIds: List) {
+ markAllRoomsReadTask.execute(MarkAllRoomsReadTask.Params(roomIds))
}
- override fun getRoomIdByAlias(roomAlias: String, searchOnServer: Boolean, callback: MatrixCallback>): Cancelable {
- return roomIdByAliasTask
- .configureWith(GetRoomIdByAliasTask.Params(roomAlias, searchOnServer)) {
- this.callback = callback
- }
- .executeBy(taskExecutor)
+ override suspend fun getRoomIdByAlias(roomAlias: String, searchOnServer: Boolean): Optional {
+ return roomIdByAliasTask.execute(GetRoomIdByAliasTask.Params(roomAlias, searchOnServer))
}
override suspend fun deleteRoomAlias(roomAlias: String) {
@@ -179,19 +158,25 @@ internal class DefaultRoomService @Inject constructor(
}
}
- override fun getRoomState(roomId: String, callback: MatrixCallback>) {
- resolveRoomStateTask
- .configureWith(ResolveRoomStateTask.Params(roomId)) {
- this.callback = callback
- }
- .executeBy(taskExecutor)
+ override suspend fun getRoomState(roomId: String): List {
+ return resolveRoomStateTask.execute(ResolveRoomStateTask.Params(roomId))
}
- override fun peekRoom(roomIdOrAlias: String, callback: MatrixCallback) {
- peekRoomTask
- .configureWith(PeekRoomTask.Params(roomIdOrAlias)) {
- this.callback = callback
- }
- .executeBy(taskExecutor)
+ override suspend fun peekRoom(roomIdOrAlias: String): PeekResult {
+ return peekRoomTask.execute(PeekRoomTask.Params(roomIdOrAlias))
+ }
+
+ override fun getFlattenRoomSummaryChildrenOf(spaceId: String?, memberships: List): List {
+ if (spaceId == null) {
+ return roomSummaryDataSource.getFlattenOrphanRooms()
+ }
+ return roomSummaryDataSource.getAllRoomSummaryChildOf(spaceId, memberships)
+ }
+
+ override fun getFlattenRoomSummaryChildrenOfLive(spaceId: String?, memberships: List): LiveData> {
+ if (spaceId == null) {
+ return roomSummaryDataSource.getFlattenOrphanRoomsLive()
+ }
+ return roomSummaryDataSource.getAllRoomSummaryChildOfLive(spaceId, memberships)
}
}
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 3b070d628e..e70235a273 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
@@ -24,6 +24,7 @@ import org.commonmark.renderer.html.HtmlRenderer
import org.matrix.android.sdk.api.session.file.FileService
import org.matrix.android.sdk.api.session.room.RoomDirectoryService
import org.matrix.android.sdk.api.session.room.RoomService
+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
@@ -91,6 +92,7 @@ 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.space.DefaultSpaceService
import retrofit2.Retrofit
@Module
@@ -137,6 +139,9 @@ internal abstract class RoomModule {
@Binds
abstract fun bindRoomService(service: DefaultRoomService): RoomService
+ @Binds
+ abstract fun bindSpaceService(service: DefaultSpaceService): SpaceService
+
@Binds
abstract fun bindRoomDirectoryService(service: DefaultRoomDirectoryService): RoomDirectoryService
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/SpaceGetter.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/SpaceGetter.kt
new file mode 100644
index 0000000000..fed3ff542b
--- /dev/null
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/SpaceGetter.kt
@@ -0,0 +1,33 @@
+/*
+ * 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 org.matrix.android.sdk.api.session.space.Space
+import javax.inject.Inject
+
+internal interface SpaceGetter {
+ fun get(spaceId: String): Space?
+}
+
+internal class DefaultSpaceGetter @Inject constructor(
+ private val roomGetter: RoomGetter
+) : SpaceGetter {
+
+ override fun get(spaceId: String): Space? {
+ return roomGetter.getRoom(spaceId)?.asSpace()
+ }
+}
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 9faf50dd8b..b39cbaa582 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
@@ -36,7 +36,11 @@ internal class RoomAliasAvailabilityChecker @Inject constructor(
@Throws(RoomAliasError::class)
suspend fun check(aliasLocalPart: String?) {
if (aliasLocalPart.isNullOrEmpty()) {
- throw RoomAliasError.AliasEmpty
+ // don't check empty or not provided alias
+ return
+ }
+ if (aliasLocalPart.isBlank()) {
+ throw RoomAliasError.AliasIsBlank
}
// Check alias availability
val fullAlias = aliasLocalPart.toFullLocalAlias(userId)
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/create/CreateRoomBody.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/create/CreateRoomBody.kt
index 13d403e2e4..69352688e3 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/create/CreateRoomBody.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/create/CreateRoomBody.kt
@@ -111,5 +111,12 @@ internal data class CreateRoomBody(
* The power level content to override in the default power level event
*/
@Json(name = "power_level_content_override")
- val powerLevelContentOverride: PowerLevelsContent?
+ val powerLevelContentOverride: PowerLevelsContent?,
+
+ /**
+ * The room version to set for the room. If not provided, the homeserver is to use its configured default.
+ * If provided, the homeserver will return a 400 error with the errcode M_UNSUPPORTED_ROOM_VERSION if it does not support the room version.
+ */
+ @Json(name = "room_version")
+ val roomVersion: String?
)
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 5e823fc87f..018b865388 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
@@ -20,13 +20,19 @@ 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
import org.matrix.android.sdk.internal.crypto.MXCRYPTO_ALGORITHM_MEGOLM
import org.matrix.android.sdk.internal.di.AuthenticatedIdentity
+import org.matrix.android.sdk.internal.di.UserId
import org.matrix.android.sdk.internal.network.token.AccessTokenProvider
import org.matrix.android.sdk.internal.session.content.FileUploader
import org.matrix.android.sdk.internal.session.identity.EnsureIdentityTokenTask
@@ -43,6 +49,8 @@ internal class CreateRoomBodyBuilder @Inject constructor(
private val deviceListManager: DeviceListManager,
private val identityStore: IdentityStore,
private val fileUploader: FileUploader,
+ @UserId
+ private val userId: String,
@AuthenticatedIdentity
private val accessTokenProvider: AccessTokenProvider
) {
@@ -68,10 +76,17 @@ 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)
+ buildAvatarEvent(params),
+ buildGuestAccess(params),
+ buildJoinRulesRestricted(params)
)
.takeIf { it.isNotEmpty() }
@@ -80,13 +95,15 @@ internal class CreateRoomBodyBuilder @Inject constructor(
roomAliasName = params.roomAliasName,
name = params.name,
topic = params.topic,
- invitedUserIds = params.invitedUserIds,
+ invitedUserIds = params.invitedUserIds.filter { it != userId },
invite3pids = invite3pids,
creationContent = params.creationContent.takeIf { it.isNotEmpty() },
initialStates = initialStates,
preset = params.preset,
isDirect = params.isDirect,
- powerLevelContentOverride = params.powerLevelContentOverride
+ powerLevelContentOverride = params.powerLevelContentOverride,
+ roomVersion = params.roomVersion
+
)
}
@@ -120,6 +137,31 @@ internal class CreateRoomBodyBuilder @Inject constructor(
}
}
+ private fun buildGuestAccess(params: CreateRoomParams): Event? {
+ return params.guestAccess
+ ?.let {
+ Event(
+ type = EventType.STATE_ROOM_GUEST_ACCESS,
+ stateKey = "",
+ content = mapOf("guest_access" to it.value)
+ )
+ }
+ }
+
+ 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/create/CreateRoomTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/create/CreateRoomTask.kt
index bafe2b90ae..de6a71e581 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
@@ -102,7 +102,7 @@ internal class DefaultCreateRoomTask @Inject constructor(
.equalTo(RoomSummaryEntityFields.MEMBERSHIP_STR, Membership.JOIN.name)
}
} catch (exception: TimeoutCancellationException) {
- throw CreateRoomFailure.CreatedWithTimeout
+ throw CreateRoomFailure.CreatedWithTimeout(roomId)
}
Realm.getInstance(realmConfiguration).executeTransactionAsync {
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/peeking/PeekRoomTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/peeking/PeekRoomTask.kt
index 5b211c505f..c6f4bbb4e1 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/peeking/PeekRoomTask.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/peeking/PeekRoomTask.kt
@@ -23,11 +23,14 @@ import org.matrix.android.sdk.api.session.events.model.toModel
import org.matrix.android.sdk.api.session.room.model.RoomAvatarContent
import org.matrix.android.sdk.api.session.room.model.RoomCanonicalAliasContent
import org.matrix.android.sdk.api.session.room.model.RoomDirectoryVisibility
+import org.matrix.android.sdk.api.session.room.model.RoomMemberContent
import org.matrix.android.sdk.api.session.room.model.RoomNameContent
import org.matrix.android.sdk.api.session.room.model.RoomTopicContent
+import org.matrix.android.sdk.api.session.room.model.create.RoomCreateContent
import org.matrix.android.sdk.api.session.room.model.roomdirectory.PublicRoomsFilter
import org.matrix.android.sdk.api.session.room.model.roomdirectory.PublicRoomsParams
import org.matrix.android.sdk.api.session.room.peeking.PeekResult
+import org.matrix.android.sdk.api.util.MatrixItem
import org.matrix.android.sdk.internal.session.room.alias.GetRoomIdByAliasTask
import org.matrix.android.sdk.internal.session.room.directory.GetPublicRoomTask
import org.matrix.android.sdk.internal.session.room.directory.GetRoomDirectoryVisibilityTask
@@ -100,7 +103,9 @@ internal class DefaultPeekRoomTask @Inject constructor(
name = publicRepoResult.name,
topic = publicRepoResult.topic,
numJoinedMembers = publicRepoResult.numJoinedMembers,
- viaServers = serverList
+ viaServers = serverList,
+ roomType = null, // would be nice to get that from directory...
+ someMembers = null
)
}
@@ -125,11 +130,25 @@ internal class DefaultPeekRoomTask @Inject constructor(
?.let { it.content?.toModel()?.canonicalAlias }
// not sure if it's the right way to do that :/
- val memberCount = stateEvents
+ val membersEvent = stateEvents
.filter { it.type == EventType.STATE_ROOM_MEMBER && it.stateKey?.isNotEmpty() == true }
+
+ val memberCount = membersEvent
.distinctBy { it.stateKey }
.count()
+ val someMembers = membersEvent.mapNotNull { ev ->
+ ev.content?.toModel()?.let {
+ MatrixItem.UserItem(ev.stateKey ?: "", it.displayName, it.avatarUrl)
+ }
+ }
+
+ val roomType = stateEvents
+ .lastOrNull { it.type == EventType.STATE_ROOM_CREATE }
+ ?.content
+ ?.toModel()
+ ?.type
+
return PeekResult.Success(
roomId = roomId,
alias = alias,
@@ -137,7 +156,9 @@ internal class DefaultPeekRoomTask @Inject constructor(
name = name,
topic = topic,
numJoinedMembers = memberCount,
- viaServers = serverList
+ roomType = roomType,
+ viaServers = serverList,
+ someMembers = someMembers
)
} catch (failure: Throwable) {
// Would be M_FORBIDDEN if cannot peek :/
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
new file mode 100644
index 0000000000..2efea7f118
--- /dev/null
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/relationship/RoomChildRelationInfo.kt
@@ -0,0 +1,104 @@
+/*
+ * Copyright 2020 The Matrix.org Foundation C.I.C.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.matrix.android.sdk.internal.session.room.relationship
+
+import io.realm.Realm
+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.space.model.SpaceChildContent
+import org.matrix.android.sdk.api.session.space.model.SpaceParentContent
+import org.matrix.android.sdk.internal.database.mapper.ContentMapper
+import org.matrix.android.sdk.internal.database.model.CurrentStateEventEntity
+import org.matrix.android.sdk.internal.database.query.whereType
+
+/**
+ * Relationship between rooms and spaces
+ * The intention is that rooms and spaces form a hierarchy, which clients can use to structure the user's room list into a tree view.
+ * The parent/child relationship can be expressed in one of two ways:
+ * - The admins of a space can advertise rooms and subspaces for their space by setting m.space.child state events.
+ * The state_key is the ID of a child room or space, and the content should contain a via key which gives
+ * a list of candidate servers that can be used to join the room. present: true key is included to distinguish from a deleted state event.
+ *
+ * - Separately, rooms can claim parents via the m.room.parent state event.
+ */
+internal class RoomChildRelationInfo(
+ private val realm: Realm,
+ private val roomId: String
+) {
+
+ data class SpaceChildInfo(
+ val roomId: String,
+ val order: String?,
+ val autoJoin: Boolean,
+ val viaServers: List
+ )
+
+ data class SpaceParentInfo(
+ val roomId: String,
+ val canonical: Boolean,
+ val viaServers: List,
+ val stateEventSender: String
+ )
+
+ /**
+ * Gets the ordered list of valid child description.
+ */
+ fun getDirectChildrenDescriptions(): List {
+ return CurrentStateEventEntity.whereType(realm, roomId, EventType.STATE_SPACE_CHILD)
+ .findAll()
+// .also {
+// Timber.v("## Space: Found ${it.count()} m.space.child state events for $roomId")
+// }
+ .mapNotNull {
+ ContentMapper.map(it.root?.content).toModel()?.let { scc ->
+// Timber.v("## Space child desc state event $scc")
+ // Children where via is not present are ignored.
+ scc.via?.let { via ->
+ SpaceChildInfo(
+ roomId = it.stateKey,
+ order = scc.validOrder(),
+ autoJoin = scc.autoJoin ?: false,
+ viaServers = via
+ )
+ }
+ }
+ }
+ .sortedBy { it.order }
+ }
+
+ fun getParentDescriptions(): List {
+ return CurrentStateEventEntity.whereType(realm, roomId, EventType.STATE_SPACE_PARENT)
+ .findAll()
+// .also {
+// Timber.v("## Space: Found ${it.count()} m.space.parent state events for $roomId")
+// }
+ .mapNotNull {
+ ContentMapper.map(it.root?.content).toModel()?.let { scc ->
+// Timber.v("## Space parent desc state event $scc")
+ // Parent where via is not present are ignored.
+ scc.via?.let { via ->
+ SpaceParentInfo(
+ roomId = it.stateKey,
+ canonical = scc.canonical ?: false,
+ viaServers = via,
+ stateEventSender = it.root?.sender ?: ""
+ )
+ }
+ }
+ }
+ }
+}
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/queue/EventSenderProcessor.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/queue/EventSenderProcessor.kt
index 8bafa5f882..cd5bf575db 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/queue/EventSenderProcessor.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/queue/EventSenderProcessor.kt
@@ -18,7 +18,7 @@ package org.matrix.android.sdk.internal.session.room.send.queue
import org.matrix.android.sdk.api.session.events.model.Event
import org.matrix.android.sdk.api.util.Cancelable
-import org.matrix.android.sdk.internal.session.SessionLifecycleObserver
+import org.matrix.android.sdk.api.session.SessionLifecycleObserver
internal interface EventSenderProcessor: SessionLifecycleObserver {
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 a5c09f5ff6..80bfd02b0e 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
@@ -25,6 +25,7 @@ import org.matrix.android.sdk.api.auth.data.SessionParams
import org.matrix.android.sdk.api.failure.Failure
import org.matrix.android.sdk.api.failure.MatrixError
import org.matrix.android.sdk.api.failure.getRetryDelay
+import org.matrix.android.sdk.api.session.Session
import org.matrix.android.sdk.api.session.crypto.CryptoService
import org.matrix.android.sdk.api.session.events.model.Event
import org.matrix.android.sdk.api.util.Cancelable
@@ -72,7 +73,7 @@ internal class EventSenderProcessorCoroutine @Inject constructor(
*/
private val cancelableBag = ConcurrentHashMap()
- override fun onSessionStarted() {
+ override fun onSessionStarted(session: Session) {
// We should check for sending events not handled because app was killed
// But we should be careful of only took those that was submitted to us, because if it's
// for example it's a media event it is handled by some worker and he will handle it
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 b79a86dd7e..9db7cc9039 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
@@ -25,6 +25,7 @@ import org.matrix.android.sdk.api.extensions.tryOrNull
import org.matrix.android.sdk.api.failure.Failure
import org.matrix.android.sdk.api.failure.MatrixError
import org.matrix.android.sdk.api.failure.isTokenError
+import org.matrix.android.sdk.api.session.Session
import org.matrix.android.sdk.api.session.crypto.CryptoService
import org.matrix.android.sdk.api.session.events.model.Event
import org.matrix.android.sdk.api.session.sync.SyncState
@@ -64,11 +65,11 @@ internal class EventSenderProcessorThread @Inject constructor(
memento.unTrack(task)
}
- override fun onSessionStarted() {
+ override fun onSessionStarted(session: Session) {
start()
}
- override fun onSessionStopped() {
+ override fun onSessionStopped(session: Session) {
interrupt()
}
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 615bc99096..ff2afb5d61 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
@@ -34,6 +34,7 @@ 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
internal class DefaultStateService @AssistedInject constructor(@Assisted private val roomId: String,
private val stateEventDataSource: StateEventDataSource,
@@ -73,7 +74,7 @@ internal class DefaultStateService @AssistedInject constructor(@Assisted private
eventType = eventType,
body = body.toSafeJson(eventType)
)
- sendStateTask.execute(params)
+ sendStateTask.executeRetry(params, 3)
}
private fun JsonDict.toSafeJson(eventType: String): JsonDict {
@@ -127,6 +128,7 @@ internal class DefaultStateService @AssistedInject constructor(@Assisted private
override suspend fun updateJoinRule(joinRules: RoomJoinRules?, guestAccess: GuestAccess?) {
if (joinRules != null) {
+ if (joinRules == RoomJoinRules.RESTRICTED) throw UnsupportedOperationException("No yet supported")
sendStateEvent(
eventType = EventType.STATE_ROOM_JOIN_RULES,
body = mapOf("join_rule" to joinRules),
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/state/SafePowerLevelContent.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/state/SafePowerLevelContent.kt
index a97709e38b..197b4f8688 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/state/SafePowerLevelContent.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/state/SafePowerLevelContent.kt
@@ -21,22 +21,21 @@ import com.squareup.moshi.JsonClass
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.model.PowerLevelsContent
-import org.matrix.android.sdk.api.session.room.powerlevels.Role
import org.matrix.android.sdk.api.util.JsonDict
@JsonClass(generateAdapter = true)
internal data class SerializablePowerLevelsContent(
- @Json(name = "ban") val ban: Int = Role.Moderator.value,
- @Json(name = "kick") val kick: Int = Role.Moderator.value,
- @Json(name = "invite") val invite: Int = Role.Moderator.value,
- @Json(name = "redact") val redact: Int = Role.Moderator.value,
- @Json(name = "events_default") val eventsDefault: Int = Role.Default.value,
- @Json(name = "events") val events: Map = emptyMap(),
- @Json(name = "users_default") val usersDefault: Int = Role.Default.value,
- @Json(name = "users") val users: Map = emptyMap(),
- @Json(name = "state_default") val stateDefault: Int = Role.Moderator.value,
+ @Json(name = "ban") val ban: Int?,
+ @Json(name = "kick") val kick: Int?,
+ @Json(name = "invite") val invite: Int?,
+ @Json(name = "redact") val redact: Int?,
+ @Json(name = "events_default") val eventsDefault: Int?,
+ @Json(name = "events") val events: Map?,
+ @Json(name = "users_default") val usersDefault: Int?,
+ @Json(name = "users") val users: Map?,
+ @Json(name = "state_default") val stateDefault: Int?,
// `Int` is the diff here (instead of `Any`)
- @Json(name = "notifications") val notifications: Map = emptyMap()
+ @Json(name = "notifications") val notifications: Map?
)
internal fun JsonDict.toSafePowerLevelsContentDict(): JsonDict {
@@ -52,7 +51,7 @@ internal fun JsonDict.toSafePowerLevelsContentDict(): JsonDict {
usersDefault = content.usersDefault,
users = content.users,
stateDefault = content.stateDefault,
- notifications = content.notifications.mapValues { content.notificationLevel(it.key) }
+ notifications = content.notifications?.mapValues { content.notificationLevel(it.key) }
)
}
?.toContent()
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/summary/GraphUtils.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/summary/GraphUtils.kt
new file mode 100644
index 0000000000..b7e6548b54
--- /dev/null
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/summary/GraphUtils.kt
@@ -0,0 +1,166 @@
+/*
+ * 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.summary
+
+import java.util.LinkedList
+
+data class GraphNode(
+ val name: String
+)
+
+data class GraphEdge(
+ val source: GraphNode,
+ val destination: GraphNode
+)
+
+class Graph {
+
+ private val adjacencyList: HashMap> = HashMap()
+
+ fun getOrCreateNode(name: String): GraphNode {
+ return adjacencyList.entries.firstOrNull { it.key.name == name }?.key
+ ?: GraphNode(name).also {
+ adjacencyList[it] = ArrayList()
+ }
+ }
+
+ fun addEdge(sourceName: String, destinationName: String) {
+ val source = getOrCreateNode(sourceName)
+ val destination = getOrCreateNode(destinationName)
+ adjacencyList.getOrPut(source) { ArrayList() }.add(
+ GraphEdge(source, destination)
+ )
+ }
+
+ fun addEdge(source: GraphNode, destination: GraphNode) {
+ adjacencyList.getOrPut(source) { ArrayList() }.add(
+ GraphEdge(source, destination)
+ )
+ }
+
+ fun edgesOf(node: GraphNode): List {
+ return adjacencyList[node]?.toList() ?: emptyList()
+ }
+
+ fun withoutEdges(edgesToPrune: List): Graph {
+ val output = Graph()
+ this.adjacencyList.forEach { (vertex, edges) ->
+ output.getOrCreateNode(vertex.name)
+ edges.forEach {
+ if (!edgesToPrune.contains(it)) {
+ // add it
+ output.addEdge(it.source, it.destination)
+ }
+ }
+ }
+ return output
+ }
+
+ /**
+ * Depending on the chosen starting point the background edge might change
+ */
+ fun findBackwardEdges(startFrom: GraphNode? = null): List {
+ val backwardEdges = mutableSetOf()
+ val visited = mutableMapOf()
+ val notVisited = -1
+ val inPath = 0
+ val completed = 1
+ adjacencyList.keys.forEach {
+ visited[it] = notVisited
+ }
+ val stack = LinkedList()
+
+ (startFrom ?: adjacencyList.entries.firstOrNull { visited[it.key] == notVisited }?.key)
+ ?.let {
+ stack.push(it)
+ visited[it] = inPath
+ }
+
+ while (stack.isNotEmpty()) {
+// Timber.w("VAL: current stack: ${stack.reversed().joinToString { it.name }}")
+ val vertex = stack.peek() ?: break
+ // peek a path to follow
+ var destination: GraphNode? = null
+ edgesOf(vertex).forEach {
+ when (visited[it.destination]) {
+ notVisited -> {
+ // it's a candidate
+ destination = it.destination
+ }
+ inPath -> {
+ // Cycle!!
+ backwardEdges.add(it)
+ }
+ completed -> {
+ // dead end
+ }
+ }
+ }
+ if (destination == null) {
+ // dead end, pop
+ stack.pop().let {
+ visited[it] = completed
+ }
+ } else {
+ // go down this path
+ stack.push(destination)
+ visited[destination!!] = inPath
+ }
+
+ if (stack.isEmpty()) {
+ // try to get another graph of forest?
+ adjacencyList.entries.firstOrNull { visited[it.key] == notVisited }?.key?.let {
+ stack.push(it)
+ visited[it] = inPath
+ }
+ }
+ }
+
+ return backwardEdges.toList()
+ }
+
+ /**
+ * Only call that on acyclic graph!
+ */
+ fun flattenDestination(): Map> {
+ val result = HashMap>()
+ adjacencyList.keys.forEach { vertex ->
+ result[vertex] = flattenOf(vertex)
+ }
+ return result
+ }
+
+ private fun flattenOf(node: GraphNode): Set {
+ val result = mutableSetOf()
+ val edgesOf = edgesOf(node)
+ result.addAll(edgesOf.map { it.destination })
+ edgesOf.forEach {
+ result.addAll(flattenOf(it.destination))
+ }
+ return result
+ }
+
+ override fun toString(): String {
+ return buildString {
+ adjacencyList.forEach { (node, edges) ->
+ append("${node.name} : [")
+ append(edges.joinToString(" ") { it.destination.name })
+ append("]\n")
+ }
+ }
+ }
+}
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/summary/HierarchyLiveDataHelper.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/summary/HierarchyLiveDataHelper.kt
new file mode 100644
index 0000000000..29db8431fd
--- /dev/null
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/summary/HierarchyLiveDataHelper.kt
@@ -0,0 +1,67 @@
+/*
+ * 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.summary
+
+import androidx.lifecycle.LiveData
+import androidx.lifecycle.MediatorLiveData
+import org.matrix.android.sdk.api.session.room.model.Membership
+import org.matrix.android.sdk.api.session.room.model.RoomSummary
+import org.matrix.android.sdk.api.util.Optional
+
+internal class HierarchyLiveDataHelper(
+ val spaceId: String,
+ val memberships: List,
+ val roomSummaryDataSource: RoomSummaryDataSource) {
+
+ private val sources = HashMap>>()
+ private val mediatorLiveData = MediatorLiveData>()
+
+ fun liveData(): LiveData> = mediatorLiveData
+
+ init {
+ onChange()
+ }
+
+ private fun parentsToCheck(): List {
+ val spaces = ArrayList()
+ roomSummaryDataSource.getSpaceSummary(spaceId)?.let {
+ roomSummaryDataSource.flattenSubSpace(it, emptyList(), spaces, memberships)
+ }
+ return spaces
+ }
+
+ private fun onChange() {
+ val existingSources = sources.keys.toList()
+ val newSources = parentsToCheck().map { it.roomId }
+ val addedSources = newSources.filter { !existingSources.contains(it) }
+ val removedSource = existingSources.filter { !newSources.contains(it) }
+ addedSources.forEach {
+ val liveData = roomSummaryDataSource.getSpaceSummaryLive(it)
+ mediatorLiveData.addSource(liveData) { onChange() }
+ sources[it] = liveData
+ }
+
+ removedSource.forEach {
+ sources[it]?.let { mediatorLiveData.removeSource(it) }
+ }
+
+ sources[spaceId]?.value?.getOrNull()?.let { spaceSummary ->
+ val results = ArrayList()
+ roomSummaryDataSource.flattenChild(spaceSummary, emptyList(), results, memberships)
+ mediatorLiveData.postValue(results.map { it.roomId })
+ }
+ }
+}
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 c3e66cd7b4..94c7860303 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
@@ -1,5 +1,6 @@
/*
* Copyright 2020 The Matrix.org Foundation C.I.C.
+ * Copyright 2021 The Matrix.org Foundation C.I.C.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -17,6 +18,7 @@
package org.matrix.android.sdk.internal.session.room.summary
import androidx.lifecycle.LiveData
+import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.Transformations
import androidx.paging.LivePagedListBuilder
import androidx.paging.PagedList
@@ -24,12 +26,21 @@ import com.zhuinden.monarchy.Monarchy
import io.realm.Realm
import io.realm.RealmQuery
import io.realm.Sort
+import io.realm.kotlin.where
+import org.matrix.android.sdk.api.query.ActiveSpaceFilter
import org.matrix.android.sdk.api.query.RoomCategoryFilter
+import org.matrix.android.sdk.api.session.room.ResultBoundaries
+import org.matrix.android.sdk.api.session.room.RoomSortOrder
import org.matrix.android.sdk.api.session.room.RoomSummaryQueryParams
-import org.matrix.android.sdk.api.session.room.UpdatableFilterLivePageResult
+import org.matrix.android.sdk.api.session.room.UpdatableLivePageResult
+import org.matrix.android.sdk.api.session.room.model.Membership
import org.matrix.android.sdk.api.session.room.model.RoomSummary
+import org.matrix.android.sdk.api.session.room.model.RoomType
import org.matrix.android.sdk.api.session.room.model.VersioningState
+import org.matrix.android.sdk.api.session.room.roomSummaryQueryParams
+import org.matrix.android.sdk.api.session.room.spaceSummaryQueryParams
import org.matrix.android.sdk.api.session.room.summary.RoomAggregateNotificationCount
+import org.matrix.android.sdk.api.session.space.SpaceSummaryQueryParams
import org.matrix.android.sdk.api.util.Optional
import org.matrix.android.sdk.api.util.toOptional
import org.matrix.android.sdk.internal.database.mapper.RoomSummaryMapper
@@ -80,11 +91,60 @@ internal class RoomSummaryDataSource @Inject constructor(@SessionDatabase privat
fun getRoomSummariesLive(queryParams: RoomSummaryQueryParams): LiveData> {
return monarchy.findAllMappedWithChanges(
- { roomSummariesQuery(it, queryParams) },
+ {
+ roomSummariesQuery(it, queryParams)
+ .sort(RoomSummaryEntityFields.LAST_ACTIVITY_TIME, Sort.DESCENDING)
+ },
{ roomSummaryMapper.map(it) }
)
}
+ fun getSpaceSummariesLive(queryParams: SpaceSummaryQueryParams): LiveData> {
+ return getRoomSummariesLive(queryParams)
+ }
+
+ fun getSpaceSummary(roomIdOrAlias: String): RoomSummary? {
+ return getRoomSummary(roomIdOrAlias)
+ ?.takeIf { it.roomType == RoomType.SPACE }
+ }
+
+ fun getSpaceSummaryLive(roomId: String): LiveData> {
+ val liveData = monarchy.findAllMappedWithChanges(
+ { realm ->
+ RoomSummaryEntity.where(realm, roomId)
+ .isNotEmpty(RoomSummaryEntityFields.DISPLAY_NAME)
+ .equalTo(RoomSummaryEntityFields.ROOM_TYPE, RoomType.SPACE)
+ },
+ {
+ roomSummaryMapper.map(it)
+ }
+ )
+ return Transformations.map(liveData) { results ->
+ results.firstOrNull().toOptional()
+ }
+ }
+
+ fun getSpaceSummaries(spaceSummaryQueryParams: SpaceSummaryQueryParams): List {
+ return getRoomSummaries(spaceSummaryQueryParams)
+ }
+
+ fun getRootSpaceSummaries(): List {
+ return getRoomSummaries(spaceSummaryQueryParams {
+ memberships = listOf(Membership.JOIN)
+ })
+ .let { allJoinedSpace ->
+ val allFlattenChildren = arrayListOf()
+ allJoinedSpace.forEach {
+ flattenSubSpace(it, emptyList(), allFlattenChildren, listOf(Membership.JOIN), false)
+ }
+ val knownNonOrphan = allFlattenChildren.map { it.roomId }.distinct()
+ // keep only root rooms
+ allJoinedSpace.filter { candidate ->
+ !knownNonOrphan.contains(candidate.roomId)
+ }
+ }
+ }
+
fun getBreadcrumbs(queryParams: RoomSummaryQueryParams): List {
return monarchy.fetchAllMappedSync(
{ breadcrumbsQuery(it, queryParams) },
@@ -106,10 +166,10 @@ internal class RoomSummaryDataSource @Inject constructor(@SessionDatabase privat
}
fun getSortedPagedRoomSummariesLive(queryParams: RoomSummaryQueryParams,
- pagedListConfig: PagedList.Config): LiveData> {
+ pagedListConfig: PagedList.Config,
+ sortOrder: RoomSortOrder): LiveData> {
val realmDataSourceFactory = monarchy.createDataSourceFactory { realm ->
- roomSummariesQuery(realm, queryParams)
- .sort(RoomSummaryEntityFields.LAST_ACTIVITY_TIME, Sort.DESCENDING)
+ roomSummariesQuery(realm, queryParams).process(sortOrder)
}
val dataSourceFactory = realmDataSourceFactory.map {
roomSummaryMapper.map(it)
@@ -120,30 +180,48 @@ internal class RoomSummaryDataSource @Inject constructor(@SessionDatabase privat
)
}
- fun getFilteredPagedRoomSummariesLive(queryParams: RoomSummaryQueryParams,
- pagedListConfig: PagedList.Config): UpdatableFilterLivePageResult {
+ fun getUpdatablePagedRoomSummariesLive(queryParams: RoomSummaryQueryParams,
+ pagedListConfig: PagedList.Config,
+ sortOrder: RoomSortOrder): UpdatableLivePageResult {
val realmDataSourceFactory = monarchy.createDataSourceFactory { realm ->
- roomSummariesQuery(realm, queryParams)
- .sort(RoomSummaryEntityFields.LAST_ACTIVITY_TIME, Sort.DESCENDING)
+ roomSummariesQuery(realm, queryParams).process(sortOrder)
}
val dataSourceFactory = realmDataSourceFactory.map {
roomSummaryMapper.map(it)
}
+ val boundaries = MutableLiveData(ResultBoundaries())
+
val mapped = monarchy.findAllPagedWithChanges(
realmDataSourceFactory,
- LivePagedListBuilder(dataSourceFactory, pagedListConfig)
+ LivePagedListBuilder(dataSourceFactory, pagedListConfig).also {
+ it.setBoundaryCallback(object : PagedList.BoundaryCallback() {
+ override fun onItemAtEndLoaded(itemAtEnd: RoomSummary) {
+ boundaries.postValue(boundaries.value?.copy(frontLoaded = true))
+ }
+
+ override fun onItemAtFrontLoaded(itemAtFront: RoomSummary) {
+ boundaries.postValue(boundaries.value?.copy(endLoaded = true))
+ }
+
+ override fun onZeroItemsLoaded() {
+ boundaries.postValue(boundaries.value?.copy(zeroItemLoaded = true))
+ }
+ })
+ }
)
- return object : UpdatableFilterLivePageResult {
+ return object : UpdatableLivePageResult {
override val livePagedList: LiveData> = mapped
- override fun updateQuery(queryParams: RoomSummaryQueryParams) {
+ override fun updateQuery(builder: (RoomSummaryQueryParams) -> RoomSummaryQueryParams) {
realmDataSourceFactory.updateQuery {
- roomSummariesQuery(it, queryParams)
- .sort(RoomSummaryEntityFields.LAST_ACTIVITY_TIME, Sort.DESCENDING)
+ roomSummariesQuery(it, builder.invoke(queryParams)).process(sortOrder)
}
}
+
+ override val liveBoundaries: LiveData
+ get() = boundaries
}
}
@@ -176,12 +254,12 @@ 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.beginGroup()
.greaterThan(RoomSummaryEntityFields.NOTIFICATION_COUNT, 0).or()
.equalTo(RoomSummaryEntityFields.MARKED_UNREAD, true).endGroup()
- RoomCategoryFilter.ALL -> {
+ RoomCategoryFilter.ALL -> {
// nop
}
}
@@ -197,6 +275,162 @@ internal class RoomSummaryDataSource @Inject constructor(@SessionDatabase privat
query.equalTo(RoomSummaryEntityFields.IS_SERVER_NOTICE, sn)
}
}
+
+ queryParams.excludeType?.forEach {
+ query.notEqualTo(RoomSummaryEntityFields.ROOM_TYPE, it)
+ }
+ queryParams.includeType?.forEach {
+ 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_WITH_NOTIFICATIONS -> query.beginGroup()
+ .greaterThan(RoomSummaryEntityFields.NOTIFICATION_COUNT, 0).or()
+ .equalTo(RoomSummaryEntityFields.MARKED_UNREAD, true).endGroup()
+ RoomCategoryFilter.ALL -> Unit // nop
+ }
+
+ // Timber.w("VAL: activeSpaceId : ${queryParams.activeSpaceId}")
+ when (queryParams.activeSpaceId) {
+ 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.activeSpaceId.currentSpaceId == null) {
+ // orphan rooms
+ query.isNull(RoomSummaryEntityFields.FLATTEN_PARENT_IDS)
+ } else {
+ query.contains(RoomSummaryEntityFields.FLATTEN_PARENT_IDS, queryParams.activeSpaceId.currentSpaceId)
+ }
+ }
+ is ActiveSpaceFilter.ExcludeSpace -> {
+ query.not().contains(RoomSummaryEntityFields.FLATTEN_PARENT_IDS, queryParams.activeSpaceId.spaceId)
+ }
+ else -> {
+ // nop
+ }
+ }
+
+ if (queryParams.activeGroupId != null) {
+ query.contains(RoomSummaryEntityFields.GROUP_IDS, queryParams.activeGroupId!!)
+ }
return query
}
+
+ fun getAllRoomSummaryChildOf(spaceAliasOrId: String, memberShips: List): List {
+ val space = getSpaceSummary(spaceAliasOrId) ?: return emptyList()
+ val result = ArrayList()
+ flattenChild(space, emptyList(), result, memberShips)
+ return result
+ }
+
+ fun getAllRoomSummaryChildOfLive(spaceId: String, memberShips: List): LiveData> {
+ // we want to listen to all spaces in hierarchy and on change compute back all childs
+ // and switch map to listen those?
+ val mediatorLiveData = HierarchyLiveDataHelper(spaceId, memberShips, this).liveData()
+
+ return Transformations.switchMap(mediatorLiveData) { allIds ->
+ monarchy.findAllMappedWithChanges(
+ {
+ it.where()
+ .`in`(RoomSummaryEntityFields.ROOM_ID, allIds.toTypedArray())
+ .`in`(RoomSummaryEntityFields.MEMBERSHIP_STR, memberShips.map { it.name }.toTypedArray())
+ .equalTo(RoomSummaryEntityFields.IS_DIRECT, false)
+ },
+ {
+ roomSummaryMapper.map(it)
+ })
+ }
+ }
+
+ fun getFlattenOrphanRooms(): List {
+ return getRoomSummaries(
+ roomSummaryQueryParams {
+ memberships = Membership.activeMemberships()
+ excludeType = listOf(RoomType.SPACE)
+ roomCategoryFilter = RoomCategoryFilter.ONLY_ROOMS
+ }
+ ).filter { isOrphan(it) }
+ }
+
+ fun getFlattenOrphanRoomsLive(): LiveData> {
+ return Transformations.map(
+ getRoomSummariesLive(roomSummaryQueryParams {
+ memberships = Membership.activeMemberships()
+ excludeType = listOf(RoomType.SPACE)
+ roomCategoryFilter = RoomCategoryFilter.ONLY_ROOMS
+ })
+ ) {
+ it.filter { isOrphan(it) }
+ }
+ }
+
+ private fun isOrphan(roomSummary: RoomSummary): Boolean {
+ if (roomSummary.roomType == RoomType.SPACE && roomSummary.membership.isActive()) {
+ return false
+ }
+ // all parents line should be orphan
+ roomSummary.spaceParents?.forEach { info ->
+ if (info.roomSummary != null && !info.roomSummary.membership.isLeft()) {
+ if (!isOrphan(info.roomSummary)) {
+ return false
+ }
+ }
+ }
+
+ // it may not have a parent relation but could be a child of some other....
+ for (spaceSummary in getSpaceSummaries(spaceSummaryQueryParams { memberships = Membership.activeMemberships() })) {
+ if (spaceSummary.spaceChildren?.any { it.childRoomId == roomSummary.roomId } == true) {
+ return false
+ }
+ }
+
+ return true
+ }
+
+ fun flattenChild(current: RoomSummary, parenting: List, output: MutableList, memberShips: List) {
+ current.spaceChildren?.sortedBy { it.order ?: it.name }?.forEach { childInfo ->
+ if (childInfo.roomType == RoomType.SPACE) {
+ // Add recursive
+ if (!parenting.contains(childInfo.childRoomId)) { // avoid cycles!
+ getSpaceSummary(childInfo.childRoomId)?.let { subSpace ->
+ if (memberShips.isEmpty() || memberShips.contains(subSpace.membership)) {
+ flattenChild(subSpace, parenting + listOf(current.roomId), output, memberShips)
+ }
+ }
+ }
+ } else if (childInfo.isKnown) {
+ getRoomSummary(childInfo.childRoomId)?.let {
+ if (memberShips.isEmpty() || memberShips.contains(it.membership)) {
+ if (!it.isDirect) {
+ output.add(it)
+ }
+ }
+ }
+ }
+ }
+ }
+
+ fun flattenSubSpace(current: RoomSummary,
+ parenting: List,
+ output: MutableList,
+ memberShips: List,
+ includeCurrent: Boolean = true) {
+ if (includeCurrent) {
+ output.add(current)
+ }
+ current.spaceChildren?.sortedBy { it.order ?: it.name }?.forEach {
+ if (it.roomType == RoomType.SPACE) {
+ // Add recursive
+ if (!parenting.contains(it.childRoomId)) { // avoid cycles!
+ getSpaceSummary(it.childRoomId)?.let { subSpace ->
+ if (memberShips.isEmpty() || memberShips.contains(subSpace.membership)) {
+ output.add(subSpace)
+ flattenSubSpace(subSpace, parenting + listOf(current.roomId), output, memberShips)
+ }
+ }
+ }
+ }
+ }
+ }
}
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 11b96f591e..dfec3fee1c 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
@@ -17,6 +17,7 @@
package org.matrix.android.sdk.internal.session.room.summary
import io.realm.Realm
+import io.realm.kotlin.createObject
import org.matrix.android.sdk.api.extensions.tryOrNull
import org.matrix.android.sdk.api.session.events.model.EventType
import org.matrix.android.sdk.api.session.events.model.toModel
@@ -25,6 +26,8 @@ import org.matrix.android.sdk.api.session.room.model.RoomAliasesContent
import org.matrix.android.sdk.api.session.room.model.RoomCanonicalAliasContent
import org.matrix.android.sdk.api.session.room.model.RoomNameContent
import org.matrix.android.sdk.api.session.room.model.RoomTopicContent
+import org.matrix.android.sdk.api.session.room.model.RoomType
+import org.matrix.android.sdk.api.session.room.model.create.RoomCreateContent
import org.matrix.android.sdk.api.session.room.send.SendState
import org.matrix.android.sdk.internal.crypto.EventDecryptor
import org.matrix.android.sdk.internal.crypto.MXCRYPTO_ALGORITHM_MEGOLM
@@ -34,29 +37,40 @@ import org.matrix.android.sdk.internal.database.mapper.asDomain
import org.matrix.android.sdk.internal.database.model.CurrentStateEventEntity
import org.matrix.android.sdk.internal.database.model.EventEntity
import org.matrix.android.sdk.internal.database.model.EventEntityFields
+import org.matrix.android.sdk.internal.database.model.GroupSummaryEntity
import org.matrix.android.sdk.internal.database.model.RoomMemberSummaryEntityFields
import org.matrix.android.sdk.internal.database.model.RoomSummaryEntity
+import org.matrix.android.sdk.internal.database.model.RoomSummaryEntityFields
+import org.matrix.android.sdk.internal.database.model.SpaceChildSummaryEntity
+import org.matrix.android.sdk.internal.database.model.SpaceParentSummaryEntity
import org.matrix.android.sdk.internal.database.model.TimelineEventEntity
import org.matrix.android.sdk.internal.database.query.findAllInRoomWithSendStates
import org.matrix.android.sdk.internal.database.query.getOrCreate
import org.matrix.android.sdk.internal.database.query.getOrNull
import org.matrix.android.sdk.internal.database.query.isEventRead
+import org.matrix.android.sdk.internal.database.query.where
import org.matrix.android.sdk.internal.database.query.whereType
import org.matrix.android.sdk.internal.di.UserId
+import org.matrix.android.sdk.internal.extensions.clearWith
+import org.matrix.android.sdk.internal.query.process
import org.matrix.android.sdk.internal.session.room.RoomAvatarResolver
import org.matrix.android.sdk.internal.session.room.membership.RoomDisplayNameResolver
import org.matrix.android.sdk.internal.session.room.membership.RoomMemberHelper
+import org.matrix.android.sdk.internal.session.room.relationship.RoomChildRelationInfo
+import org.matrix.android.sdk.internal.session.room.state.StateEventDataSource
import org.matrix.android.sdk.internal.session.sync.model.RoomSyncSummary
import org.matrix.android.sdk.internal.session.sync.model.RoomSyncUnreadNotifications
import timber.log.Timber
import javax.inject.Inject
+import kotlin.system.measureTimeMillis
internal class RoomSummaryUpdater @Inject constructor(
@UserId private val userId: String,
private val roomDisplayNameResolver: RoomDisplayNameResolver,
private val roomAvatarResolver: RoomAvatarResolver,
private val eventDecryptor: EventDecryptor,
- private val crossSigningService: DefaultCrossSigningService) {
+ private val crossSigningService: DefaultCrossSigningService,
+ private val stateEventDataSource: StateEventDataSource) {
fun update(realm: Realm,
roomId: String,
@@ -89,6 +103,11 @@ internal class RoomSummaryUpdater @Inject constructor(
val lastTopicEvent = CurrentStateEventEntity.getOrNull(realm, roomId, type = EventType.STATE_ROOM_TOPIC, stateKey = "")?.root
val lastCanonicalAliasEvent = CurrentStateEventEntity.getOrNull(realm, roomId, type = EventType.STATE_ROOM_CANONICAL_ALIAS, stateKey = "")?.root
val lastAliasesEvent = CurrentStateEventEntity.getOrNull(realm, roomId, type = EventType.STATE_ROOM_ALIASES, stateKey = "")?.root
+ val roomCreateEvent = CurrentStateEventEntity.getOrNull(realm, roomId, type = EventType.STATE_ROOM_CREATE, stateKey = "")?.root
+
+ val roomType = ContentMapper.map(roomCreateEvent?.content).toModel()?.type
+ roomSummaryEntity.roomType = roomType
+ Timber.v("## Space: Updating summary room [$roomId] roomType: [$roomType]")
// Don't use current state for this one as we are only interested in having MXCRYPTO_ALGORITHM_MEGOLM event in the room
val encryptionEvent = EventEntity.whereType(realm, roomId = roomId, type = EventType.STATE_ROOM_ENCRYPTION)
@@ -177,4 +196,238 @@ internal class RoomSummaryUpdater @Inject constructor(
roomSummaryEntity.latestPreviewableContentEvent = RoomSummaryEventsHelper.getLatestPreviewableEvent(realm, roomId)
roomSummaryEntity.latestPreviewableOriginalContentEvent = RoomSummaryEventsHelper.getLatestPreviewableEventScOriginalContent(realm, roomId)
}
+
+ /**
+ * Should be called at the end of the room sync, to check and validate all parent/child relations
+ */
+ fun validateSpaceRelationship(realm: Realm) {
+ 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 {
+ it.flattenParentIds = null
+ it to emptyList().toMutableSet()
+ }
+ .toMap()
+
+ lookupMap.keys.forEach { lookedUp ->
+ if (lookedUp.roomType == RoomType.SPACE) {
+ // get childrens
+
+ lookedUp.children.clearWith { it.deleteFromRealm() }
+
+ RoomChildRelationInfo(realm, lookedUp.roomId).getDirectChildrenDescriptions().forEach { child ->
+
+ lookedUp.children.add(
+ realm.createObject().apply {
+ this.childRoomId = child.roomId
+ this.childSummaryEntity = RoomSummaryEntity.where(realm, child.roomId).findFirst()
+ this.order = child.order
+ this.autoJoin = child.autoJoin
+ this.viaServers.addAll(child.viaServers)
+ }
+ )
+
+ RoomSummaryEntity.where(realm, child.roomId)
+ .process(RoomSummaryEntityFields.MEMBERSHIP_STR, Membership.activeMemberships())
+ .findFirst()
+ ?.let { childSum ->
+ lookupMap.entries.firstOrNull { it.key.roomId == lookedUp.roomId }?.let { entry ->
+ if (entry.value.indexOfFirst { it.roomId == childSum.roomId } == -1) {
+ // add looked up as a parent
+ entry.value.add(childSum)
+ }
+ }
+ }
+ }
+ } else {
+ lookedUp.parents.clearWith { it.deleteFromRealm() }
+ // can we check parent relations here??
+ RoomChildRelationInfo(realm, lookedUp.roomId).getParentDescriptions()
+ .map { parentInfo ->
+
+ lookedUp.parents.add(
+ realm.createObject().apply {
+ this.parentRoomId = parentInfo.roomId
+ this.parentSummaryEntity = RoomSummaryEntity.where(realm, parentInfo.roomId).findFirst()
+ this.canonical = parentInfo.canonical
+ this.viaServers.addAll(parentInfo.viaServers)
+ }
+ )
+
+ RoomSummaryEntity.where(realm, parentInfo.roomId)
+ .process(RoomSummaryEntityFields.MEMBERSHIP_STR, Membership.activeMemberships())
+ .findFirst()
+ ?.let { parentSum ->
+ if (lookupMap[parentSum]?.indexOfFirst { it.roomId == lookedUp.roomId } == -1) {
+ // add lookedup as a parent
+ lookupMap[parentSum]?.add(lookedUp)
+ }
+ }
+ }
+ }
+ }
+
+ // Simple algorithm to break cycles
+ // Need more work to decide how to break, probably need to be as consistent as possible
+ // and also find best way to root the tree
+
+ val graph = Graph()
+ lookupMap
+ // focus only on joined spaces, as room are just leaf
+ .filter { it.key.roomType == RoomType.SPACE && it.key.membership == Membership.JOIN }
+ .forEach { (sum, children) ->
+ graph.getOrCreateNode(sum.roomId)
+ children.forEach {
+ graph.addEdge(it.roomId, sum.roomId)
+ }
+ }
+
+ val backEdges = graph.findBackwardEdges()
+ Timber.v("## SPACES: Cycle detected = ${backEdges.isNotEmpty()}")
+
+ // break cycles
+ backEdges.forEach { edge ->
+ lookupMap.entries.find { it.key.roomId == edge.source.name }?.let {
+ it.value.removeAll { it.roomId == edge.destination.name }
+ }
+ }
+
+ val acyclicGraph = graph.withoutEdges(backEdges)
+// Timber.v("## SPACES: acyclicGraph $acyclicGraph")
+ val flattenSpaceParents = acyclicGraph.flattenDestination().map {
+ it.key.name to it.value.map { it.name }
+ }.toMap()
+// Timber.v("## SPACES: flattenSpaceParents ${flattenSpaceParents.map { it.key.name to it.value.map { it.name } }.joinToString("\n") {
+// it.first + ": [" + it.second.joinToString(",") + "]"
+// }}")
+
+// Timber.v("## SPACES: lookup map ${lookupMap.map { it.key.name to it.value.map { it.name } }.toMap()}")
+
+ lookupMap.entries
+ .filter { it.key.roomType == RoomType.SPACE && it.key.membership == Membership.JOIN }
+ .forEach { entry ->
+ val parent = RoomSummaryEntity.where(realm, entry.key.roomId).findFirst()
+ if (parent != null) {
+// Timber.v("## SPACES: check hierarchy of ${parent.name} id ${parent.roomId}")
+// Timber.v("## SPACES: flat known parents of ${parent.name} are ${flattenSpaceParents[parent.roomId]}")
+ val flattenParentsIds = (flattenSpaceParents[parent.roomId] ?: emptyList()) + listOf(parent.roomId)
+// Timber.v("## SPACES: flatten known parents of children of ${parent.name} are ${flattenParentsIds}")
+ entry.value.forEach { child ->
+ RoomSummaryEntity.where(realm, child.roomId).findFirst()?.let { childSum ->
+
+// Timber.w("## SPACES: ${childSum.name} is ${childSum.roomId} fc: ${childSum.flattenParentIds}")
+// var allParents = childSum.flattenParentIds ?: ""
+ if (childSum.flattenParentIds == null) childSum.flattenParentIds = ""
+ flattenParentsIds.forEach {
+ if (childSum.flattenParentIds?.contains(it) != true) {
+ childSum.flattenParentIds += "|$it"
+ }
+ }
+// childSum.flattenParentIds = "$allParents|"
+
+// Timber.v("## SPACES: flatten of ${childSum.name} is ${childSum.flattenParentIds}")
+ }
+ }
+ }
+ }
+
+ // we need also to filter DMs...
+ // it's more annoying as based on if the other members belong the space or not
+ RoomSummaryEntity.where(realm)
+ .equalTo(RoomSummaryEntityFields.IS_DIRECT, true)
+ .process(RoomSummaryEntityFields.MEMBERSHIP_STR, Membership.activeMemberships())
+ .findAll()
+ .forEach { dmRoom ->
+ val relatedSpaces = lookupMap.keys
+ .filter { it.roomType == RoomType.SPACE }
+ .filter {
+ dmRoom.otherMemberIds.toList().intersect(it.otherMemberIds.toList()).isNotEmpty()
+ }
+ .map { it.roomId }
+ .distinct()
+ val flattenRelated = mutableListOf().apply {
+ addAll(relatedSpaces)
+ relatedSpaces.map { flattenSpaceParents[it] }.forEach {
+ if (it != null) addAll(it)
+ }
+ }.distinct()
+ if (flattenRelated.isEmpty()) {
+ dmRoom.flattenParentIds = null
+ } else {
+ dmRoom.flattenParentIds = "|${flattenRelated.joinToString("|")}|"
+ }
+// Timber.v("## SPACES: flatten of ${dmRoom.otherMemberIds.joinToString(",")} is ${dmRoom.flattenParentIds}")
+ }
+
+ // Maybe a good place to count the number of notifications for spaces?
+
+ realm.where(RoomSummaryEntity::class.java)
+ .process(RoomSummaryEntityFields.MEMBERSHIP_STR, Membership.activeMemberships())
+ .equalTo(RoomSummaryEntityFields.ROOM_TYPE, RoomType.SPACE)
+ .findAll().forEach { space ->
+ // get all children
+ var highlightCount = 0
+ var notificationCount = 0
+ var unreadCount = 0
+ var markedUnreadCount = 0
+ realm.where(RoomSummaryEntity::class.java)
+ .process(RoomSummaryEntityFields.MEMBERSHIP_STR, listOf(Membership.JOIN))
+ .notEqualTo(RoomSummaryEntityFields.ROOM_TYPE, RoomType.SPACE)
+ .contains(RoomSummaryEntityFields.FLATTEN_PARENT_IDS, space.roomId)
+ .findAll().forEach {
+ highlightCount += it.highlightCount
+ notificationCount += it.notificationCount
+ unreadCount += if (it.hasUnreadOriginalContentMessages) 1 else 0
+ markedUnreadCount += if (it.markedUnread) 1 else 0
+ }
+
+ space.highlightCount = highlightCount
+ space.notificationCount = notificationCount + markedUnreadCount
+ space.unreadCount = unreadCount
+ }
+ // xxx invites??
+
+ // LEGACY GROUPS
+ // lets mark rooms that belongs to groups
+ val existingGroups = GroupSummaryEntity.where(realm).findAll()
+
+ // For rooms
+ realm.where(RoomSummaryEntity::class.java)
+ .process(RoomSummaryEntityFields.MEMBERSHIP_STR, Membership.activeMemberships())
+ .equalTo(RoomSummaryEntityFields.IS_DIRECT, false)
+ .findAll().forEach { room ->
+ val belongsTo = existingGroups.filter { it.roomIds.contains(room.roomId) }
+ room.groupIds = if (belongsTo.isEmpty()) {
+ null
+ } else {
+ "|${belongsTo.joinToString("|")}|"
+ }
+ }
+
+ // For DMS
+ realm.where(RoomSummaryEntity::class.java)
+ .process(RoomSummaryEntityFields.MEMBERSHIP_STR, Membership.activeMemberships())
+ .equalTo(RoomSummaryEntityFields.IS_DIRECT, true)
+ .findAll().forEach { room ->
+ val belongsTo = existingGroups.filter {
+ it.userIds.intersect(room.otherMemberIds).isNotEmpty()
+ }
+ room.groupIds = if (belongsTo.isEmpty()) {
+ null
+ } else {
+ "|${belongsTo.joinToString("|")}|"
+ }
+ }
+ }.also {
+ Timber.v("## SPACES: Finish checking room hierarchy in $it ms")
+ }
+ }
+
+// private fun isValidCanonical() : Boolean {
+//
+// }
}
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/TokenChunkEventPersistor.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/TokenChunkEventPersistor.kt
index 1bc3717c05..2c32991ec9 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/TokenChunkEventPersistor.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/TokenChunkEventPersistor.kt
@@ -149,7 +149,7 @@ internal class TokenChunkEventPersistor @Inject constructor(@SessionDatabase pri
}
?: ChunkEntity.create(realm, prevToken, nextToken)
- if (receivedChunk.events.isEmpty() && !receivedChunk.hasMore()) {
+ if (receivedChunk.events.isNullOrEmpty() && !receivedChunk.hasMore()) {
handleReachEnd(realm, roomId, direction, currentChunk)
} else {
handlePagination(realm, roomId, direction, receivedChunk, currentChunk)
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
new file mode 100644
index 0000000000..93cb9d9d34
--- /dev/null
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/space/DefaultSpace.kt
@@ -0,0 +1,119 @@
+/*
+ * 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 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.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.RoomSummary
+import org.matrix.android.sdk.api.session.space.Space
+import org.matrix.android.sdk.api.session.space.model.SpaceChildContent
+import org.matrix.android.sdk.internal.session.room.summary.RoomSummaryDataSource
+
+internal class DefaultSpace(
+ private val room: Room,
+ private val spaceSummaryDataSource: RoomSummaryDataSource
+) : Space {
+
+ override fun asRoom(): Room {
+ return room
+ }
+
+ override val spaceId = room.roomId
+
+ override suspend fun leave(reason: String?) {
+ return room.leave(reason)
+ }
+
+ override fun spaceSummary(): RoomSummary? {
+ return spaceSummaryDataSource.getSpaceSummary(room.roomId)
+ }
+
+ override suspend fun addChildren(roomId: String,
+ viaServers: List,
+ order: String?,
+ autoJoin: Boolean,
+ suggested: Boolean?) {
+ room.sendStateEvent(
+ eventType = EventType.STATE_SPACE_CHILD,
+ stateKey = roomId,
+ body = SpaceChildContent(
+ via = viaServers,
+ autoJoin = autoJoin,
+ order = order,
+ suggested = suggested
+ ).toContent()
+ )
+ }
+
+ override suspend fun removeChildren(roomId: String) {
+ val existing = room.getStateEvents(setOf(EventType.STATE_SPACE_CHILD), QueryStringValue.Equals(roomId))
+ .firstOrNull()
+ ?.content.toModel()
+ ?: // should we throw here?
+ return
+
+ // edit state event and set via to null
+ room.sendStateEvent(
+ eventType = EventType.STATE_SPACE_CHILD,
+ stateKey = roomId,
+ body = SpaceChildContent(
+ order = existing.order,
+ via = null,
+ autoJoin = existing.autoJoin
+ ).toContent()
+ )
+ }
+
+ override suspend fun setChildrenOrder(roomId: String, order: String?) {
+ 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")
+
+ // edit state event and set via to null
+ room.sendStateEvent(
+ eventType = EventType.STATE_SPACE_CHILD,
+ stateKey = roomId,
+ body = SpaceChildContent(
+ order = order,
+ via = existing.via,
+ autoJoin = existing.autoJoin
+ ).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")
+
+ // 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
+ ).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
new file mode 100644
index 0000000000..8fdc563edb
--- /dev/null
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/space/DefaultSpaceService.kt
@@ -0,0 +1,199 @@
+/*
+ * 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 android.net.Uri
+import androidx.lifecycle.LiveData
+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.toContent
+import org.matrix.android.sdk.api.session.events.model.toModel
+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.RoomDirectoryVisibility
+import org.matrix.android.sdk.api.session.room.model.RoomHistoryVisibility
+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
+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.SpaceService
+import org.matrix.android.sdk.api.session.space.SpaceSummaryQueryParams
+import org.matrix.android.sdk.api.session.space.model.SpaceChildContent
+import org.matrix.android.sdk.api.session.space.model.SpaceParentContent
+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.SpaceGetter
+import org.matrix.android.sdk.internal.session.room.create.CreateRoomTask
+import org.matrix.android.sdk.internal.session.room.membership.leaving.LeaveRoomTask
+import org.matrix.android.sdk.internal.session.room.state.StateEventDataSource
+import org.matrix.android.sdk.internal.session.room.summary.RoomSummaryDataSource
+import org.matrix.android.sdk.internal.session.space.peeking.PeekSpaceTask
+import org.matrix.android.sdk.internal.session.space.peeking.SpacePeekResult
+import javax.inject.Inject
+
+internal class DefaultSpaceService @Inject constructor(
+ @UserId private val userId: String,
+ private val createRoomTask: CreateRoomTask,
+ private val joinSpaceTask: JoinSpaceTask,
+ private val spaceGetter: SpaceGetter,
+ private val roomGetter: RoomGetter,
+ private val roomSummaryDataSource: RoomSummaryDataSource,
+ private val stateEventDataSource: StateEventDataSource,
+ private val peekSpaceTask: PeekSpaceTask,
+ private val resolveSpaceInfoTask: ResolveSpaceInfoTask,
+ private val leaveRoomTask: LeaveRoomTask
+) : SpaceService {
+
+ override suspend fun createSpace(params: CreateSpaceParams): String {
+ return createRoomTask.executeRetry(params, 3)
+ }
+
+ override suspend fun createSpace(name: String, topic: String?, avatarUri: Uri?, isPublic: Boolean): String {
+ return createSpace(CreateSpaceParams().apply {
+ this.name = name
+ this.topic = topic
+ this.avatarUri = avatarUri
+ if (isPublic) {
+ this.powerLevelContentOverride = (powerLevelContentOverride ?: PowerLevelsContent()).copy(
+ invite = 0
+ )
+ this.preset = CreateRoomPreset.PRESET_PUBLIC_CHAT
+ this.historyVisibility = RoomHistoryVisibility.WORLD_READABLE
+ this.guestAccess = GuestAccess.CanJoin
+ } else {
+ this.preset = CreateRoomPreset.PRESET_PRIVATE_CHAT
+ visibility = RoomDirectoryVisibility.PRIVATE
+ enableEncryption()
+ }
+ })
+ }
+
+ override fun getSpace(spaceId: String): Space? {
+ return spaceGetter.get(spaceId)
+ }
+
+ override fun getSpaceSummariesLive(queryParams: SpaceSummaryQueryParams): LiveData> {
+ return roomSummaryDataSource.getSpaceSummariesLive(queryParams)
+ }
+
+ override fun getSpaceSummaries(spaceSummaryQueryParams: SpaceSummaryQueryParams): List {
+ return roomSummaryDataSource.getSpaceSummaries(spaceSummaryQueryParams)
+ }
+
+ override fun getRootSpaceSummaries(): List {
+ return roomSummaryDataSource.getRootSpaceSummaries()
+ }
+
+ override suspend fun peekSpace(spaceId: String): SpacePeekResult {
+ return peekSpaceTask.execute(PeekSpaceTask.Params(spaceId))
+ }
+
+ override suspend fun querySpaceChildren(spaceId: String,
+ suggestedOnly: Boolean?,
+ autoJoinedOnly: Boolean?): Pair> {
+ return resolveSpaceInfoTask.execute(ResolveSpaceInfoTask.Params.withId(spaceId, suggestedOnly, autoJoinedOnly)).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 }
+ ?.map { childSummary ->
+ val childStateEv = response.events
+ ?.firstOrNull { it.stateKey == childSummary.roomId && it.type == EventType.STATE_SPACE_CHILD }
+ val childStateEvContent = childStateEv?.content.toModel()
+ 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 ?: emptyList(),
+ activeMemberCount = childSummary.numJoinedMembers,
+ parentRoomId = childStateEv?.roomId
+ )
+ }.orEmpty()
+ )
+ }
+ }
+
+ override suspend fun joinSpace(spaceIdOrAlias: String,
+ reason: String?,
+ viaServers: List): JoinSpaceResult {
+ return joinSpaceTask.execute(JoinSpaceTask.Params(spaceIdOrAlias, reason, viaServers))
+ }
+
+ override suspend fun rejectInvite(spaceId: String, reason: String?) {
+ leaveRoomTask.execute(LeaveRoomTask.Params(spaceId, reason))
+ }
+
+// override fun getSpaceParentsOfRoom(roomId: String): List {
+// return spaceSummaryDataSource.getParentsOfRoom(roomId)
+// }
+
+ override suspend fun setSpaceParent(childRoomId: String, parentSpaceId: String, canonical: Boolean, viaServers: List) {
+ // Should we perform some validation here?,
+ // and if client want to bypass, it could use sendStateEvent directly?
+ if (canonical) {
+ // check that we can send m.child in the parent room
+ if (roomSummaryDataSource.getRoomSummary(parentSpaceId)?.membership != Membership.JOIN) {
+ throw UnsupportedOperationException("Cannot add canonical child if not member of parent")
+ }
+ val powerLevelsEvent = stateEventDataSource.getStateEvent(
+ roomId = parentSpaceId,
+ eventType = EventType.STATE_ROOM_POWER_LEVELS,
+ stateKey = QueryStringValue.NoCondition
+ )
+ val powerLevelsContent = powerLevelsEvent?.content?.toModel()
+ ?: throw UnsupportedOperationException("Cannot add canonical child, missing powerlevel")
+ val powerLevelsHelper = PowerLevelsHelper(powerLevelsContent)
+ if (!powerLevelsHelper.isUserAllowedToSend(userId, true, EventType.STATE_SPACE_CHILD)) {
+ throw UnsupportedOperationException("Cannot add canonical child, not enough power level")
+ }
+ }
+
+ val room = roomGetter.getRoom(childRoomId)
+ ?: throw IllegalArgumentException("Unknown Room $childRoomId")
+
+ room.sendStateEvent(
+ eventType = EventType.STATE_SPACE_PARENT,
+ stateKey = parentSpaceId,
+ body = SpaceParentContent(
+ via = viaServers,
+ canonical = canonical
+ ).toContent()
+ )
+ }
+}
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
new file mode 100644
index 0000000000..5e1b829249
--- /dev/null
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/space/JoinSpaceTask.kt
@@ -0,0 +1,136 @@
+/*
+ * 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 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
+import org.matrix.android.sdk.internal.database.model.RoomSummaryEntityFields
+import org.matrix.android.sdk.internal.di.SessionDatabase
+import org.matrix.android.sdk.internal.session.room.membership.joining.JoinRoomTask
+import org.matrix.android.sdk.internal.session.room.summary.RoomSummaryDataSource
+import org.matrix.android.sdk.internal.task.Task
+import timber.log.Timber
+import java.util.concurrent.TimeUnit
+import javax.inject.Inject
+
+internal interface JoinSpaceTask : Task {
+ data class Params(
+ val roomIdOrAlias: String,
+ val reason: String?,
+ val viaServers: List = emptyList()
+ )
+}
+
+internal class DefaultJoinSpaceTask @Inject constructor(
+ private val joinRoomTask: JoinRoomTask,
+ @SessionDatabase
+ private val realmConfiguration: RealmConfiguration,
+ private val roomSummaryDataSource: RoomSummaryDataSource
+) : JoinSpaceTask {
+
+ override suspend fun execute(params: JoinSpaceTask.Params): JoinSpaceResult {
+ Timber.v("## Space: > Joining root space ${params.roomIdOrAlias} ...")
+ try {
+ joinRoomTask.execute(JoinRoomTask.Params(
+ params.roomIdOrAlias,
+ params.reason,
+ params.viaServers
+ ))
+ } catch (failure: Throwable) {
+ return JoinSpaceResult.Fail(failure)
+ }
+ Timber.v("## Space: < Joining root space done for ${params.roomIdOrAlias}")
+ // we want to wait for sync result to check for auto join rooms
+
+ Timber.v("## Space: > Wait for post joined sync ${params.roomIdOrAlias} ...")
+ try {
+ awaitNotEmptyResult(realmConfiguration, TimeUnit.MINUTES.toMillis(2L)) { realm ->
+ realm.where(RoomSummaryEntity::class.java)
+ .apply {
+ if (params.roomIdOrAlias.startsWith("!")) {
+ equalTo(RoomSummaryEntityFields.ROOM_ID, params.roomIdOrAlias)
+ } else {
+ equalTo(RoomSummaryEntityFields.CANONICAL_ALIAS, params.roomIdOrAlias)
+ }
+ }
+ .equalTo(RoomSummaryEntityFields.MEMBERSHIP_STR, Membership.JOIN.name)
+ }
+ } catch (exception: TimeoutCancellationException) {
+ Timber.w("## Space: > Error created with timeout")
+ return JoinSpaceResult.PartialSuccess(emptyMap())
+ }
+
+ val errors = mutableMapOf()
+ Timber.v("## Space: > Sync done ...")
+ // 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 {
+// 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}")
+ }
+ }
+ }
+ }
+
+ return if (errors.isEmpty()) {
+ JoinSpaceResult.Success
+ } else {
+ JoinSpaceResult.PartialSuccess(errors)
+ }
+ }
+}
+
+// try {
+// awaitNotEmptyResult(realmConfiguration, TimeUnit.MINUTES.toMillis(1L)) { realm ->
+// realm.where(RoomEntity::class.java)
+// .equalTo(RoomEntityFields.ROOM_ID, roomId)
+// }
+// } catch (exception: TimeoutCancellationException) {
+// throw CreateRoomFailure.CreatedWithTimeout
+// }
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
new file mode 100644
index 0000000000..d2be49b70b
--- /dev/null
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/space/ResolveSpaceInfoTask.kt
@@ -0,0 +1,63 @@
+/*
+ * 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 org.matrix.android.sdk.internal.network.GlobalErrorReceiver
+import org.matrix.android.sdk.internal.network.executeRequest
+import org.matrix.android.sdk.internal.task.Task
+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
+ )
+ }
+ }
+}
+
+internal class DefaultResolveSpaceInfoTask @Inject constructor(
+ private val spaceApi: SpaceApi,
+ 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)
+ }
+ }
+}
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
new file mode 100644
index 0000000000..0fcc95fdb3
--- /dev/null
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/space/SpaceApi.kt
@@ -0,0 +1,43 @@
+/*
+ * 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 org.matrix.android.sdk.internal.network.NetworkConstants
+import retrofit2.http.Body
+import retrofit2.http.POST
+import retrofit2.http.Path
+
+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
+ */
+ @POST(NetworkConstants.URI_API_PREFIX_PATH_UNSTABLE + "org.matrix.msc2946/rooms/{roomId}/spaces")
+ suspend fun getSpaces(@Path("roomId") spaceId: String,
+ @Body params: SpaceSummaryParams): 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
new file mode 100644
index 0000000000..5021ff638f
--- /dev/null
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/space/SpaceChildSummaryResponse.kt
@@ -0,0 +1,96 @@
+/*
+ * 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 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.
+ * It can be omitted if there is no room type in which case it should be interpreted as a normal room.
+ */
+ @Json(name = "room_type") val roomType: String? = null,
+
+ /**
+ * Aliases of the room. May be empty.
+ */
+ @Json(name = "aliases")
+ val aliases: List? = null,
+
+ /**
+ * The canonical alias of the room, if any.
+ */
+ @Json(name = "canonical_alias")
+ val canonicalAlias: String? = null,
+
+ /**
+ * The name of the room, if any.
+ */
+ @Json(name = "name")
+ val name: String? = null,
+
+ /**
+ * Required. The number of members joined to the room.
+ */
+ @Json(name = "num_joined_members")
+ val numJoinedMembers: Int = 0,
+
+ /**
+ * Required. The ID of the room.
+ */
+ @Json(name = "room_id")
+ val roomId: String,
+
+ /**
+ * The topic of the room, if any.
+ */
+ @Json(name = "topic")
+ val topic: String? = null,
+
+ /**
+ * Required. Whether the room may be viewed by guest users without joining.
+ */
+ @Json(name = "world_readable")
+ val worldReadable: Boolean = false,
+
+ /**
+ * Required. Whether guest users may join the room and participate in it. If they can,
+ * they will be subject to ordinary power level rules like any other user.
+ */
+ @Json(name = "guest_can_join")
+ val guestCanJoin: Boolean = false,
+
+ /**
+ * The URL for the room's avatar, if one is set.
+ */
+ @Json(name = "avatar_url")
+ val avatarUrl: String? = null,
+
+ /**
+ * Undocumented item
+ */
+ @Json(name = "m.federate")
+ val isFederated: Boolean = false
+)
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/space/SpaceModule.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/space/SpaceModule.kt
new file mode 100644
index 0000000000..87425f4af2
--- /dev/null
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/space/SpaceModule.kt
@@ -0,0 +1,53 @@
+/*
+ * 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 dagger.Binds
+import dagger.Module
+import dagger.Provides
+import org.matrix.android.sdk.internal.session.SessionScope
+import org.matrix.android.sdk.internal.session.room.DefaultSpaceGetter
+import org.matrix.android.sdk.internal.session.room.SpaceGetter
+import org.matrix.android.sdk.internal.session.space.peeking.DefaultPeekSpaceTask
+import org.matrix.android.sdk.internal.session.space.peeking.PeekSpaceTask
+import retrofit2.Retrofit
+
+@Module
+internal abstract class SpaceModule {
+
+ @Module
+ companion object {
+ @Provides
+ @JvmStatic
+ @SessionScope
+ fun providesSpacesAPI(retrofit: Retrofit): SpaceApi {
+ return retrofit.create(SpaceApi::class.java)
+ }
+ }
+
+ @Binds
+ abstract fun bindResolveSpaceTask(task: DefaultResolveSpaceInfoTask): ResolveSpaceInfoTask
+
+ @Binds
+ abstract fun bindPeekSpaceTask(task: DefaultPeekSpaceTask): PeekSpaceTask
+
+ @Binds
+ abstract fun bindJoinSpaceTask(task: DefaultJoinSpaceTask): JoinSpaceTask
+
+ @Binds
+ abstract fun bindSpaceGetter(getter: DefaultSpaceGetter): SpaceGetter
+}
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
new file mode 100644
index 0000000000..013db1c286
--- /dev/null
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/space/SpaceSummaryParams.kt
@@ -0,0 +1,34 @@
+/*
+ * 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
new file mode 100644
index 0000000000..20d63c8814
--- /dev/null
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/space/SpacesResponse.kt
@@ -0,0 +1,31 @@
+/*
+ * 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
+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
+)
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
new file mode 100644
index 0000000000..f6b156a6fb
--- /dev/null
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/space/peeking/PeekSpaceTask.kt
@@ -0,0 +1,143 @@
+/*
+ * 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.peeking
+
+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.RoomType
+import org.matrix.android.sdk.api.session.room.model.create.RoomCreateContent
+import org.matrix.android.sdk.api.session.room.peeking.PeekResult
+import org.matrix.android.sdk.api.session.space.model.SpaceChildContent
+import org.matrix.android.sdk.internal.session.room.peeking.PeekRoomTask
+import org.matrix.android.sdk.internal.session.room.peeking.ResolveRoomStateTask
+import org.matrix.android.sdk.internal.task.Task
+import timber.log.Timber
+import javax.inject.Inject
+
+internal interface PeekSpaceTask : Task {
+ data class Params(
+ val roomIdOrAlias: String,
+ // A depth limit as a simple protection against cycles
+ val maxDepth: Int = 4
+ )
+}
+
+internal class DefaultPeekSpaceTask @Inject constructor(
+ private val peekRoomTask: PeekRoomTask,
+ private val resolveRoomStateTask: ResolveRoomStateTask
+) : PeekSpaceTask {
+
+ override suspend fun execute(params: PeekSpaceTask.Params): SpacePeekResult {
+ val peekResult = peekRoomTask.execute(PeekRoomTask.Params(params.roomIdOrAlias))
+ val roomResult = peekResult as? PeekResult.Success ?: return SpacePeekResult.FailedToResolve(params.roomIdOrAlias, peekResult)
+
+ // check the room type
+ // kind of duplicate cause we already did it in Peek? could we pass on the result??
+ val stateEvents = try {
+ resolveRoomStateTask.execute(ResolveRoomStateTask.Params(roomResult.roomId))
+ } catch (failure: Throwable) {
+ return SpacePeekResult.FailedToResolve(params.roomIdOrAlias, peekResult)
+ }
+ val isSpace = stateEvents
+ .lastOrNull { it.type == EventType.STATE_ROOM_CREATE && it.stateKey == "" }
+ ?.content
+ ?.toModel()
+ ?.type == RoomType.SPACE
+
+ if (!isSpace) return SpacePeekResult.NotSpaceType(params.roomIdOrAlias)
+
+ val children = peekChildren(stateEvents, 0, params.maxDepth)
+
+ return SpacePeekResult.Success(
+ SpacePeekSummary(
+ params.roomIdOrAlias,
+ peekResult,
+ children
+ )
+ )
+ }
+
+ private suspend fun peekChildren(stateEvents: List, depth: Int, maxDepth: Int): List {
+ if (depth >= maxDepth) return emptyList()
+ val childRoomsIds = stateEvents
+ .filter {
+ it.type == EventType.STATE_SPACE_CHILD && !it.stateKey.isNullOrEmpty()
+ // Children where via is not present are ignored.
+ && it.content?.toModel()?.via != null
+ }
+ .map { it.stateKey to it.content?.toModel() }
+
+ Timber.v("## SPACE_PEEK: found ${childRoomsIds.size} present children")
+
+ val spaceChildResults = mutableListOf()
+ childRoomsIds.forEach { entry ->
+
+ Timber.v("## SPACE_PEEK: peeking child $entry")
+ // peek each child
+ val childId = entry.first ?: return@forEach
+ try {
+ val childPeek = peekRoomTask.execute(PeekRoomTask.Params(childId))
+
+ val childStateEvents = resolveRoomStateTask.execute(ResolveRoomStateTask.Params(childId))
+ val createContent = childStateEvents
+ .lastOrNull { it.type == EventType.STATE_ROOM_CREATE && it.stateKey == "" }
+ ?.let { it.content?.toModel() }
+
+ if (!childPeek.isSuccess() || createContent == null) {
+ Timber.v("## SPACE_PEEK: cannot peek child $entry")
+ // can't peek :/
+ spaceChildResults.add(
+ SpaceChildPeekResult(
+ childId, childPeek, entry.second?.autoJoin, entry.second?.order
+ )
+ )
+ // continue to next child
+ return@forEach
+ }
+ val type = createContent.type
+ if (type == RoomType.SPACE) {
+ Timber.v("## SPACE_PEEK: subspace child $entry")
+ spaceChildResults.add(
+ SpaceSubChildPeekResult(
+ childId,
+ childPeek,
+ entry.second?.autoJoin,
+ entry.second?.order,
+ peekChildren(childStateEvents, depth + 1, maxDepth)
+ )
+ )
+ } else
+ /** if (type == RoomType.MESSAGING || type == null)*/
+ {
+ Timber.v("## SPACE_PEEK: room child $entry")
+ spaceChildResults.add(
+ SpaceChildPeekResult(
+ childId, childPeek, entry.second?.autoJoin, entry.second?.order
+ )
+ )
+ }
+
+ // let's check child info
+ } catch (failure: Throwable) {
+ // can this happen?
+ Timber.e(failure, "## Failed to resolve space child")
+ }
+ }
+ return spaceChildResults
+ }
+}
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
new file mode 100644
index 0000000000..1df62e94e8
--- /dev/null
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/space/peeking/SpacePeekResult.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.internal.session.space.peeking
+
+import org.matrix.android.sdk.api.session.room.peeking.PeekResult
+
+// TODO Move to api package
+data class SpacePeekSummary(
+ val idOrAlias: String,
+ val roomPeekResult: PeekResult.Success,
+ val children: List
+)
+
+interface ISpaceChild {
+ val id: String
+ val roomPeekResult: PeekResult
+ val default: Boolean?
+ val order: String?
+}
+
+data class SpaceChildPeekResult(
+ override val id: String,
+ override val roomPeekResult: PeekResult,
+ 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 order: String?,
+ val children: List
+) : ISpaceChild
+
+sealed class SpacePeekResult {
+ abstract class SpacePeekError : SpacePeekResult()
+ data class FailedToResolve(val spaceId: String, val roomPeekResult: PeekResult) : SpacePeekError()
+ data class NotSpaceType(val spaceId: String) : SpacePeekError()
+
+ data class Success(val summary: SpacePeekSummary): SpacePeekResult()
+}
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/ReadReceiptHandler.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/ReadReceiptHandler.kt
index e5d9217db7..fc1a2c3870 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/ReadReceiptHandler.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/ReadReceiptHandler.kt
@@ -143,7 +143,7 @@ internal class ReadReceiptHandler @Inject constructor(
@Suppress("UNCHECKED_CAST")
val content = dataFromFile
.events
- .firstOrNull { it.type == EventType.RECEIPT }
+ ?.firstOrNull { it.type == EventType.RECEIPT }
?.content as? ReadReceiptContent
if (content == null) {
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 bd6693206f..3e88f15573 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
@@ -97,8 +97,14 @@ internal class RoomSyncHandler @Inject constructor(private val readReceiptHandle
handleRoomSync(realm, HandlingStrategy.JOINED(roomsSyncResponse.join), isInitialSync, aggregator, reporter)
handleRoomSync(realm, HandlingStrategy.INVITED(roomsSyncResponse.invite), isInitialSync, aggregator, reporter)
handleRoomSync(realm, HandlingStrategy.LEFT(roomsSyncResponse.leave), isInitialSync, aggregator, reporter)
+
+ // post room sync validation
+// roomSummaryUpdater.validateSpaceRelationship(realm)
}
+ fun postSyncSpaceHierarchyHandle(realm: Realm) {
+ roomSummaryUpdater.validateSpaceRelationship(realm)
+ }
// PRIVATE METHODS *****************************************************************************
private fun handleRoomSync(realm: Realm,
@@ -214,6 +220,7 @@ internal class RoomSyncHandler @Inject constructor(private val readReceiptHandle
val ageLocalTs = event.unsignedData?.age?.let { syncLocalTimestampMillis - it }
val eventEntity = event.toEntity(roomId, SendState.SYNCED, ageLocalTs).copyToRealmOrIgnore(realm, insertType)
CurrentStateEventEntity.getOrCreate(realm, roomId, event.stateKey, event.type).apply {
+ // Timber.v("## Space state event: $eventEntity")
eventId = event.eventId
root = eventEntity
}
@@ -457,7 +464,7 @@ internal class RoomSyncHandler @Inject constructor(private val readReceiptHandle
}
private fun handleRoomAccountDataEvents(realm: Realm, roomId: String, accountData: RoomSyncAccountData) {
- for (event in accountData.events) {
+ accountData.events?.forEach { event ->
val eventType = event.getClearType()
if (eventType == EventType.TAG) {
val content = event.getClearContent().toModel()
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/SyncResponseHandler.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/SyncResponseHandler.kt
index 8e243c3443..157787c8cf 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/SyncResponseHandler.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/SyncResponseHandler.kt
@@ -132,6 +132,11 @@ internal class SyncResponseHandler @Inject constructor(
Timber.v("On sync completed")
cryptoSyncHandler.onSyncCompleted(syncResponse)
+
+ // post sync stuffs
+ monarchy.writeAsync {
+ roomSyncHandler.postSyncSpaceHierarchyHandle(it)
+ }
}
/**
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/job/SyncThread.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/job/SyncThread.kt
index 424c24663c..de8d009892 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/job/SyncThread.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/job/SyncThread.kt
@@ -20,6 +20,7 @@ import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.Observer
import com.squareup.moshi.JsonEncodingException
+import kotlinx.coroutines.CancellationException
import org.matrix.android.sdk.api.failure.Failure
import org.matrix.android.sdk.api.failure.isTokenError
import org.matrix.android.sdk.api.session.sync.SyncState
@@ -199,7 +200,7 @@ internal class SyncThread @Inject constructor(private val syncTask: SyncTask,
if (failure is Failure.NetworkConnection && failure.cause is SocketTimeoutException) {
// Timeout are not critical
Timber.v("Timeout")
- } else if (failure is Failure.Cancelled) {
+ } else if (failure is CancellationException) {
Timber.v("Cancelled")
} else if (failure.isTokenError()) {
// No token or invalid token, stop the thread
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/model/RoomSyncAccountData.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/model/RoomSyncAccountData.kt
index 1c35d812ee..a2375507d8 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/model/RoomSyncAccountData.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/model/RoomSyncAccountData.kt
@@ -25,5 +25,5 @@ internal data class RoomSyncAccountData(
/**
* List of account data events (array of Event).
*/
- @Json(name = "events") val events: List = emptyList()
+ @Json(name = "events") val events: List? = null
)
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/model/RoomSyncEphemeral.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/model/RoomSyncEphemeral.kt
index d59dddb3ea..f2135db6b7 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/model/RoomSyncEphemeral.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/model/RoomSyncEphemeral.kt
@@ -26,5 +26,5 @@ internal data class RoomSyncEphemeral(
/**
* List of ephemeral events (array of Event).
*/
- @Json(name = "events") val events: List = emptyList()
+ @Json(name = "events") val events: List? = null
)
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/model/RoomSyncState.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/model/RoomSyncState.kt
index 5355b7eef1..f86f05d000 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/model/RoomSyncState.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/model/RoomSyncState.kt
@@ -27,5 +27,5 @@ internal data class RoomSyncState(
/**
* List of state events (array of Event). The resulting state corresponds to the *start* of the timeline.
*/
- @Json(name = "events") val events: List = emptyList()
+ @Json(name = "events") val events: List? = null
)
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/model/RoomSyncTimeline.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/model/RoomSyncTimeline.kt
index ddf430099a..27bbc4343f 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/model/RoomSyncTimeline.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/model/RoomSyncTimeline.kt
@@ -27,7 +27,7 @@ internal data class RoomSyncTimeline(
/**
* List of events (array of Event).
*/
- @Json(name = "events") val events: List = emptyList(),
+ @Json(name = "events") val events: List? = null,
/**
* Boolean which tells whether there are more events on the server
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/widgets/DefaultWidgetURLFormatter.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/widgets/DefaultWidgetURLFormatter.kt
index 0937f6d18b..f7664bf3c2 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/widgets/DefaultWidgetURLFormatter.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/widgets/DefaultWidgetURLFormatter.kt
@@ -17,12 +17,13 @@
package org.matrix.android.sdk.internal.session.widgets
import org.matrix.android.sdk.api.MatrixConfiguration
+import org.matrix.android.sdk.api.session.Session
import org.matrix.android.sdk.api.session.integrationmanager.IntegrationManagerConfig
import org.matrix.android.sdk.api.session.integrationmanager.IntegrationManagerService
import org.matrix.android.sdk.api.session.widgets.WidgetURLFormatter
import org.matrix.android.sdk.api.util.appendParamToUrl
import org.matrix.android.sdk.api.util.appendParamsToUrl
-import org.matrix.android.sdk.internal.session.SessionLifecycleObserver
+import org.matrix.android.sdk.api.session.SessionLifecycleObserver
import org.matrix.android.sdk.internal.session.SessionScope
import org.matrix.android.sdk.internal.session.integrationmanager.IntegrationManager
import org.matrix.android.sdk.internal.session.widgets.token.GetScalarTokenTask
@@ -37,12 +38,12 @@ internal class DefaultWidgetURLFormatter @Inject constructor(private val integra
private lateinit var currentConfig: IntegrationManagerConfig
private var whiteListedUrls: List = emptyList()
- override fun onSessionStarted() {
+ override fun onSessionStarted(session: Session) {
setupWithConfiguration()
integrationManager.addListener(this)
}
- override fun onSessionStopped() {
+ override fun onSessionStopped(session: Session) {
integrationManager.removeListener(this)
}
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 3244212487..d741dbc966 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
@@ -22,6 +22,7 @@ import androidx.lifecycle.LifecycleRegistry
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.UserAccountDataEvent
import org.matrix.android.sdk.api.session.accountdata.UserAccountDataTypes
import org.matrix.android.sdk.api.session.events.model.Content
@@ -34,7 +35,7 @@ import org.matrix.android.sdk.api.session.room.powerlevels.PowerLevelsHelper
import org.matrix.android.sdk.api.session.widgets.WidgetManagementFailure
import org.matrix.android.sdk.api.session.widgets.model.Widget
import org.matrix.android.sdk.internal.di.UserId
-import org.matrix.android.sdk.internal.session.SessionLifecycleObserver
+import org.matrix.android.sdk.api.session.SessionLifecycleObserver
import org.matrix.android.sdk.internal.session.SessionScope
import org.matrix.android.sdk.internal.session.integrationmanager.IntegrationManager
import org.matrix.android.sdk.internal.session.room.state.StateEventDataSource
@@ -57,12 +58,12 @@ internal class WidgetManager @Inject constructor(private val integrationManager:
private val lifecycleOwner: LifecycleOwner = LifecycleOwner { lifecycleRegistry }
private val lifecycleRegistry: LifecycleRegistry = LifecycleRegistry(lifecycleOwner)
- override fun onSessionStarted() {
+ override fun onSessionStarted(session: Session) {
lifecycleRegistry.currentState = Lifecycle.State.STARTED
integrationManager.addListener(this)
}
- override fun onSessionStopped() {
+ override fun onSessionStopped(session: Session) {
integrationManager.removeListener(this)
lifecycleRegistry.currentState = Lifecycle.State.DESTROYED
}
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/task/ConfigurableTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/task/ConfigurableTask.kt
index 97f9a0dd51..bc80cf7ee8 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/task/ConfigurableTask.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/task/ConfigurableTask.kt
@@ -37,7 +37,8 @@ internal data class ConfigurableTask(
val id: UUID,
val callbackThread: TaskThread,
val executionThread: TaskThread,
- val callback: MatrixCallback
+ val callback: MatrixCallback,
+ val maxRetryCount: Int = 0
) : Task by task {
@@ -57,7 +58,8 @@ internal data class ConfigurableTask(
id = id,
callbackThread = callbackThread,
executionThread = executionThread,
- callback = callback
+ callback = callback,
+ maxRetryCount = retryCount
)
}
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/task/Task.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/task/Task.kt
index a6c80a0b1a..a5d031e02a 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/task/Task.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/task/Task.kt
@@ -16,7 +16,29 @@
package org.matrix.android.sdk.internal.task
+import kotlinx.coroutines.delay
+import org.matrix.android.sdk.api.failure.Failure
+import org.matrix.android.sdk.api.failure.shouldBeRetried
+import timber.log.Timber
+
internal interface Task {
suspend fun execute(params: PARAMS): RESULT
+
+ suspend fun executeRetry(params: PARAMS, remainingRetry: Int) : RESULT {
+ return try {
+ execute(params)
+ } catch (failure: Throwable) {
+ if (failure.shouldBeRetried() && remainingRetry > 0) {
+ Timber.d(failure, "## TASK: Retriable error")
+ if (failure is Failure.ServerError) {
+ val waitTime = failure.error.retryAfterMillis ?: 0L
+ Timber.d(failure, "## TASK: Quota wait time $waitTime")
+ delay(waitTime + 100)
+ }
+ return executeRetry(params, remainingRetry - 1)
+ }
+ throw failure
+ }
+ }
}
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/task/TaskExecutor.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/task/TaskExecutor.kt
index 478a356432..4da16eff22 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/task/TaskExecutor.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/task/TaskExecutor.kt
@@ -40,9 +40,9 @@ internal class TaskExecutor @Inject constructor(private val coroutineDispatchers
.launch(task.callbackThread.toDispatcher()) {
val resultOrFailure = runCatching {
withContext(task.executionThread.toDispatcher()) {
- Timber.v("Enqueue task $task")
- Timber.v("Execute task $task on ${Thread.currentThread().name}")
- task.execute(task.params)
+ Timber.v("## TASK: Enqueue task $task")
+ Timber.v("## TASK: Execute task $task on ${Thread.currentThread().name}")
+ task.executeRetry(task.params, task.maxRetryCount)
}
}
resultOrFailure
diff --git a/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/util/GraphUtilsTest.kt b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/util/GraphUtilsTest.kt
new file mode 100644
index 0000000000..618f6f4714
--- /dev/null
+++ b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/util/GraphUtilsTest.kt
@@ -0,0 +1,111 @@
+/*
+ * 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.util
+
+import org.junit.FixMethodOrder
+import org.junit.Test
+import org.junit.runners.MethodSorters
+import org.matrix.android.sdk.MatrixTest
+import org.matrix.android.sdk.internal.session.room.summary.Graph
+import kotlin.test.assertEquals
+import kotlin.test.assertNotNull
+import kotlin.test.assertTrue
+
+@FixMethodOrder(MethodSorters.JVM)
+class GraphUtilsTest : MatrixTest {
+
+ @Test
+ fun testCreateGraph() {
+ val graph = Graph()
+
+ graph.addEdge("E", "C")
+ graph.addEdge("B", "A")
+ graph.addEdge("C", "A")
+ graph.addEdge("D", "C")
+ graph.addEdge("E", "D")
+
+ graph.getOrCreateNode("F")
+
+ System.out.println(graph.toString())
+
+ val backEdges = graph.findBackwardEdges(graph.getOrCreateNode("E"))
+
+ assertTrue(backEdges.isEmpty(), "There should not be any cycle in this graphs")
+ }
+
+ @Test
+ fun testCycleGraph() {
+ val graph = Graph()
+
+ graph.addEdge("E", "C")
+ graph.addEdge("B", "A")
+ graph.addEdge("C", "A")
+ graph.addEdge("D", "C")
+ graph.addEdge("E", "D")
+
+ graph.getOrCreateNode("F")
+
+ // adding loops
+ graph.addEdge("C", "E")
+ graph.addEdge("B", "B")
+
+ System.out.println(graph.toString())
+
+ val backEdges = graph.findBackwardEdges(graph.getOrCreateNode("E"))
+ System.out.println(backEdges.joinToString(" | ") { "${it.source.name} -> ${it.destination.name}" })
+
+ assertTrue(backEdges.size == 2, "There should be 2 backward edges not ${backEdges.size}")
+
+ val edge1 = backEdges.find { it.source.name == "C" }
+ assertNotNull(edge1, "There should be a back edge from C")
+ assertEquals("E", edge1.destination.name, "There should be a back edge C -> E")
+
+ val edge2 = backEdges.find { it.source.name == "B" }
+ assertNotNull(edge2, "There should be a back edge from B")
+ assertEquals("B", edge2.destination.name, "There should be a back edge C -> C")
+
+ // clean the graph
+ val acyclicGraph = graph.withoutEdges(backEdges)
+ System.out.println(acyclicGraph.toString())
+
+ assertTrue(acyclicGraph.findBackwardEdges(acyclicGraph.getOrCreateNode("E")).isEmpty(), "There should be no backward edges")
+
+ val flatten = acyclicGraph.flattenDestination()
+
+ assertTrue(flatten[acyclicGraph.getOrCreateNode("A")]!!.isEmpty())
+
+ val flattenParentsB = flatten[acyclicGraph.getOrCreateNode("B")]
+ assertTrue(flattenParentsB!!.size == 1)
+ assertTrue(flattenParentsB.contains(acyclicGraph.getOrCreateNode("A")))
+
+ val flattenParentsE = flatten[acyclicGraph.getOrCreateNode("E")]
+ assertTrue(flattenParentsE!!.size == 3)
+ assertTrue(flattenParentsE.contains(acyclicGraph.getOrCreateNode("A")))
+ assertTrue(flattenParentsE.contains(acyclicGraph.getOrCreateNode("C")))
+ assertTrue(flattenParentsE.contains(acyclicGraph.getOrCreateNode("D")))
+
+// System.out.println(
+// buildString {
+// flatten.entries.forEach {
+// append("${it.key.name}: [")
+// append(it.value.joinToString(",") { it.name })
+// append("]\n")
+// }
+// }
+// )
+ }
+}
diff --git a/multipicker/build.gradle b/multipicker/build.gradle
index 26afd5fb77..5eff2ec3ec 100644
--- a/multipicker/build.gradle
+++ b/multipicker/build.gradle
@@ -43,7 +43,7 @@ android {
dependencies {
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
implementation 'androidx.appcompat:appcompat:1.2.0'
- implementation "androidx.fragment:fragment-ktx:1.3.2"
+ implementation "androidx.fragment:fragment-ktx:1.3.3"
implementation 'androidx.exifinterface:exifinterface:1.3.2'
// Log
diff --git a/tools/check/forbidden_strings_in_code.txt b/tools/check/forbidden_strings_in_code.txt
index 5a53ececec..8f9cc852a7 100644
--- a/tools/check/forbidden_strings_in_code.txt
+++ b/tools/check/forbidden_strings_in_code.txt
@@ -161,7 +161,7 @@ Formatter\.formatShortFileSize===1
# android\.text\.TextUtils
### This is not a rule, but a warning: the number of "enum class" has changed. For Json classes, it is mandatory that they have `@JsonClass(generateAdapter = false)`. If the enum is not used as a Json class, change the value in file forbidden_strings_in_code.txt
-enum class===94
+enum class===99
### Do not import temporary legacy classes
import org.matrix.android.sdk.internal.legacy.riot===3
diff --git a/upstream_fastlane/metadata/android/ca/changelogs/40101020.txt b/upstream_fastlane/metadata/android/ca/changelogs/40101020.txt
new file mode 100644
index 0000000000..43c140214f
--- /dev/null
+++ b/upstream_fastlane/metadata/android/ca/changelogs/40101020.txt
@@ -0,0 +1,2 @@
+Canvis principals d'aquesta versió: millora de rendiment i correcció d'errors!
+Registre de canvis complet: https://github.com/vector-im/element-android/releases/tag/v1.1.2
diff --git a/upstream_fastlane/metadata/android/ca/changelogs/40101030.txt b/upstream_fastlane/metadata/android/ca/changelogs/40101030.txt
new file mode 100644
index 0000000000..9b2627e7f2
--- /dev/null
+++ b/upstream_fastlane/metadata/android/ca/changelogs/40101030.txt
@@ -0,0 +1,2 @@
+Canvis principals d'aquesta versió: millora de rendiment i correcció d'errors!
+Registre de canvis complet: https://github.com/vector-im/element-android/releases/tag/v1.1.3
diff --git a/upstream_fastlane/metadata/android/de/changelogs/40101020.txt b/upstream_fastlane/metadata/android/de/changelogs/40101020.txt
new file mode 100644
index 0000000000..32fabf7c2f
--- /dev/null
+++ b/upstream_fastlane/metadata/android/de/changelogs/40101020.txt
@@ -0,0 +1,2 @@
+Hauptänderungen in dieser Version: Leistungsverbesserung und Fehlerbehebungen!
+Vollständiges Änderungsprotokoll: https://github.com/vector-im/element-android/releases/tag/v1.1.2
diff --git a/upstream_fastlane/metadata/android/de/changelogs/40101030.txt b/upstream_fastlane/metadata/android/de/changelogs/40101030.txt
new file mode 100644
index 0000000000..7e6dc25033
--- /dev/null
+++ b/upstream_fastlane/metadata/android/de/changelogs/40101030.txt
@@ -0,0 +1,2 @@
+Hauptänderungen in dieser Version: Leistungsverbesserung und Fehlerbehebungen!
+Vollständiges Änderungsprotokoll: https://github.com/vector-im/element-android/releases/tag/v1.1.3
diff --git a/upstream_fastlane/metadata/android/en-US/full_description.txt b/upstream_fastlane/metadata/android/en-US/full_description.txt
index e939b75bb7..853885944c 100644
--- a/upstream_fastlane/metadata/android/en-US/full_description.txt
+++ b/upstream_fastlane/metadata/android/en-US/full_description.txt
@@ -1,30 +1,39 @@
-Element is a new type of messenger and collaboration app that:
+Element is both a secure messenger and a productivity team collaboration app that is ideal for group chats while remote working. This chat app uses end-to-end encryption to provide powerful video conferencing, file sharing and voice calls.
-1. Puts you in control to preserve your privacy
-2. Lets you communicate with anyone in the Matrix network, and even beyond by integrating with apps such as Slack
-3. Protects you from advertising, datamining and walled gardens
-4. Secures you through end-to-end encryption, with cross-signing to verify others
+Element’s features include:
+- Advanced online communication tools
+- Fully encrypted messages to allow safer corporate communication, even for remote workers
+- Decentralized chat based on the Matrix open source framework
+- File sharing securely with encrypted data while managing projects
+- Video chats with Voice over IP and screen sharing
+- Easy integration with your favourite online collaboration tools, project management tools, VoIP services and other team messaging apps
-Element is completely different from other messaging and collaboration apps because it is decentralised and open source.
+Element is completely different from other messaging and collaboration apps. It operates on Matrix, an open network for secure messaging and decentralized communication. It allows self-hosting to give users maximum ownership and control of their data and messages.
-Element lets you self-host - or choose a host - so that you have privacy, ownership and control of your data and conversations. It gives you access to an open network; so you’re not just stuck speaking to other Element users only. And it is very secure.
+Privacy and encrypted messaging
+Element protects you from unwanted ads, data mining and walled gardens. It also secures all your data, one-to-one video and voice communication through end-to-end encryption and cross-signed device verification.
-Element is able to do all this because it operates on Matrix - the standard for open, decentralised communication.
+Element gives you control over your privacy while allowing you to communicate securely with anyone on the Matrix network, or other business collaboration tools by integrating with apps such as Slack.
-Element puts you in control by letting you choose who hosts your conversations. From the Element app, you can choose to host in different ways:
+Element can be self-hosted
+To allow more control of your sensitive data and conversations, Element can be self-hosted or you can choose any Matrix-based host - the standard for open source, decentralized communication. Element gives you privacy, security compliance and integration flexibility.
+Own your data
+You decide where to keep your data and messages. Without the risk of data mining or access from third parties.
+
+Element puts you in control in different ways:
1. Get a free account on the matrix.org public server hosted by the Matrix developers, or choose from thousands of public servers hosted by volunteers
-2. Self-host your account by running a server on your own hardware
+2. Self-host your account by running a server on your own IT infrastructure
3. Sign up for an account on a custom server by simply subscribing to the Element Matrix Services hosting platform
-Why choose Element?
+Open messaging and collaboration
+You can chat with anyone on the Matrix network, whether they’re using Element, another Matrix app or even if they are using a different messaging app.
-OWN YOUR DATA: You decide where to keep your data and messages. You own it and control it, not some MEGACORP that mines your data or gives access to third parties.
+Super secure
+Real end-to-end encryption (only those in the conversation can decrypt messages), and cross-signed device verification.
-OPEN MESSAGING AND COLLABORATION: You can chat with anyone else in the Matrix network, whether they’re using Element or another Matrix app, and even if they are using a different messaging system of the likes of Slack, IRC or XMPP.
+Complete communication and integration
+Messaging, voice and video calls, file sharing, screen sharing and a whole bunch of integrations, bots and widgets. Build rooms, communities, stay in touch and get things done.
-SUPER-SECURE: Real end-to-end encryption (only those in the conversation can decrypt messages), and cross-signing to verify the devices of conversation participants.
-
-COMPLETE COMMUNICATION: Messaging, voice and video calls, file sharing, screen sharing and a whole bunch of integrations, bots and widgets. Build rooms, communities, stay in touch and get things done.
-
-EVERYWHERE YOU ARE: Stay in touch wherever you are with fully synchronised message history across all your devices and on the web at https://app.element.io.
+Pick up where you left off
+Stay in touch wherever you are with fully synchronised message history across all your devices and on the web at https://app.element.io
\ No newline at end of file
diff --git a/upstream_fastlane/metadata/android/en-US/short_description.txt b/upstream_fastlane/metadata/android/en-US/short_description.txt
index 023b366c9a..5a98f6f772 100644
--- a/upstream_fastlane/metadata/android/en-US/short_description.txt
+++ b/upstream_fastlane/metadata/android/en-US/short_description.txt
@@ -1 +1 @@
-Secure decentralised chat & VoIP. Keep your data safe from third parties.
\ No newline at end of file
+Group messenger - encrypted messaging, group chat and video calls
\ No newline at end of file
diff --git a/upstream_fastlane/metadata/android/en-US/title.txt b/upstream_fastlane/metadata/android/en-US/title.txt
index 039da1fc3b..12fa89b99b 100644
--- a/upstream_fastlane/metadata/android/en-US/title.txt
+++ b/upstream_fastlane/metadata/android/en-US/title.txt
@@ -1 +1 @@
-Element (previously Riot.im)
\ No newline at end of file
+Element - Secure Messenger
\ No newline at end of file
diff --git a/upstream_fastlane/metadata/android/et/changelogs/40101020.txt b/upstream_fastlane/metadata/android/et/changelogs/40101020.txt
new file mode 100644
index 0000000000..5f34bb579f
--- /dev/null
+++ b/upstream_fastlane/metadata/android/et/changelogs/40101020.txt
@@ -0,0 +1,2 @@
+Põhilised muutused selles versioonis: jõudluse parandused ja pisikohendused.
+Kogu ingliskeelne muudatuste logi: https://github.com/vector-im/element-android/releases/tag/v1.1.2
diff --git a/upstream_fastlane/metadata/android/et/changelogs/40101030.txt b/upstream_fastlane/metadata/android/et/changelogs/40101030.txt
new file mode 100644
index 0000000000..5a558d911a
--- /dev/null
+++ b/upstream_fastlane/metadata/android/et/changelogs/40101030.txt
@@ -0,0 +1,2 @@
+Põhilised muutused selles versioonis: jõudluse parandused ja pisikohendused.
+Kogu ingliskeelne muudatuste logi: https://github.com/vector-im/element-android/releases/tag/v1.1.3
diff --git a/upstream_fastlane/metadata/android/fr/changelogs/40101020.txt b/upstream_fastlane/metadata/android/fr/changelogs/40101020.txt
new file mode 100644
index 0000000000..7bce8ac19a
--- /dev/null
+++ b/upstream_fastlane/metadata/android/fr/changelogs/40101020.txt
@@ -0,0 +1,2 @@
+Principaux changements pour cette version : amélioration des performances et corrections de bugs !
+Intégralité des changements : https://github.com/vector-im/element-android/releases/tag/v1.1.2
diff --git a/upstream_fastlane/metadata/android/fr/changelogs/40101030.txt b/upstream_fastlane/metadata/android/fr/changelogs/40101030.txt
new file mode 100644
index 0000000000..93f0b9e7fb
--- /dev/null
+++ b/upstream_fastlane/metadata/android/fr/changelogs/40101030.txt
@@ -0,0 +1,2 @@
+Principaux changements pour cette version : amélioration des performances et corrections de bugs !
+Intégralité des changements : https://github.com/vector-im/element-android/releases/tag/v1.1.3
diff --git a/upstream_fastlane/metadata/android/fr/full_description.txt b/upstream_fastlane/metadata/android/fr/full_description.txt
index 2b17d8f846..066b94868b 100644
--- a/upstream_fastlane/metadata/android/fr/full_description.txt
+++ b/upstream_fastlane/metadata/android/fr/full_description.txt
@@ -1,30 +1,30 @@
-Element est une nouvelle application de messagerie et de collaboration qui :
+Element est une nouvelle application de messagerie et de collaboration qui :
-1) Vous place aux commandes de votre vie privée
-2) Vous permet de communiquer avec n'importe qui du réseau Matrix, et plus encore par des intégrations d'autres applications comme Slack ou Discord
-3) Vous protège de la publicité et de la collecte de données
-4) Vous sécurise grâce à du chiffrement bout-à-bout, avec de la signature croisée pour authentifier les autres utilisateurs
+1. Vous permet de préserver votre vie privée
+2. Vous permet de communiquer avec n’importe qui sur réseau Matrix, et plus encore grâce aux intégrations d’autres applications telles que Slack ou Discord
+3. Vous protège de la publicité et de la collecte de données
+4. Vous protège grâce au chiffrement de bout-à-bout et à la signature croisée pour authentifier les autres utilisateurs
-Element est complètement différent des autres applications de messagerie et de collaboration puisque l'application est décentralisée et open-source.
+Element est complètement différente des autres applications de messagerie et de collaboration puisque l’application est décentralisée et open-source.
-Element vous permet d'héberger vous-même -ou de choisir un hôte- vous permettant d'assurer votre vie privée, la propriété et le contrôle de vos données et de vos conversations. Cela vous offre l'accès à un réseau ouvert, vous n'êtes donc pas condamné à parler à d'autres utilisateurs d'Element seulement. Et c'est très sécurisé.
+Element vous permet d’héberger vous-même ou de choisir un hôte vous permettant d’assurer votre vie privée, la propriété et le contrôle de vos données et de vos conversations. Cela vous donne accès à un réseau ouvert. Vous n’êtes donc pas condamné à parler à d’autres utilisateurs d’Element seulement. Et c'est très sécurisé.
-Element peut faire tout ça car il est basé sur Matrix, le protocole standard pour la communication ouverte et décentralisée.
+Element peut faire tout ça car elle est basée sur Matrix, le protocole standard pour la communication ouverte et décentralisée.
-Element vous donne le contrôle en vous laissant choisir qui héberge vos conversations. Depuis l'application Element, vous pouvez choisir votre hôte de différentes manières :
+Element vous donne le contrôle en vous laissant choisir qui héberge vos conversations. Depuis l'application Element, vous pouvez choisir votre hôte de différentes manières :
-1) Créer un compte gratuit sur le serveur public matrix.org hébergé par les développeurs de Matrix, ou choisir parler les milliers de serveurs public hébergés par des bénévoles
-2) Héberger vous-même votre compte en installant un serveur sur votre propre machine
-3) Créer un compte sur un serveur personnalisé en souscrivant sur la plateforme d'hébergement « Element Matrix Services » (EMS)
+1. Créer un compte gratuit sur le serveur public matrix.org hébergé par les développeurs de Matrix, ou choisir parmi les milliers de serveurs public hébergés par des bénévoles
+2. Héberger vous-même votre compte en installant un serveur sur votre propre machine
+3. Créer un compte sur un serveur personnalisé en souscrivant sur la plateforme d'hébergement « Element Matrix Services » (EMS)
Pourquoi choisir Element ?
-POSSÉDEZ VOS DONNÉES : Vous décidez où conserver vos données et vos messages. Vous les possédez et vous les contrôlez, et non une MEGACORP qui mine vos données ou les donnent à des tiers
+VOS DONNÉES VOUS APPARTIENNENT : vous décidez où stocker vos données et messages. Ils vous appartiennent et vous les maîtrisez. Aucune multinationale ne viendra extraire vos données pour les envoyer au plus offrant.
-UNE MESSAGERIE OUVERTE ET COLLABORATIVE : Vous pouvez discuter avec n'importe qui sur le réseau Matrix, qu'ils utilisent Element ou une autre application basée sur Matrix, et même s'ils utilisent un système de messagerie différent comment Slack, Discord, IRC ou XMPP.
+MESSAGERIE ET COLLABORATION OUVERTES : vous pouvez discuter avec tout le réseau Matrix, qu’ils utilisent Element ou une autre application Matrix, même s’ils utilisent une autre plateforme de messagerie telle que Slack, IRC ou XMPP.
-SUPER SÉCURISÉ : Un réel chiffrement bout-à-bout (seulement ceux deux la conversation peuvent déchiffrer les messages), et une signature croisée pour vérifier les appareils des participants de la conversation.
+ULTRA SÉCURISÉ : chiffrement de bout en bout (seuls les membres d’une conversation peuvent déchiffrer les messages), et signature croisée pour vérifier les appareils de vos interlocuteurs.
-COMMUNICATION COMPLÈTE : Messagerie, appels vocaux et vidéo, transfert de fichiers, partage d'écran et un tas d'intégrations, robots et widgets. Construisez des salons, des communautés, restez en contact et accomplissez de grandes choses.
+TOUTES VOS COMMUNICATIONS : messagerie, appels audio et vidéo, partage de fichier, partage d’écran et un grand nombre d’intégrations, robots et widgets. Participez à des salons, des communautés, restez en contact et faites avancer vos projets.
-PARTOUT OÙ VOUS ÊTES : Restez connectés peu import où vous êtes avec la synchronisation complète de l'historique des messages sur tous vos appareils et sur le web sur https://app.element.io.
+PARTOUT AVEC VOUS : votre historique reste synchronisé entre tous vos appareils et sur le web sur https://element.io/app.
diff --git a/upstream_fastlane/metadata/android/it/changelogs/40101020.txt b/upstream_fastlane/metadata/android/it/changelogs/40101020.txt
new file mode 100644
index 0000000000..21057629e3
--- /dev/null
+++ b/upstream_fastlane/metadata/android/it/changelogs/40101020.txt
@@ -0,0 +1,2 @@
+Modifiche principali in questa versione: prestazioni migliorate e correzione di errori!
+Cronologia completa: https://github.com/vector-im/element-android/releases/tag/v1.1.2
diff --git a/upstream_fastlane/metadata/android/it/changelogs/40101030.txt b/upstream_fastlane/metadata/android/it/changelogs/40101030.txt
new file mode 100644
index 0000000000..a62c4a0736
--- /dev/null
+++ b/upstream_fastlane/metadata/android/it/changelogs/40101030.txt
@@ -0,0 +1,2 @@
+Modifiche principali in questa versione: prestazioni migliorate e correzioni di errori!
+Cronologia completa: https://github.com/vector-im/element-android/releases/tag/v1.1.3
diff --git a/upstream_fastlane/metadata/android/ja/changelogs/40100100.txt b/upstream_fastlane/metadata/android/ja/changelogs/40100100.txt
new file mode 100644
index 0000000000..8359a12964
--- /dev/null
+++ b/upstream_fastlane/metadata/android/ja/changelogs/40100100.txt
@@ -0,0 +1,2 @@
+今回の新バージョンでは、主にバグの修正と改善が行われています。メッセージの送信がより速くなりました。
+すべての変更履歴はこちら: https://github.com/vector-im/element-android/releases/tag/v1.0.10
diff --git a/upstream_fastlane/metadata/android/ja/changelogs/40100110.txt b/upstream_fastlane/metadata/android/ja/changelogs/40100110.txt
new file mode 100644
index 0000000000..c93db421af
--- /dev/null
+++ b/upstream_fastlane/metadata/android/ja/changelogs/40100110.txt
@@ -0,0 +1,2 @@
+今回の新バージョンでは、主にUI(ユーザーインターフェース)とUX(ユーザーエクスペリエンス)の向上が図られています。友達を招待したり、QRコードを読み取って素早くDMを作成できるようになりました。
+すべての変更履歴はこちら: https://github.com/vector-im/element-android/releases/tag/v1.0.11
diff --git a/upstream_fastlane/metadata/android/ja/changelogs/40100120.txt b/upstream_fastlane/metadata/android/ja/changelogs/40100120.txt
new file mode 100644
index 0000000000..aace2ef79f
--- /dev/null
+++ b/upstream_fastlane/metadata/android/ja/changelogs/40100120.txt
@@ -0,0 +1,2 @@
+このバージョンの主な変更点: URLプレビュー、新しい絵文字、新しいルーム設定機能、それにクリスマスには雪が!
+すべての変更履歴はこちら: https://github.com/vector-im/element-android/releases/tag/v1.0.12
diff --git a/upstream_fastlane/metadata/android/ja/changelogs/40100130.txt b/upstream_fastlane/metadata/android/ja/changelogs/40100130.txt
new file mode 100644
index 0000000000..97633621c5
--- /dev/null
+++ b/upstream_fastlane/metadata/android/ja/changelogs/40100130.txt
@@ -0,0 +1,2 @@
+このバージョンの主な変更点: URLプレビュー、新しい絵文字、新しいルーム設定機能、それにクリスマスには雪が!
+すべての変更履歴はこちら: https://github.com/vector-im/element-android/releases/tag/v1.0.13
diff --git a/upstream_fastlane/metadata/android/ja/changelogs/40100140.txt b/upstream_fastlane/metadata/android/ja/changelogs/40100140.txt
new file mode 100644
index 0000000000..c340663127
--- /dev/null
+++ b/upstream_fastlane/metadata/android/ja/changelogs/40100140.txt
@@ -0,0 +1,2 @@
+このバージョンの主な変更点: 部屋の許可、自動のテーマ切替、そして多くのバグを修正しました。
+すべての変更履歴はこちら: https://github.com/vector-im/element-android/releases/tag/v1.0.14
diff --git a/upstream_fastlane/metadata/android/ja/changelogs/40100150.txt b/upstream_fastlane/metadata/android/ja/changelogs/40100150.txt
new file mode 100644
index 0000000000..42f28c7bea
--- /dev/null
+++ b/upstream_fastlane/metadata/android/ja/changelogs/40100150.txt
@@ -0,0 +1,2 @@
+このバージョンの主な変更点: ソーシャルログインに対応しました。
+すべての変更履歴はこちら: https://github.com/vector-im/element-android/releases/tag/v1.0.15
diff --git a/upstream_fastlane/metadata/android/ja/changelogs/40100160.txt b/upstream_fastlane/metadata/android/ja/changelogs/40100160.txt
new file mode 100644
index 0000000000..8b5196998a
--- /dev/null
+++ b/upstream_fastlane/metadata/android/ja/changelogs/40100160.txt
@@ -0,0 +1,2 @@
+このバージョンの主な変更点: パフォーマンスの向上とバグの修正!
+すべての変更履歴はこちら: https://github.com/vector-im/element-android/releases/tag/v1.0.15 and https://github.com/vector-im/element-android/releases/tag/v1.0.16
diff --git a/upstream_fastlane/metadata/android/ja/changelogs/40100170.txt b/upstream_fastlane/metadata/android/ja/changelogs/40100170.txt
new file mode 100644
index 0000000000..586b01cb2b
--- /dev/null
+++ b/upstream_fastlane/metadata/android/ja/changelogs/40100170.txt
@@ -0,0 +1,2 @@
+このバージョンの主な変更点: バグの修正!
+すべての変更履歴はこちら: https://github.com/vector-im/element-android/releases/tag/v1.0.17
diff --git a/upstream_fastlane/metadata/android/ja/changelogs/40101000.txt b/upstream_fastlane/metadata/android/ja/changelogs/40101000.txt
new file mode 100644
index 0000000000..25bbd7ab87
--- /dev/null
+++ b/upstream_fastlane/metadata/android/ja/changelogs/40101000.txt
@@ -0,0 +1,2 @@
+このバージョンの主な変更点: パフォーマンスの向上とバグの修正!
+すべての変更履歴はこちら: https://github.com/vector-im/element-android/releases/tag/v1.1.0
diff --git a/upstream_fastlane/metadata/android/ja/changelogs/40101010.txt b/upstream_fastlane/metadata/android/ja/changelogs/40101010.txt
new file mode 100644
index 0000000000..35ba933069
--- /dev/null
+++ b/upstream_fastlane/metadata/android/ja/changelogs/40101010.txt
@@ -0,0 +1,2 @@
+このバージョンの主な変更点: パフォーマンスの向上とバグの修正!
+すべての変更履歴はこちら: https://github.com/vector-im/element-android/releases/tag/v1.1.1
diff --git a/upstream_fastlane/metadata/android/ja/changelogs/40101020.txt b/upstream_fastlane/metadata/android/ja/changelogs/40101020.txt
new file mode 100644
index 0000000000..88e3c79ca8
--- /dev/null
+++ b/upstream_fastlane/metadata/android/ja/changelogs/40101020.txt
@@ -0,0 +1,2 @@
+このバージョンの主な変更点: パフォーマンスの向上とバグの修正!
+すべての変更履歴はこちら: https://github.com/vector-im/element-android/releases/tag/v1.1.2
diff --git a/upstream_fastlane/metadata/android/ja/changelogs/40101030.txt b/upstream_fastlane/metadata/android/ja/changelogs/40101030.txt
new file mode 100644
index 0000000000..87d191b226
--- /dev/null
+++ b/upstream_fastlane/metadata/android/ja/changelogs/40101030.txt
@@ -0,0 +1,2 @@
+このバージョンの主な変更点: パフォーマンスの向上とバグの修正!
+すべての変更履歴はこちら: https://github.com/vector-im/element-android/releases/tag/v1.1.3
diff --git a/upstream_fastlane/metadata/android/ja/full_description.txt b/upstream_fastlane/metadata/android/ja/full_description.txt
new file mode 100644
index 0000000000..855eb309c9
--- /dev/null
+++ b/upstream_fastlane/metadata/android/ja/full_description.txt
@@ -0,0 +1,30 @@
+Elementはまったく新しいタイプのメッセンジャーアプリです。
+
+1. あなた自身がプライバシーをコントロールすることを可能にします。
+2. Matrixネットワークにいる誰とでも通信できることはもちろん、Slackなどのアプリとの連携によって他のネットワークとも通信ができます。
+3. 広告、データ収集、バックドア、ユーザーの囲い込みから逃れることができます。
+4. エンドツーエンド暗号化とクロス署名によってあなたを保護します。
+
+Elementは非中央集権型でオープンソースであるため、他のメッセンジャーアプリとは完全に異なっています。
+
+Elementはあなた自身でサーバーをホストすることも、サーバーを選ぶこともできます。これによってあなたのデータと会話に関するプライバシーや所有権はあなた自身で管理できるようになります。さらに、あなたは他のElementユーザーと話せるだけでなくオープンネットワークへのアクセスも可能です。とてもセキュアです。
+
+Elementは、オープンな分散型通信の標準規格であるMatrixで動作するため、これらすべてを実現することができています。
+
+Elementではあなたの会話をどのサーバーでホストするか決めることができます。アプリでは、さまざまな方法で選択できます。
+
+1. matrix.orgの公開サーバーで無料のアカウントを取得します。
+2. あなた自身のハードウェアでサーバーを動かし、アカウントを管理します。
+3. Element Matrix Servicesのホスティングプラットフォームに登録することで、カスタムサーバー上のアカウントを取得できます。
+
+なぜElementを選ぶべきなのか?
+
+データの所有権: 自分でデータやメッセージを保管する場所を決めることができます。あなたが所有権を持ってコントロールすることで、第三者にあなたのデータを渡したり、ビッグデータを収集する巨大テック企業に依存する必要がなくなります。
+
+開かれたネットワークと共同作業: Matrixネットワーク内の他の誰とでも、あるいはElementや他のMatrixアプリを使っているかどうかに関わらず、またSlack、IRC、XMPPのような他のメッセージングシステムを使っているかどうかに関わらず、チャットすることができます。
+
+はるかに安全: 本物のエンドツーエンド暗号化(会話に参加している者のみがメッセージを読める)と会話参加者の真正性を確認するためクロス署名によって。
+
+完全なるコミュニケーションの訪れ: テキスト、音声通話、ビデオ通話、ファイル共有、画面共有、連携機能、ボット、ウィジェットなどのコミュニケーションに必要な機能の全てが実装されています。ルームやコミュニティを立ち上げて連絡を取り合い、物事をスムーズに成し遂げることができます。
+
+いつでもどこでも!: すべてのデバイスとウェブ(https://app.element.io)でメッセージの履歴が完全に同期されるため、どこにいても連絡を取ることができます。
diff --git a/upstream_fastlane/metadata/android/ja/short_description.txt b/upstream_fastlane/metadata/android/ja/short_description.txt
new file mode 100644
index 0000000000..c3991b7a93
--- /dev/null
+++ b/upstream_fastlane/metadata/android/ja/short_description.txt
@@ -0,0 +1 @@
+安全な分散型チャットとVoIP。あなたの情報が第三者から守られます。
diff --git a/upstream_fastlane/metadata/android/ja/title.txt b/upstream_fastlane/metadata/android/ja/title.txt
new file mode 100644
index 0000000000..376f4a95de
--- /dev/null
+++ b/upstream_fastlane/metadata/android/ja/title.txt
@@ -0,0 +1 @@
+Element(エレメントメッセンジャー)
diff --git a/upstream_fastlane/metadata/android/nb-NO/changelogs/40100120.txt b/upstream_fastlane/metadata/android/nb-NO/changelogs/40100120.txt
new file mode 100644
index 0000000000..163cd64cdc
--- /dev/null
+++ b/upstream_fastlane/metadata/android/nb-NO/changelogs/40100120.txt
@@ -0,0 +1,2 @@
+Hovedendringene i denne versjonen: URL-forhåndsvisning, nytt Emoji-tastatur, nye rominnstillingsmuligheter og snø til jul!
+Full endringslogg: https://github.com/vector-im/element-android/releases/tag/v1.0.12
diff --git a/upstream_fastlane/metadata/android/nb-NO/changelogs/40100130.txt b/upstream_fastlane/metadata/android/nb-NO/changelogs/40100130.txt
new file mode 100644
index 0000000000..23ab42ef2c
--- /dev/null
+++ b/upstream_fastlane/metadata/android/nb-NO/changelogs/40100130.txt
@@ -0,0 +1,2 @@
+Hovedendringene i denne versjonen: URL-forhåndsvisning, nytt Emoji-tastatur, nye rominnstillingsmuligheter og snø til jul!
+Full endringslogg: https://github.com/vector-im/element-android/releases/tag/v1.0.13
diff --git a/upstream_fastlane/metadata/android/nb-NO/changelogs/40100140.txt b/upstream_fastlane/metadata/android/nb-NO/changelogs/40100140.txt
new file mode 100644
index 0000000000..10a3d9b925
--- /dev/null
+++ b/upstream_fastlane/metadata/android/nb-NO/changelogs/40100140.txt
@@ -0,0 +1,2 @@
+Hovedendringene i denne versjonen: Rediger romtillatelser, automatisk lys/mørkt tema og en haug med feilrettinger.
+Full endringslogg: https://github.com/vector-im/element-android/releases/tag/v1.0.14
diff --git a/upstream_fastlane/metadata/android/nb-NO/changelogs/40100150.txt b/upstream_fastlane/metadata/android/nb-NO/changelogs/40100150.txt
new file mode 100644
index 0000000000..3237da115d
--- /dev/null
+++ b/upstream_fastlane/metadata/android/nb-NO/changelogs/40100150.txt
@@ -0,0 +1,2 @@
+Hovedendringene i denne versjonen: Sosial innloggingsstøtte.
+Full endringslogg: https://github.com/vector-im/element-android/releases/tag/v1.0.15
diff --git a/upstream_fastlane/metadata/android/nb-NO/changelogs/40100160.txt b/upstream_fastlane/metadata/android/nb-NO/changelogs/40100160.txt
new file mode 100644
index 0000000000..5502fd3ab1
--- /dev/null
+++ b/upstream_fastlane/metadata/android/nb-NO/changelogs/40100160.txt
@@ -0,0 +1,2 @@
+Hovedendringene i denne versjonen: Sosial innloggingsstøtte.
+Full endringslogg: https://github.com/vector-im/element-android/releases/tag/v1.0.15 og https://github.com/vector-im/element-android/releases/tag/v1.0.16
diff --git a/upstream_fastlane/metadata/android/nb-NO/changelogs/40100170.txt b/upstream_fastlane/metadata/android/nb-NO/changelogs/40100170.txt
new file mode 100644
index 0000000000..f9174a2ee4
--- /dev/null
+++ b/upstream_fastlane/metadata/android/nb-NO/changelogs/40100170.txt
@@ -0,0 +1,2 @@
+Hovedendringene i denne versjonen: Feilrettinger!
+Full endringslogg: https://github.com/vector-im/element-android/releases/tag/v1.0.17
diff --git a/upstream_fastlane/metadata/android/nb-NO/changelogs/40101000.txt b/upstream_fastlane/metadata/android/nb-NO/changelogs/40101000.txt
new file mode 100644
index 0000000000..370dbb36ce
--- /dev/null
+++ b/upstream_fastlane/metadata/android/nb-NO/changelogs/40101000.txt
@@ -0,0 +1,2 @@
+Hovedendringene i denne versjonen: forbedring av VoIP (lyd og videosamtaler i DM) og feilrettinger!
+Full endringslogg: https://github.com/vector-im/element-android/releases/tag/v1.1.0
diff --git a/upstream_fastlane/metadata/android/nb-NO/changelogs/40101010.txt b/upstream_fastlane/metadata/android/nb-NO/changelogs/40101010.txt
new file mode 100644
index 0000000000..c6109b8d9b
--- /dev/null
+++ b/upstream_fastlane/metadata/android/nb-NO/changelogs/40101010.txt
@@ -0,0 +1,2 @@
+Hovedendringene i denne versjonen: forbedring av ytelsen og feilrettinger!
+Full endringslogg: https://github.com/vector-im/element-android/releases/tag/v1.1.1
diff --git a/upstream_fastlane/metadata/android/nb-NO/changelogs/40101020.txt b/upstream_fastlane/metadata/android/nb-NO/changelogs/40101020.txt
new file mode 100644
index 0000000000..9464c6fb0f
--- /dev/null
+++ b/upstream_fastlane/metadata/android/nb-NO/changelogs/40101020.txt
@@ -0,0 +1,2 @@
+Hovedendringene i denne versjonen: forbedring av ytelsen og feilrettinger!
+Full endringslogg: https://github.com/vector-im/element-android/releases/tag/v1.1.2
diff --git a/upstream_fastlane/metadata/android/nb-NO/changelogs/40101030.txt b/upstream_fastlane/metadata/android/nb-NO/changelogs/40101030.txt
new file mode 100644
index 0000000000..1e12246e9a
--- /dev/null
+++ b/upstream_fastlane/metadata/android/nb-NO/changelogs/40101030.txt
@@ -0,0 +1,2 @@
+Hovedendringene i denne versjonen: forbedring av ytelsen og feilrettinger!
+Full endringslogg: https://github.com/vector-im/element-android/releases/tag/v1.1.3
diff --git a/upstream_fastlane/metadata/android/nb-NO/full_description.txt b/upstream_fastlane/metadata/android/nb-NO/full_description.txt
new file mode 100644
index 0000000000..92a3c4c5c3
--- /dev/null
+++ b/upstream_fastlane/metadata/android/nb-NO/full_description.txt
@@ -0,0 +1,30 @@
+Element er en ny type messenger og samarbeidsapp som:
+
+1. Gir deg kontrollen for å bevare personvernet ditt
+2. Lar deg kommunisere med hvem som helst i Matrix-nettverket, og til og med ved å integrere med apper som Slack
+3. Beskytter deg mot reklame, datamining og inngjerdede hager
+4. Sikrer deg gjennom end-to-end-kryptering, med kryssignering for å bekrefte andre
+
+Element er helt forskjellig fra andre meldings- og samarbeidsapper fordi det er desentralisert og åpen kildekode.
+
+Element lar deg selv være vert - eller velge en vert - slik at du har personvern, eierskap og kontroll over dataene og samtalene dine. Det gir deg tilgang til et åpent nettverk; slik at du ikke bare holder på å snakke med bare andre Element-brukere. Og det er veldig sikkert.
+
+Element er i stand til å gjøre alt dette fordi det opererer på Matrix - standarden for åpen, desentralisert kommunikasjon.
+
+Element setter deg i kontroll ved å la deg velge hvem som er vert for samtalene dine. Fra Element-appen kan du velge å være vert på forskjellige måter:
+
+1. Få en gratis konto på matrix.org-serveren som er vert for Matrix-utviklerne, eller velg blant tusenvis av offentlige servere som er vert for frivillige
+2. Vær vert for kontoen din ved å kjøre en server på din egen maskinvare
+3. Registrer deg for en konto på en tilpasset server ved å bare abonnere på Hosting Matrix Services-vertsplattformen
+
+ Hvorfor velge Element?
+
+ EGNE DATA DINE : Du bestemmer hvor du vil oppbevare dataene og meldingene dine. Du eier den og kontrollerer den, ikke noe MEGACORP som utvinner dataene dine eller gir tilgang til tredjeparter.
+
+ ÅPEN MELDING OG SAMARBEID : Du kan chatte med alle andre i Matrix-nettverket, enten de bruker Element eller en annen Matrix-app, og selv om de bruker et annet meldingssystem som Slack, IRC eller XMPP.
+
+ SUPER-SECURE : Ekte end-to-end-kryptering (bare de i samtalen kan dekryptere meldinger), og kryssignering for å verifisere enhetene til samtaledeltakerne.
+
+ KOMPLETT KOMMUNIKASJON : Meldinger, tale- og videosamtaler, fildeling, skjermdeling og en hel haug med integrasjoner, bots og widgets. Bygg rom, lokalsamfunn, hold kontakten og få ting gjort.
+
+ ALT DER DU ER : Hold kontakten uansett hvor du er med fullt synkronisert meldingslogg på alle enhetene dine og på nettet på https://app.element.io.
diff --git a/upstream_fastlane/metadata/android/nb/changelogs/40100170.txt b/upstream_fastlane/metadata/android/nb/changelogs/40100170.txt
new file mode 100644
index 0000000000..3593e50e05
--- /dev/null
+++ b/upstream_fastlane/metadata/android/nb/changelogs/40100170.txt
@@ -0,0 +1,2 @@
+Hovedendringene i denne versjonen: Bugfikser!
+Full endringslogg: https://github.com/vector-im/element-android/releases/tag/v1.0.17
diff --git a/upstream_fastlane/metadata/android/nb/changelogs/40101010.txt b/upstream_fastlane/metadata/android/nb/changelogs/40101010.txt
new file mode 100644
index 0000000000..2d80855ed8
--- /dev/null
+++ b/upstream_fastlane/metadata/android/nb/changelogs/40101010.txt
@@ -0,0 +1,2 @@
+Hovedendringene i denne versjonen: Forbedringer i ytelse og bugfikser!
+Full endringslogg: https://github.com/vector-im/element-android/releases/tag/v1.1.1
diff --git a/upstream_fastlane/metadata/android/ru/changelogs/40101020.txt b/upstream_fastlane/metadata/android/ru/changelogs/40101020.txt
new file mode 100644
index 0000000000..70e164f39d
--- /dev/null
+++ b/upstream_fastlane/metadata/android/ru/changelogs/40101020.txt
@@ -0,0 +1,2 @@
+Основные изменения в этой версии: улучшение производительности и исправления ошибок!
+Полный список изменений: https://github.com/vector-im/element-android/releases/tag/v1.1.2
diff --git a/upstream_fastlane/metadata/android/ru/changelogs/40101030.txt b/upstream_fastlane/metadata/android/ru/changelogs/40101030.txt
new file mode 100644
index 0000000000..381c2761d0
--- /dev/null
+++ b/upstream_fastlane/metadata/android/ru/changelogs/40101030.txt
@@ -0,0 +1,2 @@
+Основные изменения в этой версии: улучшение производительности и исправления ошибок!
+Полный список изменений: https://github.com/vector-im/element-android/releases/tag/v1.1.3
diff --git a/upstream_fastlane/metadata/android/sv/changelogs/40101020.txt b/upstream_fastlane/metadata/android/sv/changelogs/40101020.txt
new file mode 100644
index 0000000000..229793ab31
--- /dev/null
+++ b/upstream_fastlane/metadata/android/sv/changelogs/40101020.txt
@@ -0,0 +1,2 @@
+Huvudsakliga ändringar i den här versionen: prestandaförbättringar och buggfixar!
+Full ändringslogg: https://github.com/vector-im/element-android/releases/tag/v1.1.2
diff --git a/upstream_fastlane/metadata/android/sv/changelogs/40101030.txt b/upstream_fastlane/metadata/android/sv/changelogs/40101030.txt
new file mode 100644
index 0000000000..7e0f8c80d2
--- /dev/null
+++ b/upstream_fastlane/metadata/android/sv/changelogs/40101030.txt
@@ -0,0 +1,2 @@
+Huvudsakliga ändringar i den här versionen: prestandaförbättringar och buggfixar!
+Full ändringslogg: https://github.com/vector-im/element-android/releases/tag/v1.1.3
diff --git a/upstream_fastlane/metadata/android/uk/changelogs/40101020.txt b/upstream_fastlane/metadata/android/uk/changelogs/40101020.txt
new file mode 100644
index 0000000000..469de21a6f
--- /dev/null
+++ b/upstream_fastlane/metadata/android/uk/changelogs/40101020.txt
@@ -0,0 +1,2 @@
+Основні зміни в цій версії: поліпшення продуктивності та виправлення помилок!
+Повний журнал змін: https://github.com/vector-im/element-android/releases/tag/v1.1.2
diff --git a/upstream_fastlane/metadata/android/uk/changelogs/40101030.txt b/upstream_fastlane/metadata/android/uk/changelogs/40101030.txt
new file mode 100644
index 0000000000..da2bb0ddd6
--- /dev/null
+++ b/upstream_fastlane/metadata/android/uk/changelogs/40101030.txt
@@ -0,0 +1,2 @@
+Основні зміни в цій версії: поліпшення продуктивності та виправлення помилок!
+Повний журнал змін: https://github.com/vector-im/element-android/releases/tag/v1.1.3
diff --git a/upstream_fastlane/metadata/android/zh-Hant/changelogs/40101020.txt b/upstream_fastlane/metadata/android/zh-Hant/changelogs/40101020.txt
new file mode 100644
index 0000000000..90e76b074e
--- /dev/null
+++ b/upstream_fastlane/metadata/android/zh-Hant/changelogs/40101020.txt
@@ -0,0 +1,2 @@
+此版本中的主要變更:效能改進與錯誤修復!
+完整變更紀錄:https://github.com/vector-im/element-android/releases/tag/v1.1.2
diff --git a/upstream_fastlane/metadata/android/zh-Hant/changelogs/40101030.txt b/upstream_fastlane/metadata/android/zh-Hant/changelogs/40101030.txt
new file mode 100644
index 0000000000..c13d6ecfd4
--- /dev/null
+++ b/upstream_fastlane/metadata/android/zh-Hant/changelogs/40101030.txt
@@ -0,0 +1,2 @@
+此版本中的主要變更:效能改進與錯誤修復!
+完整變更紀錄:https://github.com/vector-im/element-android/releases/tag/v1.1.3
diff --git a/vector/build.gradle b/vector/build.gradle
index 8d300f77b5..71b72f67e1 100644
--- a/vector/build.gradle
+++ b/vector/build.gradle
@@ -14,7 +14,7 @@ kapt {
// Note: 2 digits max for each value
ext.versionMajor = 1
ext.versionMinor = 1
-ext.versionPatch = 6
+ext.versionPatch = 7
ext.scVersion = 31
@@ -66,9 +66,9 @@ static def gitBranchName() {
}
}
-// For Google Play build, build on any other branch than master will have a "-dev" suffix
+// For Google Play build, build on any other branch than main will have a "-dev" suffix
static def getGplayVersionSuffix() {
- if (gitBranchName() == "master" || true) {
+ if (gitBranchName() == "main" || true) {
return ""
} else {
return "-dev"
@@ -121,7 +121,7 @@ android {
renderscriptSupportModeEnabled true
// `develop` branch will have version code from timestamp, to ensure each build from CI has a incremented versionCode.
- // Other branches (master, features, etc.) will have version code based on application version.
+ // Other branches (main, features, etc.) will have version code based on application version.
versionCode 40100450
// Required for sonar analysis
@@ -295,14 +295,14 @@ android {
dependencies {
- def epoxy_version = '4.4.4'
- def fragment_version = '1.3.2'
+ def epoxy_version = '4.5.0'
+ def fragment_version = '1.3.3'
def arrow_version = "0.8.2"
def markwon_version = '4.1.2'
- def big_image_viewer_version = '1.7.1'
+ def big_image_viewer_version = '1.8.0'
def glide_version = '4.12.0'
def moshi_version = '1.12.0'
- def daggerVersion = '2.33'
+ def daggerVersion = '2.35'
def autofill_version = "1.1.0"
def work_version = '2.5.0'
def arch_version = '2.1.0'
@@ -325,19 +325,20 @@ dependencies {
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:$kotlin_coroutines_version"
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$kotlin_coroutines_version"
- implementation "androidx.recyclerview:recyclerview:1.2.0-rc01"
+ implementation "androidx.recyclerview:recyclerview:1.2.0"
implementation 'androidx.appcompat:appcompat:1.2.0'
implementation "androidx.fragment:fragment-ktx:$fragment_version"
implementation 'androidx.constraintlayout:constraintlayout:2.0.4'
implementation "androidx.sharetarget:sharetarget:1.1.0"
implementation 'androidx.core:core-ktx:1.3.2'
- implementation "androidx.media:media:1.2.1"
+ implementation "androidx.media:media:1.3.0"
implementation "org.threeten:threetenbp:1.4.0:no-tzdb"
implementation "com.gabrielittner.threetenbp:lazythreetenbp:0.9.0"
implementation "com.squareup.moshi:moshi-adapters:$moshi_version"
implementation "androidx.lifecycle:lifecycle-extensions:$lifecycle_version"
+ implementation "androidx.lifecycle:lifecycle-livedata-ktx:2.3.1"
kapt "com.squareup.moshi:moshi-kotlin-codegen:$moshi_version"
// Log
@@ -347,7 +348,7 @@ dependencies {
implementation 'com.facebook.stetho:stetho:1.6.0'
// Phone number https://github.com/google/libphonenumber
- implementation 'com.googlecode.libphonenumber:libphonenumber:8.12.21'
+ implementation 'com.googlecode.libphonenumber:libphonenumber:8.12.22'
// rx
implementation 'io.reactivex.rxjava2:rxkotlin:2.4.0'
@@ -393,7 +394,7 @@ dependencies {
implementation 'androidx.browser:browser:1.3.0'
// Passphrase strength helper
- implementation 'com.nulab-inc:zxcvbn:1.4.0'
+ implementation 'com.nulab-inc:zxcvbn:1.5.0'
//Alerter
implementation 'com.tapadoo.android:alerter:7.0.1'
@@ -428,7 +429,7 @@ dependencies {
kapt "com.google.dagger:dagger-compiler:$daggerVersion"
// gplay flavor only
- gplayImplementation('com.google.firebase:firebase-messaging:21.0.1') {
+ gplayImplementation('com.google.firebase:firebase-messaging:21.1.0') {
exclude group: 'com.google.firebase', module: 'firebase-core'
exclude group: 'com.google.firebase', module: 'firebase-analytics'
exclude group: 'com.google.firebase', module: 'firebase-measurement-connector'
diff --git a/vector/sampledata/matrix.json b/vector/sampledata/matrix.json
index 5328ec81b7..c69e0201ad 100644
--- a/vector/sampledata/matrix.json
+++ b/vector/sampledata/matrix.json
@@ -6,6 +6,7 @@
"message": "William Shakespeare (bapt. 26 April 1564 – 23 April 1616) was an English poet, playwright and actor, widely regarded as the greatest writer in the English language and the world's greatest dramatist. He is often called England's national poet and the \"Bard of Avon\". His extant works, including collaborations, consist of approximately 39 plays, 154 sonnets, two long narrative poems, and a few other verses, some of uncertain authorship. His plays have been translated into every major living language and are performed more often than those of any other playwright.\n\nShakespeare was born and raised in Stratford-upon-Avon, Warwickshire. At the age of 18, he married Anne Hathaway, with whom he had three children: Susanna and twins Hamnet and Judith. Sometime between 1585 and 1592, he began a successful career in London as an actor, writer, and part-owner of a playing company called the Lord Chamberlain's Men, later known as the King's Men. At age 49 (around 1613), he appears to have retired to Stratford, where he died three years later. Few records of Shakespeare's private life survive; this has stimulated considerable speculation about such matters as his physical appearance, his sexuality, his religious beliefs, and whether the works attributed to him were written by others. Such theories are often criticised for failing to adequately note that few records survive of most commoners of the period.\n\nShakespeare produced most of his known works between 1589 and 1613. His early plays were primarily comedies and histories and are regarded as some of the best work produced in these genres. Until about 1608, he wrote mainly tragedies, among them Hamlet, Othello, King Lear, and Macbeth, all considered to be among the finest works in the English language. In the last phase of his life, he wrote tragicomedies (also known as romances) and collaborated with other playwrights.\n\nMany of Shakespeare's plays were published in editions of varying quality and accuracy in his lifetime. However, in 1623, two fellow actors and friends of Shakespeare's, John Heminges and Henry Condell, published a more definitive text known as the First Folio, a posthumous collected edition of Shakespeare's dramatic works that included all but two of his plays. The volume was prefaced with a poem by Ben Jonson, in which Jonson presciently hails Shakespeare in a now-famous quote as \"not of an age, but for all time\".\n\nThroughout the 20th and 21st centuries, Shakespeare's works have been continually adapted and rediscovered by new movements in scholarship and performance. His plays remain popular and are studied, performed, and reinterpreted through various cultural and political contexts around the world.",
"roomName": "Matrix HQ",
"roomAlias": "#matrix:matrix.org",
+ "spaceName": "Runner's world",
"roomTopic": "Welcome to Matrix HQ! Here is the rest of the room topic, with a https://www.example.org url and a phone number: 0102030405 which should not be clickable."
},
{
@@ -14,6 +15,7 @@
"message": "Hello!",
"roomName": "Room name very loooooooong with some details",
"roomAlias": "#matrix:matrix.org",
+ "spaceName": "Matrix Org",
"roomTopic": "Room topic very loooooooong with some details"
},
{
@@ -22,6 +24,7 @@
"message": "How are you?",
"roomName": "Room name very loooooooong with some details",
"roomAlias": "#matrix:matrix.org",
+ "spaceName": "Rennes",
"roomTopic": "Room topic very loooooooong with some details"
},
{
@@ -30,6 +33,7 @@
"message": "Great weather today!",
"roomName": "Room name very loooooooong with some details",
"roomAlias": "#matrix:matrix.org",
+ "spaceName": "Est London",
"roomTopic": "Room topic very loooooooong with some details"
},
{
@@ -38,6 +42,7 @@
"message": "Let's do a picnic",
"roomName": "Room name very loooooooong with some details",
"roomAlias": "#matrix:matrix.org",
+ "spaceName": "Element HQ",
"roomTopic": "Room topic very loooooooong with some details"
},
{
@@ -46,6 +51,7 @@
"message": "Yes, great idea",
"roomName": "Room name very loooooooong with some details",
"roomAlias": "#matrix:matrix.org",
+ "spaceName": "My Company",
"roomTopic": "Room topic very loooooooong with some details"
}
]
diff --git a/vector/src/debug/res/layout/item_sas_emoji.xml b/vector/src/debug/res/layout/item_sas_emoji.xml
index 53fd448f90..fc56bc1948 100644
--- a/vector/src/debug/res/layout/item_sas_emoji.xml
+++ b/vector/src/debug/res/layout/item_sas_emoji.xml
@@ -39,7 +39,7 @@
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:textSize="16sp"
- tools:text="@string/verification_emoji_wrench" />
+ tools:text="@string/verification_emoji_spanner" />
+ tools:text="verification_emoji_spanner" />
diff --git a/vector/src/gplay/java/im/vector/app/gplay/features/settings/troubleshoot/TestPushFromPushGateway.kt b/vector/src/gplay/java/im/vector/app/gplay/features/settings/troubleshoot/TestPushFromPushGateway.kt
index ad4b9ecb01..15c7e88bac 100644
--- a/vector/src/gplay/java/im/vector/app/gplay/features/settings/troubleshoot/TestPushFromPushGateway.kt
+++ b/vector/src/gplay/java/im/vector/app/gplay/features/settings/troubleshoot/TestPushFromPushGateway.kt
@@ -42,8 +42,10 @@ class TestPushFromPushGateway @Inject constructor(private val context: AppCompat
: TroubleshootTest(R.string.settings_troubleshoot_test_push_loop_title) {
private var action: Job? = null
+ private var pushReceived: Boolean = false
override fun perform(activityResultLauncher: ActivityResultLauncher) {
+ pushReceived = false
val fcmToken = FcmHelper.getFcmToken(context) ?: run {
status = TestStatus.FAILED
return
@@ -55,9 +57,15 @@ class TestPushFromPushGateway @Inject constructor(private val context: AppCompat
status = result
.fold(
{
- // Wait for the push to be received
- description = stringProvider.getString(R.string.settings_troubleshoot_test_push_loop_waiting_for_push)
- TestStatus.RUNNING
+ if (pushReceived) {
+ // Push already received (race condition)
+ description = stringProvider.getString(R.string.settings_troubleshoot_test_push_loop_success)
+ TestStatus.SUCCESS
+ } else {
+ // Wait for the push to be received
+ description = stringProvider.getString(R.string.settings_troubleshoot_test_push_loop_waiting_for_push)
+ TestStatus.RUNNING
+ }
},
{
description = if (it is PushGatewayFailure.PusherRejected) {
@@ -73,6 +81,7 @@ class TestPushFromPushGateway @Inject constructor(private val context: AppCompat
}
override fun onPushReceived() {
+ pushReceived = true
description = stringProvider.getString(R.string.settings_troubleshoot_test_push_loop_success)
status = TestStatus.SUCCESS
}
diff --git a/vector/src/main/AndroidManifest.xml b/vector/src/main/AndroidManifest.xml
index c9e004c4cc..b806f69123 100644
--- a/vector/src/main/AndroidManifest.xml
+++ b/vector/src/main/AndroidManifest.xml
@@ -28,6 +28,9 @@
+
+
+
+
+
+
+