Skip to content
Snippets Groups Projects
Commit b84879d7 authored by Benoit Marty's avatar Benoit Marty
Browse files

Import v1.5.11 from Element Android

parent 9b27bec1
No related branches found
No related tags found
No related merge requests found
Showing
with 557 additions and 46 deletions
...@@ -10,7 +10,7 @@ def gradle = "7.3.1" ...@@ -10,7 +10,7 @@ def gradle = "7.3.1"
// Ref: https://kotlinlang.org/releases.html // Ref: https://kotlinlang.org/releases.html
def kotlin = "1.7.21" def kotlin = "1.7.21"
def kotlinCoroutines = "1.6.4" def kotlinCoroutines = "1.6.4"
def dagger = "2.44" def dagger = "2.44.2"
def appDistribution = "16.0.0-beta05" def appDistribution = "16.0.0-beta05"
def retrofit = "2.9.0" def retrofit = "2.9.0"
def markwon = "4.6.2" def markwon = "4.6.2"
...@@ -83,7 +83,7 @@ ext.libs = [ ...@@ -83,7 +83,7 @@ ext.libs = [
'appdistributionApi' : "com.google.firebase:firebase-appdistribution-api-ktx:$appDistribution", 'appdistributionApi' : "com.google.firebase:firebase-appdistribution-api-ktx:$appDistribution",
'appdistribution' : "com.google.firebase:firebase-appdistribution:$appDistribution", 'appdistribution' : "com.google.firebase:firebase-appdistribution:$appDistribution",
// Phone number https://github.com/google/libphonenumber // Phone number https://github.com/google/libphonenumber
'phonenumber' : "com.googlecode.libphonenumber:libphonenumber:8.13.0" 'phonenumber' : "com.googlecode.libphonenumber:libphonenumber:8.13.1"
], ],
dagger : [ dagger : [
'dagger' : "com.google.dagger:dagger:$dagger", 'dagger' : "com.google.dagger:dagger:$dagger",
...@@ -98,7 +98,7 @@ ext.libs = [ ...@@ -98,7 +98,7 @@ ext.libs = [
], ],
element : [ element : [
'opusencoder' : "io.element.android:opusencoder:1.1.0", 'opusencoder' : "io.element.android:opusencoder:1.1.0",
'wysiwyg' : "io.element.android:wysiwyg:0.4.0" 'wysiwyg' : "io.element.android:wysiwyg:0.7.0.1"
], ],
squareup : [ squareup : [
'moshi' : "com.squareup.moshi:moshi:$moshi", 'moshi' : "com.squareup.moshi:moshi:$moshi",
......
File added
...@@ -50,6 +50,7 @@ import org.matrix.android.sdk.api.session.room.send.SendState ...@@ -50,6 +50,7 @@ import org.matrix.android.sdk.api.session.room.send.SendState
import org.matrix.android.sdk.api.session.room.timeline.Timeline import org.matrix.android.sdk.api.session.room.timeline.Timeline
import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent
import org.matrix.android.sdk.api.session.room.timeline.TimelineSettings import org.matrix.android.sdk.api.session.room.timeline.TimelineSettings
import org.matrix.android.sdk.api.session.sync.filter.SyncFilterBuilder
import timber.log.Timber import timber.log.Timber
import java.util.UUID import java.util.UUID
import java.util.concurrent.CountDownLatch import java.util.concurrent.CountDownLatch
...@@ -346,6 +347,10 @@ class CommonTestHelper internal constructor(context: Context, val cryptoConfig: ...@@ -346,6 +347,10 @@ class CommonTestHelper internal constructor(context: Context, val cryptoConfig:
assertTrue(registrationResult is RegistrationResult.Success) assertTrue(registrationResult is RegistrationResult.Success)
val session = (registrationResult as RegistrationResult.Success).session val session = (registrationResult as RegistrationResult.Success).session
session.open() session.open()
session.filterService().setSyncFilter(
SyncFilterBuilder()
.lazyLoadMembersForStateEvents(true)
)
if (sessionTestParams.withInitialSync) { if (sessionTestParams.withInitialSync) {
syncSession(session, 120_000) syncSession(session, 120_000)
} }
......
/*
* Copyright 2022 The Matrix.org Foundation C.I.C.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.matrix.android.sdk.internal.database
import android.content.Context
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.platform.app.InstrumentationRegistry
import io.realm.Realm
import org.amshove.kluent.fail
import org.amshove.kluent.shouldBe
import org.amshove.kluent.shouldBeEqualTo
import org.amshove.kluent.shouldNotBe
import org.junit.After
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
import org.matrix.android.sdk.api.session.events.model.toModel
import org.matrix.android.sdk.api.session.room.model.message.MessageContent
import org.matrix.android.sdk.internal.database.mapper.EventMapper
import org.matrix.android.sdk.internal.database.model.EventAnnotationsSummaryEntity
import org.matrix.android.sdk.internal.database.model.SessionRealmModule
import org.matrix.android.sdk.internal.database.query.where
import org.matrix.android.sdk.internal.util.Normalizer
@RunWith(AndroidJUnit4::class)
class RealmSessionStoreMigration43Test {
@get:Rule val configurationFactory = TestRealmConfigurationFactory()
lateinit var context: Context
var realm: Realm? = null
@Before
fun setUp() {
context = InstrumentationRegistry.getInstrumentation().context
}
@After
fun tearDown() {
realm?.close()
}
@Test
fun migrationShouldBeNeeed() {
val realmName = "session_42.realm"
val realmConfiguration = configurationFactory.createConfiguration(
realmName,
"efa9ab2c77ae06b0e767ffdb1c45b12be3c77d48d94f1ac41a7cd1d637fc59ac41f869a250453074e21ce13cfe7ed535593e7d150c08ce2bad7a2ab8c7b841f0",
SessionRealmModule(),
43,
null
)
configurationFactory.copyRealmFromAssets(context, realmName, realmName)
try {
realm = Realm.getInstance(realmConfiguration)
fail("Should need a migration")
} catch (failure: Throwable) {
// nop
}
}
// Database key for alias `session_db_e00482619b2597069b1f192b86de7da9`: efa9ab2c77ae06b0e767ffdb1c45b12be3c77d48d94f1ac41a7cd1d637fc59ac41f869a250453074e21ce13cfe7ed535593e7d150c08ce2bad7a2ab8c7b841f0
// $WEJ8U6Zsx3TDZx3qmHIOKh-mXe5kqL_MnPcIkStEwwI
// $11EtAQ8RYcudJVtw7e6B5Vm4ufCqKTOWKblY2U_wrpo
@Test
fun testMigration43() {
val realmName = "session_42.realm"
val migration = RealmSessionStoreMigration(Normalizer())
val realmConfiguration = configurationFactory.createConfiguration(
realmName,
"efa9ab2c77ae06b0e767ffdb1c45b12be3c77d48d94f1ac41a7cd1d637fc59ac41f869a250453074e21ce13cfe7ed535593e7d150c08ce2bad7a2ab8c7b841f0",
SessionRealmModule(),
43,
migration
)
configurationFactory.copyRealmFromAssets(context, realmName, realmName)
realm = Realm.getInstance(realmConfiguration)
// assert that the edit from 42 are migrated
val editions = EventAnnotationsSummaryEntity
.where(realm!!, "\$WEJ8U6Zsx3TDZx3qmHIOKh-mXe5kqL_MnPcIkStEwwI")
.findFirst()
?.editSummary
?.editions
editions shouldNotBe null
editions!!.size shouldBe 1
val firstEdition = editions.first()
firstEdition?.eventId shouldBeEqualTo "\$DvOyA8vJxwGfTaJG3OEJVcL4isShyaVDnprihy38W28"
firstEdition?.isLocalEcho shouldBeEqualTo false
val editEvent = EventMapper.map(firstEdition!!.event!!)
val body = editEvent.content.toModel<MessageContent>()?.body
body shouldBeEqualTo "* Message 2 with edit"
// assert that the edit from 42 are migrated
val editionsOfE2E = EventAnnotationsSummaryEntity
.where(realm!!, "\$11EtAQ8RYcudJVtw7e6B5Vm4ufCqKTOWKblY2U_wrpo")
.findFirst()
?.editSummary
?.editions
editionsOfE2E shouldNotBe null
editionsOfE2E!!.size shouldBe 1
val firstEditionE2E = editionsOfE2E.first()
firstEditionE2E?.eventId shouldBeEqualTo "\$HUwJOQRCJwfPv7XSKvBPcvncjM0oR3q2tGIIIdv9Zts"
firstEditionE2E?.isLocalEcho shouldBeEqualTo false
val editEventE2E = EventMapper.map(firstEditionE2E!!.event!!)
val body2 = editEventE2E.getClearContent().toModel<MessageContent>()?.body
body2 shouldBeEqualTo "* Message 2, e2e edit"
}
}
/*
* 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
import android.content.Context
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.platform.app.InstrumentationRegistry
import io.realm.Realm
import org.junit.After
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
import org.matrix.android.sdk.internal.database.model.SessionRealmModule
import org.matrix.android.sdk.internal.util.Normalizer
@RunWith(AndroidJUnit4::class)
class SessionSanityMigrationTest {
@get:Rule val configurationFactory = TestRealmConfigurationFactory()
lateinit var context: Context
var realm: Realm? = null
@Before
fun setUp() {
context = InstrumentationRegistry.getInstrumentation().context
}
@After
fun tearDown() {
realm?.close()
}
@Test
fun sessionDatabaseShouldMigrateGracefully() {
val realmName = "session_42.realm"
val migration = RealmSessionStoreMigration(Normalizer())
val realmConfiguration = configurationFactory.createConfiguration(
realmName,
"efa9ab2c77ae06b0e767ffdb1c45b12be3c77d48d94f1ac41a7cd1d637fc59ac41f869a250453074e21ce13cfe7ed535593e7d150c08ce2bad7a2ab8c7b841f0",
SessionRealmModule(),
migration.schemaVersion,
migration
)
configurationFactory.copyRealmFromAssets(context, realmName, realmName)
realm = Realm.getInstance(realmConfiguration)
}
}
/*
* Copyright 2022 The Matrix.org Foundation C.I.C.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.matrix.android.sdk.internal.database
import android.content.Context
import androidx.test.platform.app.InstrumentationRegistry
import io.realm.Realm
import io.realm.RealmConfiguration
import io.realm.RealmMigration
import org.junit.rules.TemporaryFolder
import org.junit.runner.Description
import org.junit.runners.model.Statement
import java.io.File
import java.io.FileOutputStream
import java.io.IOException
import java.io.InputStream
import java.lang.IllegalStateException
import java.util.Collections
import java.util.Locale
import java.util.concurrent.ConcurrentHashMap
import kotlin.Throws
/**
* Based on https://github.com/realm/realm-java/blob/master/realm/realm-library/src/testUtils/java/io/realm/TestRealmConfigurationFactory.java
*/
class TestRealmConfigurationFactory : TemporaryFolder() {
private val map: Map<RealmConfiguration, Boolean> = ConcurrentHashMap()
private val configurations = Collections.newSetFromMap(map)
@get:Synchronized private var isUnitTestFailed = false
private var testName = ""
private var tempFolder: File? = null
override fun apply(base: Statement, description: Description): Statement {
return object : Statement() {
@Throws(Throwable::class)
override fun evaluate() {
setTestName(description)
before()
try {
base.evaluate()
} catch (throwable: Throwable) {
setUnitTestFailed()
throw throwable
} finally {
after()
}
}
}
}
@Throws(Throwable::class)
override fun before() {
Realm.init(InstrumentationRegistry.getInstrumentation().targetContext)
super.before()
}
override fun after() {
try {
for (configuration in configurations) {
Realm.deleteRealm(configuration)
}
} catch (e: IllegalStateException) {
// Only throws the exception caused by deleting the opened Realm if the test case itself doesn't throw.
if (!isUnitTestFailed) {
throw e
}
} finally {
// This will delete the temp directory.
super.after()
}
}
@Throws(IOException::class)
override fun create() {
super.create()
tempFolder = File(super.getRoot(), testName)
check(!(tempFolder!!.exists() && !tempFolder!!.delete())) { "Could not delete folder: " + tempFolder!!.absolutePath }
check(tempFolder!!.mkdir()) { "Could not create folder: " + tempFolder!!.absolutePath }
}
override fun getRoot(): File {
checkNotNull(tempFolder) { "the temporary folder has not yet been created" }
return tempFolder!!
}
/**
* To be called in the [.apply].
*/
protected fun setTestName(description: Description) {
testName = description.displayName
}
@Synchronized
fun setUnitTestFailed() {
isUnitTestFailed = true
}
// This builder creates a configuration that is *NOT* managed.
// You have to delete it yourself.
private fun createConfigurationBuilder(): RealmConfiguration.Builder {
return RealmConfiguration.Builder().directory(root)
}
fun String.decodeHex(): ByteArray {
check(length % 2 == 0) { "Must have an even length" }
return chunked(2)
.map { it.toInt(16).toByte() }
.toByteArray()
}
fun createConfiguration(
name: String,
key: String?,
module: Any,
schemaVersion: Long,
migration: RealmMigration?
): RealmConfiguration {
val builder = createConfigurationBuilder()
builder
.directory(root)
.name(name)
.apply {
if (key != null) {
encryptionKey(key.decodeHex())
}
}
.modules(module)
// Allow writes on UI
.allowWritesOnUiThread(true)
.schemaVersion(schemaVersion)
.apply {
migration?.let { migration(it) }
}
val configuration = builder.build()
configurations.add(configuration)
return configuration
}
// Copies a Realm file from assets to temp dir
@Throws(IOException::class)
fun copyRealmFromAssets(context: Context, realmPath: String, newName: String) {
val config = RealmConfiguration.Builder()
.directory(root)
.name(newName)
.build()
copyRealmFromAssets(context, realmPath, config)
}
@Throws(IOException::class)
fun copyRealmFromAssets(context: Context, realmPath: String, config: RealmConfiguration) {
check(!File(config.path).exists()) { String.format(Locale.ENGLISH, "%s exists!", config.path) }
val outFile = File(config.realmDirectory, config.realmFileName)
copyFileFromAssets(context, realmPath, outFile)
}
@Throws(IOException::class)
fun copyFileFromAssets(context: Context, assetPath: String?, outFile: File?) {
var stream: InputStream? = null
var os: FileOutputStream? = null
try {
stream = context.assets.open(assetPath!!)
os = FileOutputStream(outFile)
val buf = ByteArray(1024)
var bytesRead: Int
while (stream.read(buf).also { bytesRead = it } > -1) {
os.write(buf, 0, bytesRead)
}
} finally {
if (stream != null) {
try {
stream.close()
} catch (ignore: IOException) {
}
}
if (os != null) {
try {
os.close()
} catch (ignore: IOException) {
}
}
}
}
}
...@@ -66,7 +66,7 @@ class PollAggregationTest : InstrumentedTest { ...@@ -66,7 +66,7 @@ class PollAggregationTest : InstrumentedTest {
val aliceEventsListener = object : Timeline.Listener { val aliceEventsListener = object : Timeline.Listener {
override fun onTimelineUpdated(snapshot: List<TimelineEvent>) { override fun onTimelineUpdated(snapshot: List<TimelineEvent>) {
snapshot.firstOrNull { it.root.getClearType() in EventType.POLL_START }?.let { pollEvent -> snapshot.firstOrNull { it.root.getClearType() in EventType.POLL_START.values }?.let { pollEvent ->
val pollEventId = pollEvent.eventId val pollEventId = pollEvent.eventId
val pollContent = pollEvent.root.content?.toModel<MessagePollContent>() val pollContent = pollEvent.root.content?.toModel<MessagePollContent>()
val pollSummary = pollEvent.annotations?.pollResponseSummary val pollSummary = pollEvent.annotations?.pollResponseSummary
......
...@@ -38,5 +38,4 @@ data class AggregatedAnnotation( ...@@ -38,5 +38,4 @@ data class AggregatedAnnotation(
override val limited: Boolean? = false, override val limited: Boolean? = false,
override val count: Int? = 0, override val count: Int? = 0,
val chunk: List<RelationChunkInfo>? = null val chunk: List<RelationChunkInfo>? = null
) : UnsignedRelationInfo ) : UnsignedRelationInfo
...@@ -19,7 +19,8 @@ import com.squareup.moshi.Json ...@@ -19,7 +19,8 @@ import com.squareup.moshi.Json
import com.squareup.moshi.JsonClass import com.squareup.moshi.JsonClass
/** /**
* <code> * Server side relation aggregation.
* ```
* { * {
* "m.annotation": { * "m.annotation": {
* "chunk": [ * "chunk": [
...@@ -43,12 +44,13 @@ import com.squareup.moshi.JsonClass ...@@ -43,12 +44,13 @@ import com.squareup.moshi.JsonClass
* "count": 1 * "count": 1
* } * }
* } * }
* </code> * ```
*/ */
@JsonClass(generateAdapter = true) @JsonClass(generateAdapter = true)
data class AggregatedRelations( data class AggregatedRelations(
@Json(name = "m.annotation") val annotations: AggregatedAnnotation? = null, @Json(name = "m.annotation") val annotations: AggregatedAnnotation? = null,
@Json(name = "m.reference") val references: DefaultUnsignedRelationInfo? = null, @Json(name = "m.reference") val references: DefaultUnsignedRelationInfo? = null,
@Json(name = "m.replace") val replaces: AggregatedReplace? = null,
@Json(name = RelationType.THREAD) val latestThread: LatestThreadUnsignedRelation? = null @Json(name = RelationType.THREAD) val latestThread: LatestThreadUnsignedRelation? = null
) )
/*
* Copyright 2022 The Matrix.org Foundation C.I.C.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.matrix.android.sdk.api.session.events.model
import com.squareup.moshi.Json
import com.squareup.moshi.JsonClass
/**
* Note that there can be multiple events with an m.replace relationship to a given event (for example, if an event is edited multiple times).
* These should be aggregated by the homeserver.
* https://spec.matrix.org/v1.4/client-server-api/#server-side-aggregation-of-mreplace-relationships
*
*/
@JsonClass(generateAdapter = true)
data class AggregatedReplace(
@Json(name = "event_id") val eventId: String? = null,
@Json(name = "origin_server_ts") val originServerTs: Long? = null,
@Json(name = "sender") val senderId: String? = null,
)
...@@ -26,13 +26,12 @@ import org.matrix.android.sdk.api.session.crypto.model.OlmDecryptionResult ...@@ -26,13 +26,12 @@ import org.matrix.android.sdk.api.session.crypto.model.OlmDecryptionResult
import org.matrix.android.sdk.api.session.events.model.content.EncryptedEventContent import org.matrix.android.sdk.api.session.events.model.content.EncryptedEventContent
import org.matrix.android.sdk.api.session.room.model.Membership import org.matrix.android.sdk.api.session.room.model.Membership
import org.matrix.android.sdk.api.session.room.model.RoomMemberContent import org.matrix.android.sdk.api.session.room.model.RoomMemberContent
import org.matrix.android.sdk.api.session.room.model.message.MessageBeaconLocationDataContent
import org.matrix.android.sdk.api.session.room.model.message.MessageContent import org.matrix.android.sdk.api.session.room.model.message.MessageContent
import org.matrix.android.sdk.api.session.room.model.message.MessagePollContent import org.matrix.android.sdk.api.session.room.model.message.MessagePollContent
import org.matrix.android.sdk.api.session.room.model.message.MessageStickerContent
import org.matrix.android.sdk.api.session.room.model.message.MessageType import org.matrix.android.sdk.api.session.room.model.message.MessageType
import org.matrix.android.sdk.api.session.room.model.message.asMessageAudioEvent import org.matrix.android.sdk.api.session.room.model.message.asMessageAudioEvent
import org.matrix.android.sdk.api.session.room.model.relation.RelationDefaultContent import org.matrix.android.sdk.api.session.room.model.relation.RelationDefaultContent
import org.matrix.android.sdk.api.session.room.model.relation.isReply
import org.matrix.android.sdk.api.session.room.model.relation.shouldRenderInThread import org.matrix.android.sdk.api.session.room.model.relation.shouldRenderInThread
import org.matrix.android.sdk.api.session.room.send.SendState import org.matrix.android.sdk.api.session.room.send.SendState
import org.matrix.android.sdk.api.session.threads.ThreadDetails import org.matrix.android.sdk.api.session.threads.ThreadDetails
...@@ -228,11 +227,14 @@ data class Event( ...@@ -228,11 +227,14 @@ data class Event(
return when { return when {
isReplyRenderedInThread() || isQuote() -> ContentUtils.extractUsefulTextFromReply(text) isReplyRenderedInThread() || isQuote() -> ContentUtils.extractUsefulTextFromReply(text)
isFileMessage() -> "sent a file." isFileMessage() -> "sent a file."
isVoiceMessage() -> "sent a voice message."
isAudioMessage() -> "sent an audio file." isAudioMessage() -> "sent an audio file."
isImageMessage() -> "sent an image." isImageMessage() -> "sent an image."
isVideoMessage() -> "sent a video." isVideoMessage() -> "sent a video."
isSticker() -> "sent a sticker" isSticker() -> "sent a sticker."
isPoll() -> getPollQuestion() ?: "created a poll." isPoll() -> getPollQuestion() ?: "created a poll."
isLiveLocation() -> "Live location."
isLocationMessage() -> "has shared their location."
else -> text else -> text
} }
} }
...@@ -386,24 +388,18 @@ fun Event.isLocationMessage(): Boolean { ...@@ -386,24 +388,18 @@ fun Event.isLocationMessage(): Boolean {
} }
} }
fun Event.isPoll(): Boolean = getClearType() in EventType.POLL_START || getClearType() in EventType.POLL_END fun Event.isPoll(): Boolean = getClearType() in EventType.POLL_START.values || getClearType() in EventType.POLL_END.values
fun Event.isSticker(): Boolean = getClearType() == EventType.STICKER fun Event.isSticker(): Boolean = getClearType() == EventType.STICKER
fun Event.isLiveLocation(): Boolean = getClearType() in EventType.STATE_ROOM_BEACON_INFO fun Event.isLiveLocation(): Boolean = getClearType() in EventType.STATE_ROOM_BEACON_INFO.values
fun Event.getRelationContent(): RelationDefaultContent? { fun Event.getRelationContent(): RelationDefaultContent? {
return if (isEncrypted()) { return if (isEncrypted()) {
content.toModel<EncryptedEventContent>()?.relatesTo content.toModel<EncryptedEventContent>()?.relatesTo
} else { } else {
content.toModel<MessageContent>()?.relatesTo ?: run { content.toModel<MessageContent>()?.relatesTo
// Special cases when there is only a local msgtype for some event types ?: getClearContent()?.get("m.relates_to")?.toContent().toModel() // Special cases when there is only a local msgtype for some event types
when (getClearType()) {
EventType.STICKER -> getClearContent().toModel<MessageStickerContent>()?.relatesTo
in EventType.BEACON_LOCATION_DATA -> getClearContent().toModel<MessageBeaconLocationDataContent>()?.relatesTo
else -> getClearContent()?.get("m.relates_to")?.toContent().toModel()
}
}
} }
} }
...@@ -420,7 +416,7 @@ fun Event.getRelationContentForType(type: String): RelationDefaultContent? = ...@@ -420,7 +416,7 @@ fun Event.getRelationContentForType(type: String): RelationDefaultContent? =
getRelationContent()?.takeIf { it.type == type } getRelationContent()?.takeIf { it.type == type }
fun Event.isReply(): Boolean { fun Event.isReply(): Boolean {
return getRelationContent()?.inReplyTo?.eventId != null return getRelationContent().isReply()
} }
fun Event.isReplyRenderedInThread(): Boolean { fun Event.isReplyRenderedInThread(): Boolean {
...@@ -443,11 +439,11 @@ fun Event.isInvitation(): Boolean = type == EventType.STATE_ROOM_MEMBER && ...@@ -443,11 +439,11 @@ fun Event.isInvitation(): Boolean = type == EventType.STATE_ROOM_MEMBER &&
content?.toModel<RoomMemberContent>()?.membership == Membership.INVITE content?.toModel<RoomMemberContent>()?.membership == Membership.INVITE
fun Event.getPollContent(): MessagePollContent? { fun Event.getPollContent(): MessagePollContent? {
return content.toModel<MessagePollContent>() return getClearContent().toModel<MessagePollContent>()
} }
fun Event.supportsNotification() = fun Event.supportsNotification() =
this.getClearType() in EventType.MESSAGE + EventType.POLL_START + EventType.STATE_ROOM_BEACON_INFO this.getClearType() in EventType.MESSAGE + EventType.POLL_START.values + EventType.STATE_ROOM_BEACON_INFO.values
fun Event.isContentReportable() = fun Event.isContentReportable() =
this.getClearType() in EventType.MESSAGE + EventType.STATE_ROOM_BEACON_INFO this.getClearType() in EventType.MESSAGE + EventType.STATE_ROOM_BEACON_INFO.values
/*
* Copyright 2022 The Matrix.org Foundation C.I.C.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.matrix.android.sdk.api.session.events.model
fun Event.toValidDecryptedEvent(): ValidDecryptedEvent? {
if (!this.isEncrypted()) return null
val decryptedContent = this.getDecryptedContent() ?: return null
val eventId = this.eventId ?: return null
val roomId = this.roomId ?: return null
val type = this.getDecryptedType() ?: return null
val senderKey = this.getSenderKey() ?: return null
val algorithm = this.content?.get("algorithm") as? String ?: return null
// copy the relation as it's in clear in the encrypted content
val updatedContent = this.content.get("m.relates_to")?.let {
decryptedContent.toMutableMap().apply {
put("m.relates_to", it)
}
} ?: decryptedContent
return ValidDecryptedEvent(
type = type,
eventId = eventId,
clearContent = updatedContent,
prevContent = this.prevContent,
originServerTs = this.originServerTs ?: 0,
cryptoSenderKey = senderKey,
roomId = roomId,
unsignedData = this.unsignedData,
redacts = this.redacts,
algorithm = algorithm
)
}
...@@ -49,11 +49,10 @@ object EventType { ...@@ -49,11 +49,10 @@ object EventType {
const val STATE_ROOM_JOIN_RULES = "m.room.join_rules" const val STATE_ROOM_JOIN_RULES = "m.room.join_rules"
const val STATE_ROOM_GUEST_ACCESS = "m.room.guest_access" const val STATE_ROOM_GUEST_ACCESS = "m.room.guest_access"
const val STATE_ROOM_POWER_LEVELS = "m.room.power_levels" const val STATE_ROOM_POWER_LEVELS = "m.room.power_levels"
val STATE_ROOM_BEACON_INFO = listOf("org.matrix.msc3672.beacon_info", "m.beacon_info") val STATE_ROOM_BEACON_INFO = StableUnstableId(stable = "m.beacon_info", unstable = "org.matrix.msc3672.beacon_info")
val BEACON_LOCATION_DATA = listOf("org.matrix.msc3672.beacon", "m.beacon") val BEACON_LOCATION_DATA = StableUnstableId(stable = "m.beacon", unstable = "org.matrix.msc3672.beacon")
const val STATE_SPACE_CHILD = "m.space.child" const val STATE_SPACE_CHILD = "m.space.child"
const val STATE_SPACE_PARENT = "m.space.parent" const val STATE_SPACE_PARENT = "m.space.parent"
/** /**
...@@ -81,8 +80,7 @@ object EventType { ...@@ -81,8 +80,7 @@ object EventType {
const val CALL_NEGOTIATE = "m.call.negotiate" const val CALL_NEGOTIATE = "m.call.negotiate"
const val CALL_REJECT = "m.call.reject" const val CALL_REJECT = "m.call.reject"
const val CALL_HANGUP = "m.call.hangup" const val CALL_HANGUP = "m.call.hangup"
const val CALL_ASSERTED_IDENTITY = "m.call.asserted_identity" val CALL_ASSERTED_IDENTITY = StableUnstableId(stable = "m.call.asserted_identity", unstable = "org.matrix.call.asserted_identity")
const val CALL_ASSERTED_IDENTITY_PREFIX = "org.matrix.call.asserted_identity"
// This type is not processed by the client, just sent to the server // This type is not processed by the client, just sent to the server
const val CALL_REPLACES = "m.call.replaces" const val CALL_REPLACES = "m.call.replaces"
...@@ -90,10 +88,7 @@ object EventType { ...@@ -90,10 +88,7 @@ object EventType {
// Key share events // Key share events
const val ROOM_KEY_REQUEST = "m.room_key_request" const val ROOM_KEY_REQUEST = "m.room_key_request"
const val FORWARDED_ROOM_KEY = "m.forwarded_room_key" const val FORWARDED_ROOM_KEY = "m.forwarded_room_key"
val ROOM_KEY_WITHHELD = StableUnstableId( val ROOM_KEY_WITHHELD = StableUnstableId(stable = "m.room_key.withheld", unstable = "org.matrix.room_key.withheld")
stable = "m.room_key.withheld",
unstable = "org.matrix.room_key.withheld"
)
const val REQUEST_SECRET = "m.secret.request" const val REQUEST_SECRET = "m.secret.request"
const val SEND_SECRET = "m.secret.send" const val SEND_SECRET = "m.secret.send"
...@@ -111,9 +106,9 @@ object EventType { ...@@ -111,9 +106,9 @@ object EventType {
const val REACTION = "m.reaction" const val REACTION = "m.reaction"
// Poll // Poll
val POLL_START = listOf("org.matrix.msc3381.poll.start", "m.poll.start") val POLL_START = StableUnstableId(stable = "m.poll.start", unstable = "org.matrix.msc3381.poll.start")
val POLL_RESPONSE = listOf("org.matrix.msc3381.poll.response", "m.poll.response") val POLL_RESPONSE = StableUnstableId(stable = "m.poll.response", unstable = "org.matrix.msc3381.poll.response")
val POLL_END = listOf("org.matrix.msc3381.poll.end", "m.poll.end") val POLL_END = StableUnstableId(stable = "m.poll.end", unstable = "org.matrix.msc3381.poll.end")
// Unwedging // Unwedging
internal const val DUMMY = "m.dummy" internal const val DUMMY = "m.dummy"
......
/*
* Copyright 2022 The Matrix.org Foundation C.I.C.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.matrix.android.sdk.api.session.events.model
import org.matrix.android.sdk.api.session.room.model.message.MessageRelationContent
import org.matrix.android.sdk.api.session.room.model.relation.RelationDefaultContent
data class ValidDecryptedEvent(
val type: String,
val eventId: String,
val clearContent: Content,
val prevContent: Content? = null,
val originServerTs: Long,
val cryptoSenderKey: String,
val roomId: String,
val unsignedData: UnsignedData? = null,
val redacts: String? = null,
val algorithm: String,
)
fun ValidDecryptedEvent.getRelationContent(): RelationDefaultContent? {
return clearContent.toModel<MessageRelationContent?>()?.relatesTo
}
...@@ -47,10 +47,9 @@ interface LocationSharingService { ...@@ -47,10 +47,9 @@ interface LocationSharingService {
/** /**
* Starts sharing live location in the room. * Starts sharing live location in the room.
* @param timeoutMillis timeout of the live in milliseconds * @param timeoutMillis timeout of the live in milliseconds
* @param description description of the live for text fallback
* @return the result of the update of the live * @return the result of the update of the live
*/ */
suspend fun startLiveLocationShare(timeoutMillis: Long, description: String): UpdateLiveLocationShareResult suspend fun startLiveLocationShare(timeoutMillis: Long): UpdateLiveLocationShareResult
/** /**
* Stops sharing live location in the room. * Stops sharing live location in the room.
......
...@@ -15,10 +15,10 @@ ...@@ -15,10 +15,10 @@
*/ */
package org.matrix.android.sdk.api.session.room.model package org.matrix.android.sdk.api.session.room.model
import org.matrix.android.sdk.api.session.events.model.Content import org.matrix.android.sdk.api.session.events.model.Event
data class EditAggregatedSummary( data class EditAggregatedSummary(
val latestContent: Content? = null, val latestEdit: Event? = null,
// The list of the eventIDs used to build the summary (might be out of sync if chunked received from message chunk) // The list of the eventIDs used to build the summary (might be out of sync if chunked received from message chunk)
val sourceEvents: List<String>, val sourceEvents: List<String>,
val localEchos: List<String>, val localEchos: List<String>,
......
...@@ -18,5 +18,6 @@ package org.matrix.android.sdk.api.session.room.model ...@@ -18,5 +18,6 @@ package org.matrix.android.sdk.api.session.room.model
data class ReadReceipt( data class ReadReceipt(
val roomMember: RoomMemberSummary, val roomMember: RoomMemberSummary,
val originServerTs: Long val originServerTs: Long,
val threadId: String?
) )
...@@ -34,7 +34,7 @@ data class MessageStickerContent( ...@@ -34,7 +34,7 @@ data class MessageStickerContent(
* Required. A textual representation of the image. This could be the alt text of the image, the filename of the image, * Required. A textual representation of the image. This could be the alt text of the image, the filename of the image,
* or some kind of content description for accessibility e.g. 'image attachment'. * or some kind of content description for accessibility e.g. 'image attachment'.
*/ */
@Json(name = "body") override val body: String, @Json(name = "body") override val body: String = "",
/** /**
* Metadata about the image referred to in url. * Metadata about the image referred to in url.
......
...@@ -28,3 +28,5 @@ data class RelationDefaultContent( ...@@ -28,3 +28,5 @@ data class RelationDefaultContent(
) : RelationContent ) : RelationContent
fun RelationDefaultContent.shouldRenderInThread(): Boolean = isFallingBack == false fun RelationDefaultContent.shouldRenderInThread(): Boolean = isFallingBack == false
fun RelationDefaultContent?.isReply(): Boolean = this?.inReplyTo?.eventId != null
...@@ -34,12 +34,14 @@ interface ReadService { ...@@ -34,12 +34,14 @@ interface ReadService {
/** /**
* Force the read marker to be set on the latest event. * Force the read marker to be set on the latest event.
*/ */
suspend fun markAsRead(params: MarkAsReadParams = MarkAsReadParams.BOTH) suspend fun markAsRead(params: MarkAsReadParams = MarkAsReadParams.BOTH, mainTimeLineOnly: Boolean = true)
/** /**
* Set the read receipt on the event with provided eventId. * Set the read receipt on the event with provided eventId.
* @param eventId the id of the event where read receipt will be set
* @param threadId the id of the thread in which read receipt will be set. For main thread use [ReadService.THREAD_ID_MAIN] constant
*/ */
suspend fun setReadReceipt(eventId: String) suspend fun setReadReceipt(eventId: String, threadId: String)
/** /**
* Set the read marker on the event with provided eventId. * Set the read marker on the event with provided eventId.
...@@ -59,10 +61,10 @@ interface ReadService { ...@@ -59,10 +61,10 @@ interface ReadService {
/** /**
* Returns a live read receipt id for the room. * Returns a live read receipt id for the room.
*/ */
fun getMyReadReceiptLive(): LiveData<Optional<String>> fun getMyReadReceiptLive(threadId: String?): LiveData<Optional<String>>
/** /**
* Get the eventId where the read receipt for the provided user is. * Get the eventId from the main timeline where the read receipt for the provided user is.
* @param userId the id of the user to look for * @param userId the id of the user to look for
* *
* @return the eventId where the read receipt for the provided user is attached, or null if not found * @return the eventId where the read receipt for the provided user is attached, or null if not found
...@@ -74,4 +76,8 @@ interface ReadService { ...@@ -74,4 +76,8 @@ interface ReadService {
* @param eventId the event * @param eventId the event
*/ */
fun getEventReadReceiptsLive(eventId: String): LiveData<List<ReadReceipt>> fun getEventReadReceiptsLive(eventId: String): LiveData<List<ReadReceipt>>
companion object {
const val THREAD_ID_MAIN = "main"
}
} }
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment