Merge remote-tracking branch 'origin/develop' into dependency-cleanup
This commit is contained in:
commit
9ce9ad6d3a
|
@ -36,7 +36,7 @@ Other changes
|
||||||
- Reformatted project code ([#5953](https://github.com/vector-im/element-android/issues/5953))
|
- Reformatted project code ([#5953](https://github.com/vector-im/element-android/issues/5953))
|
||||||
- Update check for server-side threads support to match spec. ([#5997](https://github.com/vector-im/element-android/issues/5997))
|
- Update check for server-side threads support to match spec. ([#5997](https://github.com/vector-im/element-android/issues/5997))
|
||||||
- Setup detekt ([#6038](https://github.com/vector-im/element-android/issues/6038))
|
- Setup detekt ([#6038](https://github.com/vector-im/element-android/issues/6038))
|
||||||
- Notify the user for each new message ([#46312](https://github.com/vector-im/element-android/issues/46312))
|
- Notify the user for each new message ([#4632](https://github.com/vector-im/element-android/issues/4632))
|
||||||
|
|
||||||
|
|
||||||
Changes in Element v1.4.14 (2022-05-05)
|
Changes in Element v1.4.14 (2022-05-05)
|
||||||
|
|
|
@ -27,7 +27,7 @@ buildscript {
|
||||||
classpath 'com.google.gms:google-services:4.3.10'
|
classpath 'com.google.gms:google-services:4.3.10'
|
||||||
classpath 'org.sonarsource.scanner.gradle:sonarqube-gradle-plugin:3.3'
|
classpath 'org.sonarsource.scanner.gradle:sonarqube-gradle-plugin:3.3'
|
||||||
classpath 'com.google.android.gms:oss-licenses-plugin:0.10.5'
|
classpath 'com.google.android.gms:oss-licenses-plugin:0.10.5'
|
||||||
classpath "com.likethesalad.android:stem-plugin:2.0.0"
|
classpath "com.likethesalad.android:stem-plugin:2.1.1"
|
||||||
classpath 'org.owasp:dependency-check-gradle:7.1.0.1'
|
classpath 'org.owasp:dependency-check-gradle:7.1.0.1'
|
||||||
classpath "org.jetbrains.dokka:dokka-gradle-plugin:1.6.21"
|
classpath "org.jetbrains.dokka:dokka-gradle-plugin:1.6.21"
|
||||||
classpath "org.jetbrains.kotlinx:kotlinx-knit:0.4.0"
|
classpath "org.jetbrains.kotlinx:kotlinx-knit:0.4.0"
|
||||||
|
|
|
@ -0,0 +1 @@
|
||||||
|
Adds email input and verification screens to the new FTUE onboarding flow
|
|
@ -0,0 +1 @@
|
||||||
|
FTUE - Adds the redesigned Sign In screen
|
|
@ -0,0 +1 @@
|
||||||
|
FTUE - Overrides sign up flow ordering for matrix.org only
|
|
@ -0,0 +1 @@
|
||||||
|
Use fixed text size in read receipt counter
|
|
@ -0,0 +1 @@
|
||||||
|
Live location sharing: navigation from timeline to map screen
|
|
@ -0,0 +1 @@
|
||||||
|
Revert: Use member name instead of room name in DM creation item
|
|
@ -0,0 +1 @@
|
||||||
|
Poll refactoring with unit tests
|
|
@ -0,0 +1 @@
|
||||||
|
Improve replay attacks and reduce duplicate message index errors
|
|
@ -0,0 +1 @@
|
||||||
|
Test: Ensure calling 'fail()' is not caught by the catch block
|
|
@ -0,0 +1 @@
|
||||||
|
Labs flag for enabling live location sharing
|
|
@ -0,0 +1 @@
|
||||||
|
Excludes transitive optional non FOSS google location dependency from fdroid builds
|
|
@ -0,0 +1 @@
|
||||||
|
Glide - Use current drawable while loading new static map image
|
|
@ -0,0 +1 @@
|
||||||
|
Fix sending multiple invites to a room reaching only one or two people
|
|
@ -0,0 +1 @@
|
||||||
|
[Live location sharing] Update entity in DB when a live is timed out
|
|
@ -0,0 +1 @@
|
||||||
|
Prevent widget web view from reloading on screen / orientation change
|
|
@ -0,0 +1 @@
|
||||||
|
Downgrade gradle from 7.2.0 to 7.1.3
|
|
@ -0,0 +1 @@
|
||||||
|
Fix decrypting redacted event from sending errors
|
|
@ -7,7 +7,10 @@ ext.versions = [
|
||||||
'targetCompat' : JavaVersion.VERSION_11,
|
'targetCompat' : JavaVersion.VERSION_11,
|
||||||
]
|
]
|
||||||
|
|
||||||
def gradle = "7.2.0"
|
|
||||||
|
// Pinned to 7.1.3 because of https://github.com/vector-im/element-android/issues/6142
|
||||||
|
// Please test carefully before upgrading again.
|
||||||
|
def gradle = "7.1.3"
|
||||||
// Ref: https://kotlinlang.org/releases.html
|
// Ref: https://kotlinlang.org/releases.html
|
||||||
def kotlin = "1.6.21"
|
def kotlin = "1.6.21"
|
||||||
def kotlinCoroutines = "1.6.1"
|
def kotlinCoroutines = "1.6.1"
|
||||||
|
@ -23,7 +26,7 @@ def mavericks = "2.6.1"
|
||||||
def glide = "4.13.2"
|
def glide = "4.13.2"
|
||||||
def bigImageViewer = "1.8.1"
|
def bigImageViewer = "1.8.1"
|
||||||
def jjwt = "0.11.5"
|
def jjwt = "0.11.5"
|
||||||
def vanniktechEmoji = "0.9.0"
|
def vanniktechEmoji = "0.13.0"
|
||||||
|
|
||||||
// Testing
|
// Testing
|
||||||
def mockk = "1.12.4"
|
def mockk = "1.12.4"
|
||||||
|
@ -51,7 +54,7 @@ ext.libs = [
|
||||||
'recyclerview' : "androidx.recyclerview:recyclerview:1.2.1",
|
'recyclerview' : "androidx.recyclerview:recyclerview:1.2.1",
|
||||||
'exifinterface' : "androidx.exifinterface:exifinterface:1.3.3",
|
'exifinterface' : "androidx.exifinterface:exifinterface:1.3.3",
|
||||||
'fragmentKtx' : "androidx.fragment:fragment-ktx:1.4.1",
|
'fragmentKtx' : "androidx.fragment:fragment-ktx:1.4.1",
|
||||||
'constraintLayout' : "androidx.constraintlayout:constraintlayout:2.1.3",
|
'constraintLayout' : "androidx.constraintlayout:constraintlayout:2.1.4",
|
||||||
'work' : "androidx.work:work-runtime-ktx:2.7.1",
|
'work' : "androidx.work:work-runtime-ktx:2.7.1",
|
||||||
'autoFill' : "androidx.autofill:autofill:1.1.0",
|
'autoFill' : "androidx.autofill:autofill:1.1.0",
|
||||||
'preferenceKtx' : "androidx.preference:preference-ktx:1.2.0",
|
'preferenceKtx' : "androidx.preference:preference-ktx:1.2.0",
|
||||||
|
@ -110,6 +113,10 @@ ext.libs = [
|
||||||
'mavericks' : "com.airbnb.android:mavericks:$mavericks",
|
'mavericks' : "com.airbnb.android:mavericks:$mavericks",
|
||||||
'mavericksTesting' : "com.airbnb.android:mavericks-testing:$mavericks"
|
'mavericksTesting' : "com.airbnb.android:mavericks-testing:$mavericks"
|
||||||
],
|
],
|
||||||
|
maplibre : [
|
||||||
|
'androidSdk' : "org.maplibre.gl:android-sdk:9.5.2",
|
||||||
|
'pluginAnnotation' : "org.maplibre.gl:android-plugin-annotation-v9:1.0.0"
|
||||||
|
],
|
||||||
mockk : [
|
mockk : [
|
||||||
'mockk' : "io.mockk:mockk:$mockk",
|
'mockk' : "io.mockk:mockk:$mockk",
|
||||||
'mockkAndroid' : "io.mockk:mockk-android:$mockk"
|
'mockkAndroid' : "io.mockk:mockk-android:$mockk"
|
||||||
|
|
|
@ -0,0 +1,19 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<item>
|
||||||
|
<shape>
|
||||||
|
<gradient
|
||||||
|
android:angle="@integer/rtl_mirror_flip"
|
||||||
|
android:endColor="#55DFD1FF"
|
||||||
|
android:startColor="#55A5F2E0" />
|
||||||
|
</shape>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<shape>
|
||||||
|
<gradient
|
||||||
|
android:angle="90"
|
||||||
|
android:endColor="@android:color/transparent"
|
||||||
|
android:startColor="?android:colorBackground" />
|
||||||
|
</shape>
|
||||||
|
</item>
|
||||||
|
</layer-list>
|
|
@ -1,5 +1,5 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<resources>
|
<resources xmlns:tools="http://schemas.android.com/tools">
|
||||||
|
|
||||||
<style name="TimelineContentStubBaseParams">
|
<style name="TimelineContentStubBaseParams">
|
||||||
<item name="android:layout_width">match_parent</item>
|
<item name="android:layout_width">match_parent</item>
|
||||||
|
@ -33,5 +33,8 @@
|
||||||
<item name="android:gravity">center</item>
|
<item name="android:gravity">center</item>
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
|
<style name="TimelineFixedSizeCaptionStyle" parent="@style/Widget.Vector.TextView.Caption">
|
||||||
|
<item name="android:textSize" tools:ignore="SpUsage">12dp</item>
|
||||||
|
</style>
|
||||||
|
|
||||||
</resources>
|
</resources>
|
|
@ -0,0 +1,44 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 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
|
||||||
|
|
||||||
|
import junit.framework.TestCase.fail
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Will fail the test if invoking [block] is not throwing a Throwable.
|
||||||
|
*
|
||||||
|
* @param message the failure message, if the block does not throw any Throwable
|
||||||
|
* @param failureBlock a Lambda to be able to do extra check on the thrown Throwable
|
||||||
|
* @param block the block to test
|
||||||
|
*/
|
||||||
|
internal suspend fun mustFail(
|
||||||
|
message: String = "must fail",
|
||||||
|
failureBlock: ((Throwable) -> Unit)? = null,
|
||||||
|
block: suspend () -> Unit,
|
||||||
|
) {
|
||||||
|
val isSuccess = try {
|
||||||
|
block.invoke()
|
||||||
|
true
|
||||||
|
} catch (throwable: Throwable) {
|
||||||
|
failureBlock?.invoke(throwable)
|
||||||
|
false
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isSuccess) {
|
||||||
|
fail(message)
|
||||||
|
}
|
||||||
|
}
|
|
@ -40,6 +40,9 @@ class RetryTestRule(val retryCount: Int = 3) : TestRule {
|
||||||
for (i in 0 until retryCount) {
|
for (i in 0 until retryCount) {
|
||||||
try {
|
try {
|
||||||
base.evaluate()
|
base.evaluate()
|
||||||
|
if (i > 0) {
|
||||||
|
println("Retried test $i times")
|
||||||
|
}
|
||||||
return
|
return
|
||||||
} catch (t: Throwable) {
|
} catch (t: Throwable) {
|
||||||
caughtThrowable = t
|
caughtThrowable = t
|
||||||
|
|
|
@ -0,0 +1,79 @@
|
||||||
|
/*
|
||||||
|
* 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.crypto
|
||||||
|
|
||||||
|
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||||
|
import org.junit.Assert
|
||||||
|
import org.junit.FixMethodOrder
|
||||||
|
import org.junit.Test
|
||||||
|
import org.junit.runner.RunWith
|
||||||
|
import org.junit.runners.MethodSorters
|
||||||
|
import org.matrix.android.sdk.InstrumentedTest
|
||||||
|
import org.matrix.android.sdk.api.session.events.model.Event
|
||||||
|
import org.matrix.android.sdk.api.session.events.model.toModel
|
||||||
|
import org.matrix.android.sdk.api.session.getRoom
|
||||||
|
import org.matrix.android.sdk.api.session.room.getTimelineEvent
|
||||||
|
import org.matrix.android.sdk.common.CommonTestHelper
|
||||||
|
import org.matrix.android.sdk.common.CryptoTestHelper
|
||||||
|
|
||||||
|
@RunWith(AndroidJUnit4::class)
|
||||||
|
@FixMethodOrder(MethodSorters.JVM)
|
||||||
|
class DecryptRedactedEventTest : InstrumentedTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun doNotFailToDecryptRedactedEvent() {
|
||||||
|
val testHelper = CommonTestHelper(context())
|
||||||
|
val cryptoTestHelper = CryptoTestHelper(testHelper)
|
||||||
|
|
||||||
|
val testData = cryptoTestHelper.doE2ETestWithAliceAndBobInARoom(true)
|
||||||
|
val e2eRoomID = testData.roomId
|
||||||
|
val aliceSession = testData.firstSession
|
||||||
|
val bobSession = testData.secondSession!!
|
||||||
|
|
||||||
|
val roomALicePOV = aliceSession.getRoom(e2eRoomID)!!
|
||||||
|
val timelineEvent = testHelper.sendTextMessage(roomALicePOV, "Hello", 1).first()
|
||||||
|
val redactionReason = "Wrong Room"
|
||||||
|
roomALicePOV.sendService().redactEvent(timelineEvent.root, redactionReason)
|
||||||
|
|
||||||
|
// get the event from bob
|
||||||
|
testHelper.waitWithLatch {
|
||||||
|
testHelper.retryPeriodicallyWithLatch(it) {
|
||||||
|
bobSession.getRoom(e2eRoomID)?.getTimelineEvent(timelineEvent.eventId)?.root?.isRedacted() == true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val eventBobPov = bobSession.getRoom(e2eRoomID)?.getTimelineEvent(timelineEvent.eventId)!!
|
||||||
|
|
||||||
|
testHelper.runBlockingTest {
|
||||||
|
try {
|
||||||
|
val result = bobSession.cryptoService().decryptEvent(eventBobPov.root, "")
|
||||||
|
Assert.assertEquals(
|
||||||
|
"Unexpected redacted reason",
|
||||||
|
redactionReason,
|
||||||
|
result.clearEvent.toModel<Event>()?.unsignedData?.redactedEvent?.content?.get("reason")
|
||||||
|
)
|
||||||
|
Assert.assertEquals(
|
||||||
|
"Unexpected Redacted event id",
|
||||||
|
timelineEvent.eventId,
|
||||||
|
result.clearEvent.toModel<Event>()?.unsignedData?.redactedEvent?.redacts
|
||||||
|
)
|
||||||
|
} catch (failure: Throwable) {
|
||||||
|
Assert.fail("Should not throw when decrypting a redacted event")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -23,6 +23,7 @@ import org.amshove.kluent.fail
|
||||||
import org.amshove.kluent.internal.assertEquals
|
import org.amshove.kluent.internal.assertEquals
|
||||||
import org.junit.Assert
|
import org.junit.Assert
|
||||||
import org.junit.FixMethodOrder
|
import org.junit.FixMethodOrder
|
||||||
|
import org.junit.Ignore
|
||||||
import org.junit.Rule
|
import org.junit.Rule
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
import org.junit.runner.RunWith
|
import org.junit.runner.RunWith
|
||||||
|
@ -62,11 +63,13 @@ import org.matrix.android.sdk.common.RetryTestRule
|
||||||
import org.matrix.android.sdk.common.SessionTestParams
|
import org.matrix.android.sdk.common.SessionTestParams
|
||||||
import org.matrix.android.sdk.common.TestConstants
|
import org.matrix.android.sdk.common.TestConstants
|
||||||
import org.matrix.android.sdk.common.TestMatrixCallback
|
import org.matrix.android.sdk.common.TestMatrixCallback
|
||||||
|
import org.matrix.android.sdk.mustFail
|
||||||
import java.util.concurrent.CountDownLatch
|
import java.util.concurrent.CountDownLatch
|
||||||
|
|
||||||
@RunWith(JUnit4::class)
|
@RunWith(JUnit4::class)
|
||||||
@FixMethodOrder(MethodSorters.JVM)
|
@FixMethodOrder(MethodSorters.JVM)
|
||||||
@LargeTest
|
@LargeTest
|
||||||
|
@Ignore("This test fails with an unhandled exception thrown from a coroutine which terminates the entire test run.")
|
||||||
class E2eeSanityTests : InstrumentedTest {
|
class E2eeSanityTests : InstrumentedTest {
|
||||||
|
|
||||||
@get:Rule val rule = RetryTestRule(3)
|
@get:Rule val rule = RetryTestRule(3)
|
||||||
|
@ -525,10 +528,8 @@ class E2eeSanityTests : InstrumentedTest {
|
||||||
|
|
||||||
// Confirm we can decrypt one but not the other
|
// Confirm we can decrypt one but not the other
|
||||||
testHelper.runBlockingTest {
|
testHelper.runBlockingTest {
|
||||||
try {
|
mustFail(message = "Should not be able to decrypt event") {
|
||||||
newBobSession.cryptoService().decryptEvent(firstEventNewBobPov.root, "")
|
newBobSession.cryptoService().decryptEvent(firstEventNewBobPov.root, "")
|
||||||
fail("Should not be able to decrypt event")
|
|
||||||
} catch (_: MXCryptoError) {
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -21,7 +21,6 @@ import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||||
import androidx.test.filters.LargeTest
|
import androidx.test.filters.LargeTest
|
||||||
import junit.framework.TestCase.assertNotNull
|
import junit.framework.TestCase.assertNotNull
|
||||||
import junit.framework.TestCase.assertTrue
|
import junit.framework.TestCase.assertTrue
|
||||||
import junit.framework.TestCase.fail
|
|
||||||
import org.amshove.kluent.internal.assertEquals
|
import org.amshove.kluent.internal.assertEquals
|
||||||
import org.junit.Assert
|
import org.junit.Assert
|
||||||
import org.junit.Assert.assertNull
|
import org.junit.Assert.assertNull
|
||||||
|
@ -47,6 +46,7 @@ import org.matrix.android.sdk.common.CryptoTestHelper
|
||||||
import org.matrix.android.sdk.common.RetryTestRule
|
import org.matrix.android.sdk.common.RetryTestRule
|
||||||
import org.matrix.android.sdk.common.SessionTestParams
|
import org.matrix.android.sdk.common.SessionTestParams
|
||||||
import org.matrix.android.sdk.common.TestConstants
|
import org.matrix.android.sdk.common.TestConstants
|
||||||
|
import org.matrix.android.sdk.mustFail
|
||||||
|
|
||||||
@RunWith(AndroidJUnit4::class)
|
@RunWith(AndroidJUnit4::class)
|
||||||
@FixMethodOrder(MethodSorters.JVM)
|
@FixMethodOrder(MethodSorters.JVM)
|
||||||
|
@ -95,12 +95,10 @@ class KeyShareTests : InstrumentedTest {
|
||||||
assertNotNull(receivedEvent)
|
assertNotNull(receivedEvent)
|
||||||
assert(receivedEvent!!.isEncrypted())
|
assert(receivedEvent!!.isEncrypted())
|
||||||
|
|
||||||
try {
|
|
||||||
commonTestHelper.runBlockingTest {
|
commonTestHelper.runBlockingTest {
|
||||||
|
mustFail {
|
||||||
aliceSession2.cryptoService().decryptEvent(receivedEvent.root, "foo")
|
aliceSession2.cryptoService().decryptEvent(receivedEvent.root, "foo")
|
||||||
}
|
}
|
||||||
fail("should fail")
|
|
||||||
} catch (failure: Throwable) {
|
|
||||||
}
|
}
|
||||||
|
|
||||||
val outgoingRequestsBefore = aliceSession2.cryptoService().getOutgoingRoomKeyRequests()
|
val outgoingRequestsBefore = aliceSession2.cryptoService().getOutgoingRoomKeyRequests()
|
||||||
|
@ -168,12 +166,10 @@ class KeyShareTests : InstrumentedTest {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
|
||||||
commonTestHelper.runBlockingTest {
|
commonTestHelper.runBlockingTest {
|
||||||
|
mustFail {
|
||||||
aliceSession2.cryptoService().decryptEvent(receivedEvent.root, "foo")
|
aliceSession2.cryptoService().decryptEvent(receivedEvent.root, "foo")
|
||||||
}
|
}
|
||||||
fail("should fail")
|
|
||||||
} catch (failure: Throwable) {
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Mark the device as trusted
|
// Mark the device as trusted
|
||||||
|
|
|
@ -42,6 +42,7 @@ import org.matrix.android.sdk.common.MockOkHttpInterceptor
|
||||||
import org.matrix.android.sdk.common.RetryTestRule
|
import org.matrix.android.sdk.common.RetryTestRule
|
||||||
import org.matrix.android.sdk.common.SessionTestParams
|
import org.matrix.android.sdk.common.SessionTestParams
|
||||||
import org.matrix.android.sdk.common.TestConstants
|
import org.matrix.android.sdk.common.TestConstants
|
||||||
|
import org.matrix.android.sdk.mustFail
|
||||||
|
|
||||||
@RunWith(AndroidJUnit4::class)
|
@RunWith(AndroidJUnit4::class)
|
||||||
@FixMethodOrder(MethodSorters.JVM)
|
@FixMethodOrder(MethodSorters.JVM)
|
||||||
|
@ -96,18 +97,20 @@ class WithHeldTests : InstrumentedTest {
|
||||||
// =============================
|
// =============================
|
||||||
|
|
||||||
// Bob should not be able to decrypt because the keys is withheld
|
// Bob should not be able to decrypt because the keys is withheld
|
||||||
try {
|
|
||||||
// .. might need to wait a bit for stability?
|
// .. might need to wait a bit for stability?
|
||||||
testHelper.runBlockingTest {
|
testHelper.runBlockingTest {
|
||||||
bobUnverifiedSession.cryptoService().decryptEvent(eventBobPOV.root, "")
|
mustFail(
|
||||||
}
|
message = "This session should not be able to decrypt",
|
||||||
Assert.fail("This session should not be able to decrypt")
|
failureBlock = { failure ->
|
||||||
} catch (failure: Throwable) {
|
|
||||||
val type = (failure as MXCryptoError.Base).errorType
|
val type = (failure as MXCryptoError.Base).errorType
|
||||||
val technicalMessage = failure.technicalMessage
|
val technicalMessage = failure.technicalMessage
|
||||||
Assert.assertEquals("Error should be withheld", MXCryptoError.ErrorType.KEYS_WITHHELD, type)
|
Assert.assertEquals("Error should be withheld", MXCryptoError.ErrorType.KEYS_WITHHELD, type)
|
||||||
Assert.assertEquals("Cause should be unverified", WithHeldCode.UNVERIFIED.value, technicalMessage)
|
Assert.assertEquals("Cause should be unverified", WithHeldCode.UNVERIFIED.value, technicalMessage)
|
||||||
}
|
}
|
||||||
|
) {
|
||||||
|
bobUnverifiedSession.cryptoService().decryptEvent(eventBobPOV.root, "")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Let's see if the reply we got from bob first session is unverified
|
// Let's see if the reply we got from bob first session is unverified
|
||||||
testHelper.waitWithLatch { latch ->
|
testHelper.waitWithLatch { latch ->
|
||||||
|
@ -137,17 +140,18 @@ class WithHeldTests : InstrumentedTest {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Previous message should still be undecryptable (partially withheld session)
|
// Previous message should still be undecryptable (partially withheld session)
|
||||||
try {
|
|
||||||
// .. might need to wait a bit for stability?
|
// .. might need to wait a bit for stability?
|
||||||
testHelper.runBlockingTest {
|
testHelper.runBlockingTest {
|
||||||
bobUnverifiedSession.cryptoService().decryptEvent(eventBobPOV.root, "")
|
mustFail(
|
||||||
}
|
message = "This session should not be able to decrypt",
|
||||||
Assert.fail("This session should not be able to decrypt")
|
failureBlock = { failure ->
|
||||||
} catch (failure: Throwable) {
|
|
||||||
val type = (failure as MXCryptoError.Base).errorType
|
val type = (failure as MXCryptoError.Base).errorType
|
||||||
val technicalMessage = failure.technicalMessage
|
val technicalMessage = failure.technicalMessage
|
||||||
Assert.assertEquals("Error should be withheld", MXCryptoError.ErrorType.KEYS_WITHHELD, type)
|
Assert.assertEquals("Error should be withheld", MXCryptoError.ErrorType.KEYS_WITHHELD, type)
|
||||||
Assert.assertEquals("Cause should be unverified", WithHeldCode.UNVERIFIED.value, technicalMessage)
|
Assert.assertEquals("Cause should be unverified", WithHeldCode.UNVERIFIED.value, technicalMessage)
|
||||||
|
}) {
|
||||||
|
bobUnverifiedSession.cryptoService().decryptEvent(eventBobPOV.root, "")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
testHelper.signOutAndClose(aliceSession)
|
testHelper.signOutAndClose(aliceSession)
|
||||||
|
@ -190,17 +194,18 @@ class WithHeldTests : InstrumentedTest {
|
||||||
|
|
||||||
// Previous message should still be undecryptable (partially withheld session)
|
// Previous message should still be undecryptable (partially withheld session)
|
||||||
val eventBobPOV = bobSession.getRoom(testData.roomId)?.getTimelineEvent(eventId)
|
val eventBobPOV = bobSession.getRoom(testData.roomId)?.getTimelineEvent(eventId)
|
||||||
try {
|
|
||||||
// .. might need to wait a bit for stability?
|
// .. might need to wait a bit for stability?
|
||||||
testHelper.runBlockingTest {
|
testHelper.runBlockingTest {
|
||||||
bobSession.cryptoService().decryptEvent(eventBobPOV!!.root, "")
|
mustFail(
|
||||||
}
|
message = "This session should not be able to decrypt",
|
||||||
Assert.fail("This session should not be able to decrypt")
|
failureBlock = { failure ->
|
||||||
} catch (failure: Throwable) {
|
|
||||||
val type = (failure as MXCryptoError.Base).errorType
|
val type = (failure as MXCryptoError.Base).errorType
|
||||||
val technicalMessage = failure.technicalMessage
|
val technicalMessage = failure.technicalMessage
|
||||||
Assert.assertEquals("Error should be withheld", MXCryptoError.ErrorType.KEYS_WITHHELD, type)
|
Assert.assertEquals("Error should be withheld", MXCryptoError.ErrorType.KEYS_WITHHELD, type)
|
||||||
Assert.assertEquals("Cause should be unverified", WithHeldCode.NO_OLM.value, technicalMessage)
|
Assert.assertEquals("Cause should be unverified", WithHeldCode.NO_OLM.value, technicalMessage)
|
||||||
|
}) {
|
||||||
|
bobSession.cryptoService().decryptEvent(eventBobPOV!!.root, "")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ensure that alice has marked the session to be shared with bob
|
// Ensure that alice has marked the session to be shared with bob
|
||||||
|
|
|
@ -24,6 +24,7 @@ import org.junit.Assert.assertNotNull
|
||||||
import org.junit.Assert.assertNull
|
import org.junit.Assert.assertNull
|
||||||
import org.junit.Assert.assertTrue
|
import org.junit.Assert.assertTrue
|
||||||
import org.junit.FixMethodOrder
|
import org.junit.FixMethodOrder
|
||||||
|
import org.junit.Rule
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
import org.junit.runner.RunWith
|
import org.junit.runner.RunWith
|
||||||
import org.junit.runners.MethodSorters
|
import org.junit.runners.MethodSorters
|
||||||
|
@ -45,6 +46,7 @@ import org.matrix.android.sdk.api.session.crypto.model.ImportRoomKeysResult
|
||||||
import org.matrix.android.sdk.api.session.getRoom
|
import org.matrix.android.sdk.api.session.getRoom
|
||||||
import org.matrix.android.sdk.common.CommonTestHelper
|
import org.matrix.android.sdk.common.CommonTestHelper
|
||||||
import org.matrix.android.sdk.common.CryptoTestHelper
|
import org.matrix.android.sdk.common.CryptoTestHelper
|
||||||
|
import org.matrix.android.sdk.common.RetryTestRule
|
||||||
import org.matrix.android.sdk.common.TestConstants
|
import org.matrix.android.sdk.common.TestConstants
|
||||||
import org.matrix.android.sdk.common.TestMatrixCallback
|
import org.matrix.android.sdk.common.TestMatrixCallback
|
||||||
import java.util.Collections
|
import java.util.Collections
|
||||||
|
@ -55,6 +57,8 @@ import java.util.concurrent.CountDownLatch
|
||||||
@LargeTest
|
@LargeTest
|
||||||
class KeysBackupTest : InstrumentedTest {
|
class KeysBackupTest : InstrumentedTest {
|
||||||
|
|
||||||
|
@get:Rule val rule = RetryTestRule(3)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* - From doE2ETestWithAliceAndBobInARoomWithEncryptedMessages, we should have no backed up keys
|
* - From doE2ETestWithAliceAndBobInARoomWithEncryptedMessages, we should have no backed up keys
|
||||||
* - Check backup keys after having marked one as backed up
|
* - Check backup keys after having marked one as backed up
|
||||||
|
|
|
@ -0,0 +1,115 @@
|
||||||
|
/*
|
||||||
|
* 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.crypto.replayattack
|
||||||
|
|
||||||
|
import androidx.test.filters.LargeTest
|
||||||
|
import org.amshove.kluent.internal.assertFailsWith
|
||||||
|
import org.junit.Assert
|
||||||
|
import org.junit.Assert.assertEquals
|
||||||
|
import org.junit.Assert.fail
|
||||||
|
import org.junit.FixMethodOrder
|
||||||
|
import org.junit.Test
|
||||||
|
import org.junit.runner.RunWith
|
||||||
|
import org.junit.runners.JUnit4
|
||||||
|
import org.junit.runners.MethodSorters
|
||||||
|
import org.matrix.android.sdk.InstrumentedTest
|
||||||
|
import org.matrix.android.sdk.api.session.crypto.MXCryptoError
|
||||||
|
import org.matrix.android.sdk.common.CommonTestHelper
|
||||||
|
import org.matrix.android.sdk.common.CryptoTestHelper
|
||||||
|
|
||||||
|
@RunWith(JUnit4::class)
|
||||||
|
@FixMethodOrder(MethodSorters.JVM)
|
||||||
|
@LargeTest
|
||||||
|
class ReplayAttackTest : InstrumentedTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun replayAttackAlreadyDecryptedEventTest() {
|
||||||
|
val testHelper = CommonTestHelper(context())
|
||||||
|
val cryptoTestHelper = CryptoTestHelper(testHelper)
|
||||||
|
val cryptoTestData = cryptoTestHelper.doE2ETestWithAliceAndBobInARoom(true)
|
||||||
|
|
||||||
|
val e2eRoomID = cryptoTestData.roomId
|
||||||
|
|
||||||
|
// Alice
|
||||||
|
val aliceSession = cryptoTestData.firstSession
|
||||||
|
val aliceRoomPOV = aliceSession.roomService().getRoom(e2eRoomID)!!
|
||||||
|
|
||||||
|
// Bob
|
||||||
|
val bobSession = cryptoTestData.secondSession
|
||||||
|
val bobRoomPOV = bobSession!!.roomService().getRoom(e2eRoomID)!!
|
||||||
|
assertEquals(bobRoomPOV.roomSummary()?.joinedMembersCount, 2)
|
||||||
|
|
||||||
|
// Alice will send a message
|
||||||
|
val sentEvents = testHelper.sendTextMessage(aliceRoomPOV, "Hello I will be decrypted twice", 1)
|
||||||
|
assertEquals(1, sentEvents.size)
|
||||||
|
|
||||||
|
val fakeEventId = sentEvents[0].eventId + "_fake"
|
||||||
|
val fakeEventWithTheSameIndex =
|
||||||
|
sentEvents[0].copy(eventId = fakeEventId, root = sentEvents[0].root.copy(eventId = fakeEventId))
|
||||||
|
|
||||||
|
testHelper.runBlockingTest {
|
||||||
|
// Lets assume we are from the main timelineId
|
||||||
|
val timelineId = "timelineId"
|
||||||
|
// Lets decrypt the original event
|
||||||
|
aliceSession.cryptoService().decryptEvent(sentEvents[0].root, timelineId)
|
||||||
|
// Lets decrypt the fake event that will have the same message index
|
||||||
|
val exception = assertFailsWith<MXCryptoError.Base> {
|
||||||
|
// An exception should be thrown while the same index would have been used for the previous decryption
|
||||||
|
aliceSession.cryptoService().decryptEvent(fakeEventWithTheSameIndex.root, timelineId)
|
||||||
|
}
|
||||||
|
assertEquals(MXCryptoError.ErrorType.DUPLICATED_MESSAGE_INDEX, exception.errorType)
|
||||||
|
}
|
||||||
|
cryptoTestData.cleanUp(testHelper)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun replayAttackSameEventTest() {
|
||||||
|
val testHelper = CommonTestHelper(context())
|
||||||
|
val cryptoTestHelper = CryptoTestHelper(testHelper)
|
||||||
|
val cryptoTestData = cryptoTestHelper.doE2ETestWithAliceAndBobInARoom(true)
|
||||||
|
|
||||||
|
val e2eRoomID = cryptoTestData.roomId
|
||||||
|
|
||||||
|
// Alice
|
||||||
|
val aliceSession = cryptoTestData.firstSession
|
||||||
|
val aliceRoomPOV = aliceSession.roomService().getRoom(e2eRoomID)!!
|
||||||
|
|
||||||
|
// Bob
|
||||||
|
val bobSession = cryptoTestData.secondSession
|
||||||
|
val bobRoomPOV = bobSession!!.roomService().getRoom(e2eRoomID)!!
|
||||||
|
assertEquals(bobRoomPOV.roomSummary()?.joinedMembersCount, 2)
|
||||||
|
|
||||||
|
// Alice will send a message
|
||||||
|
val sentEvents = testHelper.sendTextMessage(aliceRoomPOV, "Hello I will be decrypted twice", 1)
|
||||||
|
Assert.assertTrue("Message should be sent", sentEvents.size == 1)
|
||||||
|
assertEquals(sentEvents.size, 1)
|
||||||
|
|
||||||
|
testHelper.runBlockingTest {
|
||||||
|
// Lets assume we are from the main timelineId
|
||||||
|
val timelineId = "timelineId"
|
||||||
|
// Lets decrypt the original event
|
||||||
|
aliceSession.cryptoService().decryptEvent(sentEvents[0].root, timelineId)
|
||||||
|
try {
|
||||||
|
// Lets try to decrypt the same event
|
||||||
|
aliceSession.cryptoService().decryptEvent(sentEvents[0].root, timelineId)
|
||||||
|
} catch (ex: Throwable) {
|
||||||
|
fail("Shouldn't throw a decryption error for same event")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
cryptoTestData.cleanUp(testHelper)
|
||||||
|
}
|
||||||
|
}
|
|
@ -33,7 +33,7 @@ interface ContentScannerService {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the current public curve25519 key that the AV server is advertising.
|
* Get the current public curve25519 key that the AV server is advertising.
|
||||||
* @param callback on success callback containing the server public key
|
* @param forceDownload true to force the SDK to download again the server public key
|
||||||
*/
|
*/
|
||||||
suspend fun getServerPublicKey(forceDownload: Boolean = false): String?
|
suspend fun getServerPublicKey(forceDownload: Boolean = false): String?
|
||||||
suspend fun getScanResultForAttachment(mxcUrl: String, fileInfo: ElementToDecrypt? = null): ScanStatusInfo
|
suspend fun getScanResultForAttachment(mxcUrl: String, fileInfo: ElementToDecrypt? = null): ScanStatusInfo
|
||||||
|
|
|
@ -48,8 +48,7 @@ data class IncomingRoomKeyRequest(
|
||||||
/**
|
/**
|
||||||
* Factory.
|
* Factory.
|
||||||
*
|
*
|
||||||
* @param event the event
|
* @param trail the AuditTrail data
|
||||||
* @param currentTimeMillis the current time in milliseconds
|
|
||||||
*/
|
*/
|
||||||
fun fromEvent(trail: AuditTrail): IncomingRoomKeyRequest? {
|
fun fromEvent(trail: AuditTrail): IncomingRoomKeyRequest? {
|
||||||
return trail
|
return trail
|
||||||
|
|
|
@ -46,8 +46,8 @@ class MXUsersDevicesMap<E> {
|
||||||
/**
|
/**
|
||||||
* Provides the object for a device id and a user Id.
|
* Provides the object for a device id and a user Id.
|
||||||
*
|
*
|
||||||
|
* @param userId the user id
|
||||||
* @param deviceId the device id
|
* @param deviceId the device id
|
||||||
* @param userId the object id
|
|
||||||
* @return the object
|
* @return the object
|
||||||
*/
|
*/
|
||||||
fun getObject(userId: String?, deviceId: String?): E? {
|
fun getObject(userId: String?, deviceId: String?): E? {
|
||||||
|
@ -73,8 +73,8 @@ class MXUsersDevicesMap<E> {
|
||||||
/**
|
/**
|
||||||
* Defines the objects map for a user Id.
|
* Defines the objects map for a user Id.
|
||||||
*
|
*
|
||||||
* @param objectsPerDevices the objects maps
|
|
||||||
* @param userId the user id
|
* @param userId the user id
|
||||||
|
* @param objectsPerDevices the objects maps
|
||||||
*/
|
*/
|
||||||
fun setObjects(userId: String?, objectsPerDevices: Map<String, E>?) {
|
fun setObjects(userId: String?, objectsPerDevices: Map<String, E>?) {
|
||||||
if (!userId.isNullOrBlank()) {
|
if (!userId.isNullOrBlank()) {
|
||||||
|
|
|
@ -33,7 +33,7 @@ interface FileService {
|
||||||
/**
|
/**
|
||||||
* The original file is in cache, but the decrypted files can be deleted for security reason.
|
* 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
|
* 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.
|
* @property decryptedFileInCache true if the decrypted file is available. Always true for clear files.
|
||||||
*/
|
*/
|
||||||
data class InCache(val decryptedFileInCache: Boolean) : FileState()
|
data class InCache(val decryptedFileInCache: Boolean) : FileState()
|
||||||
object Downloading : FileState()
|
object Downloading : FileState()
|
||||||
|
|
|
@ -74,6 +74,7 @@ interface IdentityService {
|
||||||
/**
|
/**
|
||||||
* Submit the code that the identity server has sent to the user (in email or SMS).
|
* Submit the code that the identity server has sent to the user (in email or SMS).
|
||||||
* Once successful, you will have to call [finalizeBindThreePid]
|
* Once successful, you will have to call [finalizeBindThreePid]
|
||||||
|
* @param threePid the three pid
|
||||||
* @param code the code sent to the user
|
* @param code the code sent to the user
|
||||||
*/
|
*/
|
||||||
suspend fun submitValidationToken(threePid: ThreePid, code: String)
|
suspend fun submitValidationToken(threePid: ThreePid, code: String)
|
||||||
|
|
|
@ -99,6 +99,7 @@ interface IntegrationManagerService {
|
||||||
* Offers to allow or disallow a native widget domain.
|
* Offers to allow or disallow a native widget domain.
|
||||||
* @param widgetType the widget type to check for
|
* @param widgetType the widget type to check for
|
||||||
* @param domain the domain to check for
|
* @param domain the domain to check for
|
||||||
|
* @param allowed true or false
|
||||||
*/
|
*/
|
||||||
suspend fun setNativeWidgetDomainAllowed(widgetType: String, domain: String, allowed: Boolean)
|
suspend fun setNativeWidgetDomainAllowed(widgetType: String, domain: String, allowed: Boolean)
|
||||||
|
|
||||||
|
|
|
@ -29,6 +29,7 @@ object MatrixLinkify {
|
||||||
* Find the matrix spans i.e matrix id , user id ... to display them as URL.
|
* Find the matrix spans i.e matrix id , user id ... to display them as URL.
|
||||||
*
|
*
|
||||||
* @param spannable the text in which the matrix items has to be clickable.
|
* @param spannable the text in which the matrix items has to be clickable.
|
||||||
|
* @param callback listener to be notified when the span is clicked
|
||||||
*/
|
*/
|
||||||
@Suppress("UNUSED_PARAMETER")
|
@Suppress("UNUSED_PARAMETER")
|
||||||
fun addLinks(spannable: Spannable, callback: MatrixPermalinkSpan.Callback?): Boolean {
|
fun addLinks(spannable: Spannable, callback: MatrixPermalinkSpan.Callback?): Boolean {
|
||||||
|
|
|
@ -22,8 +22,8 @@ import org.matrix.android.sdk.api.session.permalinks.MatrixPermalinkSpan.Callbac
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This MatrixPermalinkSpan is a clickable span which use a [Callback] to communicate back.
|
* This MatrixPermalinkSpan is a clickable span which use a [Callback] to communicate back.
|
||||||
* @param url the permalink url tied to the span
|
* @property url the permalink url tied to the span
|
||||||
* @param callback the callback to use.
|
* @property callback the callback to use.
|
||||||
*/
|
*/
|
||||||
class MatrixPermalinkSpan(private val url: String,
|
class MatrixPermalinkSpan(private val url: String,
|
||||||
private val callback: Callback? = null) : ClickableSpan() {
|
private val callback: Callback? = null) : ClickableSpan() {
|
||||||
|
|
|
@ -60,6 +60,7 @@ interface PermalinkService {
|
||||||
* Creates a permalink for a roomId, including the via parameters.
|
* Creates a permalink for a roomId, including the via parameters.
|
||||||
*
|
*
|
||||||
* @param roomId the room id
|
* @param roomId the room id
|
||||||
|
* @param viaServers the via parameter
|
||||||
* @param forceMatrixTo whether we should force using matrix.to base URL
|
* @param forceMatrixTo whether we should force using matrix.to base URL
|
||||||
*
|
*
|
||||||
* @return the permalink, or null in case of error
|
* @return the permalink, or null in case of error
|
||||||
|
@ -90,7 +91,7 @@ interface PermalinkService {
|
||||||
* Creates a HTML or Markdown mention span template. Can be used to replace a mention with a permalink to mentioned user.
|
* Creates a HTML or Markdown mention span template. Can be used to replace a mention with a permalink to mentioned user.
|
||||||
* Ex: "<a href=\"https://matrix.to/#/%1\$s\">%2\$s</a>" or "[%2\$s](https://matrix.to/#/%1\$s)"
|
* Ex: "<a href=\"https://matrix.to/#/%1\$s\">%2\$s</a>" or "[%2\$s](https://matrix.to/#/%1\$s)"
|
||||||
*
|
*
|
||||||
* @param type: type of template to create
|
* @param type type of template to create
|
||||||
* @param forceMatrixTo whether we should force using matrix.to base URL
|
* @param forceMatrixTo whether we should force using matrix.to base URL
|
||||||
*
|
*
|
||||||
* @return the created template
|
* @return the created template
|
||||||
|
|
|
@ -34,10 +34,11 @@ interface PushRuleService {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Enables/Disables a push rule and updates the actions if necessary.
|
* Enables/Disables a push rule and updates the actions if necessary.
|
||||||
|
* @param kind the rule kind
|
||||||
|
* @param ruleId the rule id
|
||||||
* @param enable Enables/Disables the rule
|
* @param enable Enables/Disables the rule
|
||||||
* @param actions Actions to update if not null
|
* @param actions Actions to update if not null
|
||||||
*/
|
*/
|
||||||
|
|
||||||
suspend fun updatePushRuleActions(kind: RuleKind, ruleId: String, enable: Boolean, actions: List<Action>?)
|
suspend fun updatePushRuleActions(kind: RuleKind, ruleId: String, enable: Boolean, actions: List<Action>?)
|
||||||
|
|
||||||
suspend fun removePushRule(kind: RuleKind, ruleId: String)
|
suspend fun removePushRule(kind: RuleKind, ruleId: String)
|
||||||
|
|
|
@ -28,7 +28,8 @@ interface RoomCryptoService {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Enable encryption of the room.
|
* Enable encryption of the room.
|
||||||
* @param Use force to ensure that this algorithm will be used. Otherwise this call
|
* @param algorithm the algorithm to set, default to [MXCRYPTO_ALGORITHM_MEGOLM]
|
||||||
|
* @param force Use force to ensure that this algorithm will be used. Otherwise this call
|
||||||
* will throw if encryption is already setup or if the algorithm is not supported. Only to
|
* will throw if encryption is already setup or if the algorithm is not supported. Only to
|
||||||
* be used by admins to fix misconfigured encryption.
|
* be used by admins to fix misconfigured encryption.
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -22,6 +22,9 @@ import org.matrix.android.sdk.api.session.room.model.message.MessageBeaconLocati
|
||||||
* Aggregation info concerning a live location share.
|
* Aggregation info concerning a live location share.
|
||||||
*/
|
*/
|
||||||
data class LiveLocationShareAggregatedSummary(
|
data class LiveLocationShareAggregatedSummary(
|
||||||
|
/**
|
||||||
|
* Indicate whether the live is currently running.
|
||||||
|
*/
|
||||||
val isActive: Boolean?,
|
val isActive: Boolean?,
|
||||||
val endOfLiveTimestampMillis: Long?,
|
val endOfLiveTimestampMillis: Long?,
|
||||||
val lastLocationDataContent: MessageBeaconLocationDataContent?,
|
val lastLocationDataContent: MessageBeaconLocationDataContent?,
|
||||||
|
|
|
@ -71,8 +71,8 @@ interface RelationService {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Edit a poll.
|
* Edit a poll.
|
||||||
* @param pollType indicates open or closed polls
|
|
||||||
* @param targetEvent The poll event to edit
|
* @param targetEvent The poll event to edit
|
||||||
|
* @param pollType indicates open or closed polls
|
||||||
* @param question The edited question
|
* @param question The edited question
|
||||||
* @param options The edited options
|
* @param options The edited options
|
||||||
*/
|
*/
|
||||||
|
@ -84,7 +84,9 @@ interface RelationService {
|
||||||
/**
|
/**
|
||||||
* Edit a text message body. Limited to "m.text" contentType.
|
* Edit a text message body. Limited to "m.text" contentType.
|
||||||
* @param targetEvent The event to edit
|
* @param targetEvent The event to edit
|
||||||
|
* @param msgType the message type
|
||||||
* @param newBodyText The edited body
|
* @param newBodyText The edited body
|
||||||
|
* @param newBodyAutoMarkdown true to parse markdown on the new body
|
||||||
* @param compatibilityBodyText The text that will appear on clients that don't support yet edition
|
* @param compatibilityBodyText The text that will appear on clients that don't support yet edition
|
||||||
*/
|
*/
|
||||||
fun editTextMessage(targetEvent: TimelineEvent,
|
fun editTextMessage(targetEvent: TimelineEvent,
|
||||||
|
@ -153,8 +155,8 @@ interface RelationService {
|
||||||
* @param rootThreadEventId the root thread eventId
|
* @param rootThreadEventId the root thread eventId
|
||||||
* @param replyInThreadText the reply text
|
* @param replyInThreadText the reply text
|
||||||
* @param msgType the message type: MessageType.MSGTYPE_TEXT (default) or MessageType.MSGTYPE_EMOTE
|
* @param msgType the message type: MessageType.MSGTYPE_TEXT (default) or MessageType.MSGTYPE_EMOTE
|
||||||
* @param formattedText The formatted body using MessageType#FORMAT_MATRIX_HTML
|
|
||||||
* @param autoMarkdown If true, the SDK will generate a formatted HTML message from the body text if markdown syntax is present
|
* @param autoMarkdown If true, the SDK will generate a formatted HTML message from the body text if markdown syntax is present
|
||||||
|
* @param formattedText The formatted body using MessageType#FORMAT_MATRIX_HTML
|
||||||
* @param eventReplied the event referenced by the reply within a thread
|
* @param eventReplied the event referenced by the reply within a thread
|
||||||
*/
|
*/
|
||||||
fun replyInThread(rootThreadEventId: String,
|
fun replyInThread(rootThreadEventId: String,
|
||||||
|
|
|
@ -71,7 +71,7 @@ interface ReadService {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns a live list of read receipts for a given event.
|
* Returns a live list of read receipts for a given event.
|
||||||
* @param eventId: the event
|
* @param eventId the event
|
||||||
*/
|
*/
|
||||||
fun getEventReadReceiptsLive(eventId: String): LiveData<List<ReadReceipt>>
|
fun getEventReadReceiptsLive(eventId: String): LiveData<List<ReadReceipt>>
|
||||||
}
|
}
|
||||||
|
|
|
@ -62,6 +62,7 @@ interface SendService {
|
||||||
* @param quotedEvent The event to which we will quote it's content.
|
* @param quotedEvent The event to which we will quote it's content.
|
||||||
* @param text the text message to send
|
* @param text the text message to send
|
||||||
* @param autoMarkdown If true, the SDK will generate a formatted HTML message from the body text if markdown syntax is present
|
* @param autoMarkdown If true, the SDK will generate a formatted HTML message from the body text if markdown syntax is present
|
||||||
|
* @param rootThreadEventId when this param is not null, the message will be sent in this specific thread
|
||||||
* @return a [Cancelable]
|
* @return a [Cancelable]
|
||||||
*/
|
*/
|
||||||
fun sendQuotedTextMessage(quotedEvent: TimelineEvent, text: String, autoMarkdown: Boolean, rootThreadEventId: String? = null): Cancelable
|
fun sendQuotedTextMessage(quotedEvent: TimelineEvent, text: String, autoMarkdown: Boolean, rootThreadEventId: String? = null): Cancelable
|
||||||
|
|
|
@ -90,23 +90,29 @@ interface StateService {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get a state event of the room.
|
* Get a state event of the room.
|
||||||
|
* @param eventType An eventType.
|
||||||
|
* @param stateKey the query which will be done on the stateKey
|
||||||
*/
|
*/
|
||||||
fun getStateEvent(eventType: String, stateKey: QueryStringValue = QueryStringValue.NoCondition): Event?
|
fun getStateEvent(eventType: String, stateKey: QueryStringValue = QueryStringValue.NoCondition): Event?
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get a live state event of the room.
|
* Get a live state event of the room.
|
||||||
|
* @param eventType An eventType.
|
||||||
|
* @param stateKey the query which will be done on the stateKey
|
||||||
*/
|
*/
|
||||||
fun getStateEventLive(eventType: String, stateKey: QueryStringValue = QueryStringValue.NoCondition): LiveData<Optional<Event>>
|
fun getStateEventLive(eventType: String, stateKey: QueryStringValue = QueryStringValue.NoCondition): LiveData<Optional<Event>>
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get state events of the room.
|
* Get state events of the room.
|
||||||
* @param eventTypes Set of eventType. If empty, all state events will be returned
|
* @param eventTypes Set of eventType. If empty, all state events will be returned
|
||||||
|
* @param stateKey the query which will be done on the stateKey
|
||||||
*/
|
*/
|
||||||
fun getStateEvents(eventTypes: Set<String>, stateKey: QueryStringValue = QueryStringValue.NoCondition): List<Event>
|
fun getStateEvents(eventTypes: Set<String>, stateKey: QueryStringValue = QueryStringValue.NoCondition): List<Event>
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get live state events of the room.
|
* Get live state events of the room.
|
||||||
* @param eventTypes Set of eventType to observe. If empty, all state events will be observed
|
* @param eventTypes Set of eventType to observe. If empty, all state events will be observed
|
||||||
|
* @param stateKey the query which will be done on the stateKey
|
||||||
*/
|
*/
|
||||||
fun getStateEventsLive(eventTypes: Set<String>, stateKey: QueryStringValue = QueryStringValue.NoCondition): LiveData<List<Event>>
|
fun getStateEventsLive(eventTypes: Set<String>, stateKey: QueryStringValue = QueryStringValue.NoCondition): LiveData<List<Event>>
|
||||||
|
|
||||||
|
|
|
@ -90,6 +90,7 @@ data class TimelineEvent(
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the metadata associated with a key.
|
* Get the metadata associated with a key.
|
||||||
|
* @param T type to cast the metadata to
|
||||||
* @param key the key to get the metadata
|
* @param key the key to get the metadata
|
||||||
* @return the metadata
|
* @return the metadata
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -92,7 +92,7 @@ interface SharedSecretStorageService {
|
||||||
* Clients MUST ensure that the key is trusted before using it to encrypt secrets.
|
* Clients MUST ensure that the key is trusted before using it to encrypt secrets.
|
||||||
*
|
*
|
||||||
* @param name The name of the secret
|
* @param name The name of the secret
|
||||||
* @param secret The secret contents.
|
* @param secretBase64 The secret contents.
|
||||||
* @param keys The list of (ID,privateKey) of the keys to use to encrypt the secret.
|
* @param keys The list of (ID,privateKey) of the keys to use to encrypt the secret.
|
||||||
*/
|
*/
|
||||||
suspend fun storeSecret(name: String, secretBase64: String, keys: List<KeyRef>)
|
suspend fun storeSecret(name: String, secretBase64: String, keys: List<KeyRef>)
|
||||||
|
|
|
@ -44,8 +44,8 @@ interface SpaceService {
|
||||||
roomAliasLocalPart: String? = null): String
|
roomAliasLocalPart: String? = null): String
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get a space from a roomId.
|
* Get a space from a spaceId.
|
||||||
* @param spaceId the roomId to look for.
|
* @param spaceId the spaceId to look for.
|
||||||
* @return a space with spaceId or null if room type is not space
|
* @return a space with spaceId or null if room type is not space
|
||||||
*/
|
*/
|
||||||
fun getSpace(spaceId: String): Space?
|
fun getSpace(spaceId: String): Space?
|
||||||
|
@ -54,21 +54,24 @@ interface SpaceService {
|
||||||
* Try to resolve (peek) rooms and subspace in this 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
|
* Use this call get preview of children of this space, particularly useful to get a
|
||||||
* preview of rooms that you did not join yet.
|
* preview of rooms that you did not join yet.
|
||||||
|
* @param spaceId the spaceId to look for.
|
||||||
*/
|
*/
|
||||||
suspend fun peekSpace(spaceId: String): SpacePeekResult
|
suspend fun peekSpace(spaceId: String): SpacePeekResult
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get's information of a space by querying the server.
|
* Get's information of a space by querying the server.
|
||||||
|
*
|
||||||
|
* @param spaceId the spaceId to look for.
|
||||||
* @param suggestedOnly If true, return only child events and rooms where the m.space.child event has suggested: true.
|
* @param suggestedOnly If true, return only child events and rooms where the m.space.child event has suggested: true.
|
||||||
* @param limit a client-defined limit to the maximum number of rooms to return per page. Must be a non-negative integer.
|
* @param limit a client-defined limit to the maximum number of rooms to return per page. Must be a non-negative integer.
|
||||||
* @param from: Optional. Pagination token given to retrieve the next set of rooms. Note that if a pagination token is provided,
|
* @param from Optional. Pagination token given to retrieve the next set of rooms. Note that if a pagination token is provided,
|
||||||
* then the parameters given for suggested_only and max_depth must be the same.
|
* then the parameters given for suggested_only and max_depth must be the same.
|
||||||
|
* @param knownStateList when paginating, pass back the m.space.child state events
|
||||||
*/
|
*/
|
||||||
suspend fun querySpaceChildren(spaceId: String,
|
suspend fun querySpaceChildren(spaceId: String,
|
||||||
suggestedOnly: Boolean? = null,
|
suggestedOnly: Boolean? = null,
|
||||||
limit: Int? = null,
|
limit: Int? = null,
|
||||||
from: String? = null,
|
from: String? = null,
|
||||||
// when paginating, pass back the m.space.child state events
|
|
||||||
knownStateList: List<Event>? = null): SpaceHierarchyData
|
knownStateList: List<Event>? = null): SpaceHierarchyData
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -98,7 +101,10 @@ interface SpaceService {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Let this room declare that it has a parent.
|
* Let this room declare that it has a parent.
|
||||||
|
* @param childRoomId the space to set as a child
|
||||||
|
* @param parentSpaceId the parentId which will be set
|
||||||
* @param canonical true if it should be the main parent of this room
|
* @param canonical true if it should be the main parent of this room
|
||||||
|
* @param viaServers list of candidate servers that can be used to set the parent
|
||||||
* In practice, well behaved rooms should only have one canonical parent, but given this is not enforced:
|
* 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.
|
* if multiple are present the client should select the one with the lowest room ID, as determined via a lexicographic utf-8 ordering.
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -65,7 +65,8 @@ interface WidgetPostAPIMediator {
|
||||||
/**
|
/**
|
||||||
* Send an object response.
|
* Send an object response.
|
||||||
*
|
*
|
||||||
* @param klass the class of the response
|
* @param T the generic type
|
||||||
|
* @param type the type of the response
|
||||||
* @param response the response
|
* @param response the response
|
||||||
* @param eventData the modular data
|
* @param eventData the modular data
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -111,8 +111,8 @@ interface WidgetService {
|
||||||
/**
|
/**
|
||||||
* Deactivate a widget in a room. It makes sure you have the rights to handle this.
|
* Deactivate a widget in a room. It makes sure you have the rights to handle this.
|
||||||
*
|
*
|
||||||
* @param roomId: the room where you want to deactivate the widget.
|
* @param roomId the room where you want to deactivate the widget.
|
||||||
* @param widgetId: the widget to deactivate.
|
* @param widgetId the widget to deactivate.
|
||||||
*/
|
*/
|
||||||
suspend fun destroyRoomWidget(roomId: String, widgetId: String)
|
suspend fun destroyRoomWidget(roomId: String, widgetId: String)
|
||||||
|
|
||||||
|
|
|
@ -46,6 +46,7 @@ internal class CryptoSessionInfoProvider @Inject constructor(
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
* @param roomId the room Id
|
||||||
* @param allActive if true return joined as well as invited, if false, only joined
|
* @param allActive if true return joined as well as invited, if false, only joined
|
||||||
*/
|
*/
|
||||||
fun getRoomUserIds(roomId: String, allActive: Boolean): List<String> {
|
fun getRoomUserIds(roomId: String, allActive: Boolean): List<String> {
|
||||||
|
|
|
@ -905,6 +905,7 @@ internal class DefaultCryptoService @Inject constructor(
|
||||||
/**
|
/**
|
||||||
* Handle an m.room.encryption event.
|
* Handle an m.room.encryption event.
|
||||||
*
|
*
|
||||||
|
* @param roomId the room Id
|
||||||
* @param event the encryption event.
|
* @param event the encryption event.
|
||||||
*/
|
*/
|
||||||
private fun onRoomEncryptionEvent(roomId: String, event: Event) {
|
private fun onRoomEncryptionEvent(roomId: String, event: Event) {
|
||||||
|
@ -928,6 +929,7 @@ internal class DefaultCryptoService @Inject constructor(
|
||||||
/**
|
/**
|
||||||
* Handle a change in the membership state of a member of a room.
|
* Handle a change in the membership state of a member of a room.
|
||||||
*
|
*
|
||||||
|
* @param roomId the room Id
|
||||||
* @param event the membership event causing the change
|
* @param event the membership event causing the change
|
||||||
*/
|
*/
|
||||||
private fun onRoomMembershipEvent(roomId: String, event: Event) {
|
private fun onRoomMembershipEvent(roomId: String, event: Event) {
|
||||||
|
|
|
@ -29,6 +29,7 @@ import org.matrix.android.sdk.api.session.crypto.model.MXUsersDevicesMap
|
||||||
import org.matrix.android.sdk.api.session.events.model.Event
|
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.EventType
|
||||||
import org.matrix.android.sdk.api.session.events.model.content.OlmEventContent
|
import org.matrix.android.sdk.api.session.events.model.content.OlmEventContent
|
||||||
|
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.events.model.toModel
|
||||||
import org.matrix.android.sdk.internal.crypto.actions.EnsureOlmSessionsForDevicesAction
|
import org.matrix.android.sdk.internal.crypto.actions.EnsureOlmSessionsForDevicesAction
|
||||||
import org.matrix.android.sdk.internal.crypto.actions.MessageEncrypter
|
import org.matrix.android.sdk.internal.crypto.actions.MessageEncrypter
|
||||||
|
@ -42,7 +43,7 @@ import javax.inject.Inject
|
||||||
|
|
||||||
private const val SEND_TO_DEVICE_RETRY_COUNT = 3
|
private const val SEND_TO_DEVICE_RETRY_COUNT = 3
|
||||||
|
|
||||||
private val loggerTag = LoggerTag("CryptoSyncHandler", LoggerTag.CRYPTO)
|
private val loggerTag = LoggerTag("EventDecryptor", LoggerTag.CRYPTO)
|
||||||
|
|
||||||
@SessionScope
|
@SessionScope
|
||||||
internal class EventDecryptor @Inject constructor(
|
internal class EventDecryptor @Inject constructor(
|
||||||
|
@ -110,6 +111,16 @@ internal class EventDecryptor @Inject constructor(
|
||||||
if (eventContent == null) {
|
if (eventContent == null) {
|
||||||
Timber.tag(loggerTag.value).e("decryptEvent : empty event content")
|
Timber.tag(loggerTag.value).e("decryptEvent : empty event content")
|
||||||
throw MXCryptoError.Base(MXCryptoError.ErrorType.BAD_ENCRYPTED_MESSAGE, MXCryptoError.BAD_ENCRYPTED_MESSAGE_REASON)
|
throw MXCryptoError.Base(MXCryptoError.ErrorType.BAD_ENCRYPTED_MESSAGE, MXCryptoError.BAD_ENCRYPTED_MESSAGE_REASON)
|
||||||
|
} else if (event.isRedacted()) {
|
||||||
|
// we shouldn't attempt to decrypt a redacted event because the content is cleared and decryption will fail because of null algorithm
|
||||||
|
return MXEventDecryptionResult(
|
||||||
|
clearEvent = mapOf(
|
||||||
|
"room_id" to event.roomId.orEmpty(),
|
||||||
|
"type" to EventType.MESSAGE,
|
||||||
|
"content" to emptyMap<String, Any>(),
|
||||||
|
"unsigned" to event.unsignedData.toContent()
|
||||||
|
)
|
||||||
|
)
|
||||||
} else {
|
} else {
|
||||||
val algorithm = eventContent["algorithm"]?.toString()
|
val algorithm = eventContent["algorithm"]?.toString()
|
||||||
val alg = roomDecryptorProvider.getOrCreateRoomDecryptor(event.roomId, algorithm)
|
val alg = roomDecryptorProvider.getOrCreateRoomDecryptor(event.roomId, algorithm)
|
||||||
|
|
|
@ -140,7 +140,7 @@ internal object MXMegolmExportEncryption {
|
||||||
*
|
*
|
||||||
* @param data the data to encrypt.
|
* @param data the data to encrypt.
|
||||||
* @param password the password
|
* @param password the password
|
||||||
* @param kdf_rounds the iteration count
|
* @param kdfRounds the iteration count
|
||||||
* @return the encrypted data
|
* @return the encrypted data
|
||||||
* @throws Exception the failure reason
|
* @throws Exception the failure reason
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -96,8 +96,9 @@ internal class MXOlmDevice @Inject constructor(
|
||||||
// So, store these message indexes per timeline id.
|
// So, store these message indexes per timeline id.
|
||||||
//
|
//
|
||||||
// The first level keys are timeline ids.
|
// The first level keys are timeline ids.
|
||||||
// The second level keys are strings of form "<senderKey>|<session_id>|<message_index>"
|
// The second level values is a Map that represents:
|
||||||
private val inboundGroupSessionMessageIndexes: MutableMap<String, MutableSet<String>> = HashMap()
|
// "<senderKey>|<session_id>|<roomId>|<message_index>" --> eventId
|
||||||
|
private val inboundGroupSessionMessageIndexes: MutableMap<String, MutableMap<String, String>> = HashMap()
|
||||||
|
|
||||||
init {
|
init {
|
||||||
// Retrieve the account from the store
|
// Retrieve the account from the store
|
||||||
|
@ -429,8 +430,8 @@ internal class MXOlmDevice @Inject constructor(
|
||||||
*
|
*
|
||||||
* @param ciphertext the base64-encoded body from the received message.
|
* @param ciphertext the base64-encoded body from the received message.
|
||||||
* @param messageType message_type field from the received message.
|
* @param messageType message_type field from the received message.
|
||||||
* @param theirDeviceIdentityKey the Curve25519 identity key for the remote device.
|
|
||||||
* @param sessionId the id of the active session.
|
* @param sessionId the id of the active session.
|
||||||
|
* @param theirDeviceIdentityKey the Curve25519 identity key for the remote device.
|
||||||
* @return the decrypted payload.
|
* @return the decrypted payload.
|
||||||
*/
|
*/
|
||||||
@kotlin.jvm.Throws
|
@kotlin.jvm.Throws
|
||||||
|
@ -755,23 +756,29 @@ internal class MXOlmDevice @Inject constructor(
|
||||||
* @param body the base64-encoded body of the encrypted message.
|
* @param body the base64-encoded body of the encrypted message.
|
||||||
* @param roomId the room in which the message was received.
|
* @param roomId the room in which the message was received.
|
||||||
* @param timeline the id of the timeline where the event is decrypted. It is used to prevent replay attack.
|
* @param timeline the id of the timeline where the event is decrypted. It is used to prevent replay attack.
|
||||||
|
* @param eventId the eventId of the message that will be decrypted
|
||||||
* @param sessionId the session identifier.
|
* @param sessionId the session identifier.
|
||||||
* @param senderKey the base64-encoded curve25519 key of the sender.
|
* @param senderKey the base64-encoded curve25519 key of the sender.
|
||||||
* @return the decrypting result. Nil if the sessionId is unknown.
|
* @return the decrypting result. Null if the sessionId is unknown.
|
||||||
*/
|
*/
|
||||||
@Throws(MXCryptoError::class)
|
@Throws(MXCryptoError::class)
|
||||||
suspend fun decryptGroupMessage(body: String,
|
suspend fun decryptGroupMessage(body: String,
|
||||||
roomId: String,
|
roomId: String,
|
||||||
timeline: String?,
|
timeline: String?,
|
||||||
|
eventId: String,
|
||||||
sessionId: String,
|
sessionId: String,
|
||||||
senderKey: String): OlmDecryptionResult {
|
senderKey: String): OlmDecryptionResult {
|
||||||
val sessionHolder = getInboundGroupSession(sessionId, senderKey, roomId)
|
val sessionHolder = getInboundGroupSession(sessionId, senderKey, roomId)
|
||||||
val wrapper = sessionHolder.wrapper
|
val wrapper = sessionHolder.wrapper
|
||||||
val inboundGroupSession = wrapper.olmInboundGroupSession
|
val inboundGroupSession = wrapper.olmInboundGroupSession
|
||||||
?: throw MXCryptoError.Base(MXCryptoError.ErrorType.UNABLE_TO_DECRYPT, "Session is null")
|
?: throw MXCryptoError.Base(MXCryptoError.ErrorType.UNABLE_TO_DECRYPT, "Session is null")
|
||||||
|
if (roomId != wrapper.roomId) {
|
||||||
// Check that the room id matches the original one for the session. This stops
|
// Check that the room id matches the original one for the session. This stops
|
||||||
// the HS pretending a message was targeting a different room.
|
// the HS pretending a message was targeting a different room.
|
||||||
if (roomId == wrapper.roomId) {
|
val reason = String.format(MXCryptoError.INBOUND_SESSION_MISMATCH_ROOM_ID_REASON, roomId, wrapper.roomId)
|
||||||
|
Timber.tag(loggerTag.value).e("## decryptGroupMessage() : $reason")
|
||||||
|
throw MXCryptoError.Base(MXCryptoError.ErrorType.INBOUND_SESSION_MISMATCH_ROOM_ID, reason)
|
||||||
|
}
|
||||||
val decryptResult = try {
|
val decryptResult = try {
|
||||||
sessionHolder.mutex.withLock {
|
sessionHolder.mutex.withLock {
|
||||||
inboundGroupSession.decryptMessage(body)
|
inboundGroupSession.decryptMessage(body)
|
||||||
|
@ -781,20 +788,24 @@ internal class MXOlmDevice @Inject constructor(
|
||||||
throw MXCryptoError.OlmError(e)
|
throw MXCryptoError.OlmError(e)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val messageIndexKey = senderKey + "|" + sessionId + "|" + roomId + "|" + decryptResult.mIndex
|
||||||
|
Timber.tag(loggerTag.value).v("##########################################################")
|
||||||
|
Timber.tag(loggerTag.value).v("## decryptGroupMessage() timeline: $timeline")
|
||||||
|
Timber.tag(loggerTag.value).v("## decryptGroupMessage() senderKey: $senderKey")
|
||||||
|
Timber.tag(loggerTag.value).v("## decryptGroupMessage() sessionId: $sessionId")
|
||||||
|
Timber.tag(loggerTag.value).v("## decryptGroupMessage() roomId: $roomId")
|
||||||
|
Timber.tag(loggerTag.value).v("## decryptGroupMessage() eventId: $eventId")
|
||||||
|
Timber.tag(loggerTag.value).v("## decryptGroupMessage() mIndex: ${decryptResult.mIndex}")
|
||||||
|
|
||||||
if (timeline?.isNotBlank() == true) {
|
if (timeline?.isNotBlank() == true) {
|
||||||
val timelineSet = inboundGroupSessionMessageIndexes.getOrPut(timeline) { mutableSetOf() }
|
val replayAttackMap = inboundGroupSessionMessageIndexes.getOrPut(timeline) { mutableMapOf() }
|
||||||
|
if (replayAttackMap.contains(messageIndexKey) && replayAttackMap[messageIndexKey] != eventId) {
|
||||||
val messageIndexKey = senderKey + "|" + sessionId + "|" + decryptResult.mIndex
|
|
||||||
|
|
||||||
if (timelineSet.contains(messageIndexKey)) {
|
|
||||||
val reason = String.format(MXCryptoError.DUPLICATE_MESSAGE_INDEX_REASON, decryptResult.mIndex)
|
val reason = String.format(MXCryptoError.DUPLICATE_MESSAGE_INDEX_REASON, decryptResult.mIndex)
|
||||||
Timber.tag(loggerTag.value).e("## decryptGroupMessage() timelineId=$timeline: $reason")
|
Timber.tag(loggerTag.value).e("## decryptGroupMessage() timelineId=$timeline: $reason")
|
||||||
throw MXCryptoError.Base(MXCryptoError.ErrorType.DUPLICATED_MESSAGE_INDEX, reason)
|
throw MXCryptoError.Base(MXCryptoError.ErrorType.DUPLICATED_MESSAGE_INDEX, reason)
|
||||||
}
|
}
|
||||||
|
replayAttackMap[messageIndexKey] = eventId
|
||||||
timelineSet.add(messageIndexKey)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
inboundGroupSessionStore.storeInBoundGroupSession(sessionHolder, sessionId, senderKey)
|
inboundGroupSessionStore.storeInBoundGroupSession(sessionHolder, sessionId, senderKey)
|
||||||
val payload = try {
|
val payload = try {
|
||||||
val adapter = MoshiProvider.providesMoshi().adapter<JsonDict>(JSON_DICT_PARAMETERIZED_TYPE)
|
val adapter = MoshiProvider.providesMoshi().adapter<JsonDict>(JSON_DICT_PARAMETERIZED_TYPE)
|
||||||
|
@ -811,11 +822,6 @@ internal class MXOlmDevice @Inject constructor(
|
||||||
senderKey,
|
senderKey,
|
||||||
wrapper.forwardingCurve25519KeyChain
|
wrapper.forwardingCurve25519KeyChain
|
||||||
)
|
)
|
||||||
} else {
|
|
||||||
val reason = String.format(MXCryptoError.INBOUND_SESSION_MISMATCH_ROOM_ID_REASON, roomId, wrapper.roomId)
|
|
||||||
Timber.tag(loggerTag.value).e("## decryptGroupMessage() : $reason")
|
|
||||||
throw MXCryptoError.Base(MXCryptoError.ErrorType.INBOUND_SESSION_MISMATCH_ROOM_ID, reason)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -873,9 +879,9 @@ internal class MXOlmDevice @Inject constructor(
|
||||||
* Extract an InboundGroupSession from the session store and do some check.
|
* Extract an InboundGroupSession from the session store and do some check.
|
||||||
* inboundGroupSessionWithIdError describes the failure reason.
|
* inboundGroupSessionWithIdError describes the failure reason.
|
||||||
*
|
*
|
||||||
* @param roomId the room where the session is used.
|
|
||||||
* @param sessionId the session identifier.
|
* @param sessionId the session identifier.
|
||||||
* @param senderKey the base64-encoded curve25519 key of the sender.
|
* @param senderKey the base64-encoded curve25519 key of the sender.
|
||||||
|
* @param roomId the room where the session is used.
|
||||||
* @return the inbound group session.
|
* @return the inbound group session.
|
||||||
*/
|
*/
|
||||||
fun getInboundGroupSession(sessionId: String?, senderKey: String?, roomId: String?): InboundGroupSessionHolder {
|
fun getInboundGroupSession(sessionId: String?, senderKey: String?, roomId: String?): InboundGroupSessionHolder {
|
||||||
|
|
|
@ -40,6 +40,7 @@ internal interface IMXDecrypting {
|
||||||
* Handle a key event.
|
* Handle a key event.
|
||||||
*
|
*
|
||||||
* @param event the key event.
|
* @param event the key event.
|
||||||
|
* @param defaultKeysBackupService the keys backup service
|
||||||
*/
|
*/
|
||||||
fun onRoomKeyEvent(event: Event, defaultKeysBackupService: DefaultKeysBackupService) {}
|
fun onRoomKeyEvent(event: Event, defaultKeysBackupService: DefaultKeysBackupService) {}
|
||||||
}
|
}
|
||||||
|
|
|
@ -38,7 +38,7 @@ internal interface IMXGroupEncryption {
|
||||||
* Re-shares a session key with devices if the key has already been
|
* Re-shares a session key with devices if the key has already been
|
||||||
* sent to them.
|
* sent to them.
|
||||||
*
|
*
|
||||||
* @param sessionId The id of the outbound session to share.
|
* @param groupSessionId The id of the outbound session to share.
|
||||||
* @param userId The id of the user who owns the target device.
|
* @param userId The id of the user who owns the target device.
|
||||||
* @param deviceId The id of the target device.
|
* @param deviceId The id of the target device.
|
||||||
* @param senderKey The key of the originating device for the session.
|
* @param senderKey The key of the originating device for the session.
|
||||||
|
|
|
@ -78,6 +78,7 @@ internal class MXMegolmDecryption(
|
||||||
encryptedEventContent.ciphertext,
|
encryptedEventContent.ciphertext,
|
||||||
event.roomId,
|
event.roomId,
|
||||||
timeline,
|
timeline,
|
||||||
|
eventId = event.eventId.orEmpty(),
|
||||||
encryptedEventContent.sessionId,
|
encryptedEventContent.sessionId,
|
||||||
encryptedEventContent.senderKey
|
encryptedEventContent.senderKey
|
||||||
)
|
)
|
||||||
|
@ -176,6 +177,7 @@ internal class MXMegolmDecryption(
|
||||||
* Handle a key event.
|
* Handle a key event.
|
||||||
*
|
*
|
||||||
* @param event the key event.
|
* @param event the key event.
|
||||||
|
* @param defaultKeysBackupService the keys backup service
|
||||||
*/
|
*/
|
||||||
override fun onRoomKeyEvent(event: Event, defaultKeysBackupService: DefaultKeysBackupService) {
|
override fun onRoomKeyEvent(event: Event, defaultKeysBackupService: DefaultKeysBackupService) {
|
||||||
Timber.tag(loggerTag.value).v("onRoomKeyEvent()")
|
Timber.tag(loggerTag.value).v("onRoomKeyEvent()")
|
||||||
|
|
|
@ -180,8 +180,8 @@ internal class MXOlmDecryption(
|
||||||
/**
|
/**
|
||||||
* Attempt to decrypt an Olm message.
|
* Attempt to decrypt an Olm message.
|
||||||
*
|
*
|
||||||
* @param theirDeviceIdentityKey the Curve25519 identity key of the sender.
|
|
||||||
* @param message message object, with 'type' and 'body' fields.
|
* @param message message object, with 'type' and 'body' fields.
|
||||||
|
* @param theirDeviceIdentityKey the Curve25519 identity key of the sender.
|
||||||
* @return payload, if decrypted successfully.
|
* @return payload, if decrypted successfully.
|
||||||
*/
|
*/
|
||||||
private suspend fun decryptMessage(message: JsonDict, theirDeviceIdentityKey: String): String? {
|
private suspend fun decryptMessage(message: JsonDict, theirDeviceIdentityKey: String): String? {
|
||||||
|
|
|
@ -103,7 +103,7 @@ internal interface CryptoApi {
|
||||||
* Claim one-time keys.
|
* Claim one-time keys.
|
||||||
* Doc: https://matrix.org/docs/spec/client_server/r0.4.0.html#post-matrix-client-r0-keys-claim
|
* Doc: https://matrix.org/docs/spec/client_server/r0.4.0.html#post-matrix-client-r0-keys-claim
|
||||||
*
|
*
|
||||||
* @param params the params.
|
* @param body the Json body.
|
||||||
*/
|
*/
|
||||||
@POST(NetworkConstants.URI_API_PREFIX_PATH_R0 + "keys/claim")
|
@POST(NetworkConstants.URI_API_PREFIX_PATH_R0 + "keys/claim")
|
||||||
suspend fun claimOneTimeKeysForUsersDevices(@Body body: KeysClaimBody): KeysClaimResponse
|
suspend fun claimOneTimeKeysForUsersDevices(@Body body: KeysClaimBody): KeysClaimResponse
|
||||||
|
|
|
@ -159,6 +159,7 @@ internal object MXEncryptedAttachments {
|
||||||
* Encrypt an attachment stream.
|
* Encrypt an attachment stream.
|
||||||
* DO NOT USE for big files, it will load all in memory
|
* DO NOT USE for big files, it will load all in memory
|
||||||
* @param attachmentStream the attachment stream. Will be closed after this method call.
|
* @param attachmentStream the attachment stream. Will be closed after this method call.
|
||||||
|
* @param clock a clock to retrieve current time
|
||||||
* @return the encryption file info
|
* @return the encryption file info
|
||||||
*/
|
*/
|
||||||
fun encryptAttachment(attachmentStream: InputStream, clock: Clock): EncryptionResult {
|
fun encryptAttachment(attachmentStream: InputStream, clock: Clock): EncryptionResult {
|
||||||
|
@ -232,6 +233,7 @@ internal object MXEncryptedAttachments {
|
||||||
* @param attachmentStream the attachment stream. Will be closed after this method call.
|
* @param attachmentStream the attachment stream. Will be closed after this method call.
|
||||||
* @param elementToDecrypt the elementToDecrypt info
|
* @param elementToDecrypt the elementToDecrypt info
|
||||||
* @param outputStream the outputStream where the decrypted attachment will be write.
|
* @param outputStream the outputStream where the decrypted attachment will be write.
|
||||||
|
* @param clock a clock to retrieve current time
|
||||||
* @return true in case of success, false in case of error
|
* @return true in case of success, false in case of error
|
||||||
*/
|
*/
|
||||||
fun decryptAttachment(attachmentStream: InputStream?,
|
fun decryptAttachment(attachmentStream: InputStream?,
|
||||||
|
|
|
@ -1105,6 +1105,7 @@ internal class DefaultKeysBackupService @Inject constructor(
|
||||||
*
|
*
|
||||||
* @param password the password.
|
* @param password the password.
|
||||||
* @param keysBackupData the backup and its auth data.
|
* @param keysBackupData the backup and its auth data.
|
||||||
|
* @param progressListener listener to track progress
|
||||||
*
|
*
|
||||||
* @return the recovery key if successful, null in other cases
|
* @return the recovery key if successful, null in other cases
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -44,6 +44,7 @@ internal data class GeneratePrivateKeyResult(
|
||||||
* Compute a private key from a password.
|
* Compute a private key from a password.
|
||||||
*
|
*
|
||||||
* @param password the password to use.
|
* @param password the password to use.
|
||||||
|
* @param progressListener a listener to track progress
|
||||||
*
|
*
|
||||||
* @return a {privateKey, salt, iterations} tuple.
|
* @return a {privateKey, salt, iterations} tuple.
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -72,7 +72,7 @@ internal interface RoomKeysApi {
|
||||||
*/
|
*/
|
||||||
@PUT(NetworkConstants.URI_API_PREFIX_PATH_UNSTABLE + "room_keys/version/{version}")
|
@PUT(NetworkConstants.URI_API_PREFIX_PATH_UNSTABLE + "room_keys/version/{version}")
|
||||||
suspend fun updateKeysBackupVersion(@Path("version") version: String,
|
suspend fun updateKeysBackupVersion(@Path("version") version: String,
|
||||||
@Body keysBackupVersionBody: UpdateKeysBackupVersionBody)
|
@Body updateKeysBackupVersionBody: UpdateKeysBackupVersionBody)
|
||||||
|
|
||||||
/* ==========================================================================================
|
/* ==========================================================================================
|
||||||
* Storing keys
|
* Storing keys
|
||||||
|
|
|
@ -164,16 +164,14 @@ internal interface IMXCryptoStore {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Store the end to end account for the logged-in user.
|
* Store the end to end account for the logged-in user.
|
||||||
*
|
|
||||||
* @param account the account to save
|
|
||||||
*/
|
*/
|
||||||
fun saveOlmAccount()
|
fun saveOlmAccount()
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Retrieve a device for a user.
|
* Retrieve a device for a user.
|
||||||
*
|
*
|
||||||
* @param deviceId the device id.
|
|
||||||
* @param userId the user's id.
|
* @param userId the user's id.
|
||||||
|
* @param deviceId the device id.
|
||||||
* @return the device
|
* @return the device
|
||||||
*/
|
*/
|
||||||
fun getUserDevice(userId: String, deviceId: String): CryptoDeviceInfo?
|
fun getUserDevice(userId: String, deviceId: String): CryptoDeviceInfo?
|
||||||
|
@ -331,7 +329,7 @@ internal interface IMXCryptoStore {
|
||||||
/**
|
/**
|
||||||
* Mark inbound group sessions as backed up on the user homeserver.
|
* Mark inbound group sessions as backed up on the user homeserver.
|
||||||
*
|
*
|
||||||
* @param sessions the sessions
|
* @param olmInboundGroupSessionWrappers the sessions
|
||||||
*/
|
*/
|
||||||
fun markBackupDoneForInboundGroupSessions(olmInboundGroupSessionWrappers: List<OlmInboundGroupSessionWrapper2>)
|
fun markBackupDoneForInboundGroupSessions(olmInboundGroupSessionWrappers: List<OlmInboundGroupSessionWrapper2>)
|
||||||
|
|
||||||
|
@ -380,7 +378,9 @@ internal interface IMXCryptoStore {
|
||||||
/**
|
/**
|
||||||
* Look for an existing outgoing room key request, and if none is found, add a new one.
|
* Look for an existing outgoing room key request, and if none is found, add a new one.
|
||||||
*
|
*
|
||||||
* @param request the request
|
* @param requestBody the request
|
||||||
|
* @param recipients list of recipients
|
||||||
|
* @param fromIndex start index
|
||||||
* @return either the same instance as passed in, or the existing one.
|
* @return either the same instance as passed in, or the existing one.
|
||||||
*/
|
*/
|
||||||
fun getOrAddOutgoingRoomKeyRequest(requestBody: RoomKeyRequestBody, recipients: Map<String, List<String>>, fromIndex: Int): OutgoingKeyRequest
|
fun getOrAddOutgoingRoomKeyRequest(requestBody: RoomKeyRequestBody, recipients: Map<String, List<String>>, fromIndex: Int): OutgoingKeyRequest
|
||||||
|
|
|
@ -35,6 +35,11 @@ internal interface VerificationTransport {
|
||||||
onDone: (() -> Unit)?)
|
onDone: (() -> Unit)?)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
* @param supportedMethods list of supported method by this client
|
||||||
|
* @param localId a local Id
|
||||||
|
* @param otherUserId the user id to send the verification request to
|
||||||
|
* @param roomId a room Id to use to send verification message
|
||||||
|
* @param toDevices list of device Ids
|
||||||
* @param callback will be called with eventId and ValidVerificationInfoRequest in case of success
|
* @param callback will be called with eventId and ValidVerificationInfoRequest in case of success
|
||||||
*/
|
*/
|
||||||
fun sendVerificationRequest(supportedMethods: List<String>,
|
fun sendVerificationRequest(supportedMethods: List<String>,
|
||||||
|
|
|
@ -96,7 +96,9 @@ internal fun EventEntity.markEventAsRoot(
|
||||||
/**
|
/**
|
||||||
* Count the number of threads for the provided root thread eventId, and finds the latest event message.
|
* Count the number of threads for the provided root thread eventId, and finds the latest event message.
|
||||||
* Note: Redactions are handled by RedactionEventProcessor.
|
* Note: Redactions are handled by RedactionEventProcessor.
|
||||||
|
* @param realm the realm database
|
||||||
* @param rootThreadEventId The root eventId that will find the number of threads
|
* @param rootThreadEventId The root eventId that will find the number of threads
|
||||||
|
* @param chunkEntity the chunk entity
|
||||||
* @return A ThreadSummary containing the counted threads and the latest event message
|
* @return A ThreadSummary containing the counted threads and the latest event message
|
||||||
*/
|
*/
|
||||||
internal fun EventEntity.threadSummaryInThread(realm: Realm, rootThreadEventId: String, chunkEntity: ChunkEntity?): Summary {
|
internal fun EventEntity.threadSummaryInThread(realm: Realm, rootThreadEventId: String, chunkEntity: ChunkEntity?): Summary {
|
||||||
|
@ -184,6 +186,7 @@ private fun findLatestSortedChunkEvent(chunk: ChunkEntity, rootThreadEventId: St
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Find all TimelineEventEntity that are root threads for the specified room.
|
* Find all TimelineEventEntity that are root threads for the specified room.
|
||||||
|
* @param realm the realm instance
|
||||||
* @param roomId The room that all stored root threads will be returned
|
* @param roomId The room that all stored root threads will be returned
|
||||||
*/
|
*/
|
||||||
internal fun TimelineEventEntity.Companion.findAllThreadsForRoomId(realm: Realm, roomId: String): RealmQuery<TimelineEventEntity> =
|
internal fun TimelineEventEntity.Companion.findAllThreadsForRoomId(realm: Realm, roomId: String): RealmQuery<TimelineEventEntity> =
|
||||||
|
@ -218,6 +221,7 @@ internal fun List<TimelineEvent>.mapEventsWithEdition(realm: Realm, roomId: Stri
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns a list of all the marked unread threads that exists for the specified room.
|
* Returns a list of all the marked unread threads that exists for the specified room.
|
||||||
|
* @param realm the realm instance
|
||||||
* @param roomId The roomId that the user is currently in
|
* @param roomId The roomId that the user is currently in
|
||||||
*/
|
*/
|
||||||
internal fun TimelineEventEntity.Companion.findAllLocalThreadNotificationsForRoomId(realm: Realm, roomId: String): RealmQuery<TimelineEventEntity> =
|
internal fun TimelineEventEntity.Companion.findAllLocalThreadNotificationsForRoomId(realm: Realm, roomId: String): RealmQuery<TimelineEventEntity> =
|
||||||
|
@ -232,6 +236,7 @@ internal fun TimelineEventEntity.Companion.findAllLocalThreadNotificationsForRoo
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns whether or not the given user is participating in a current thread.
|
* Returns whether or not the given user is participating in a current thread.
|
||||||
|
* @param realm the realm instance
|
||||||
* @param roomId the room that the thread exists
|
* @param roomId the room that the thread exists
|
||||||
* @param rootThreadEventId the thread that the search will be done
|
* @param rootThreadEventId the thread that the search will be done
|
||||||
* @param senderId the user that will try to find participation
|
* @param senderId the user that will try to find participation
|
||||||
|
@ -247,6 +252,7 @@ internal fun TimelineEventEntity.Companion.isUserParticipatingInThread(realm: Re
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns whether or not the given user is mentioned in a current thread.
|
* Returns whether or not the given user is mentioned in a current thread.
|
||||||
|
* @param realm the realm instance
|
||||||
* @param roomId the room that the thread exists
|
* @param roomId the room that the thread exists
|
||||||
* @param rootThreadEventId the thread that the search will be done
|
* @param rootThreadEventId the thread that the search will be done
|
||||||
* @param userId the user that will try to find if there is a mention
|
* @param userId the user that will try to find if there is a mention
|
||||||
|
|
|
@ -303,6 +303,7 @@ private fun getLatestEvent(rootThreadEvent: Event): Event? {
|
||||||
/**
|
/**
|
||||||
* Find all ThreadSummaryEntity for the specified roomId, sorted by origin server.
|
* Find all ThreadSummaryEntity for the specified roomId, sorted by origin server.
|
||||||
* note: Sorting cannot be provided by server, so we have to use that unstable property.
|
* note: Sorting cannot be provided by server, so we have to use that unstable property.
|
||||||
|
* @param realm the realm instance
|
||||||
* @param roomId The id of the room
|
* @param roomId The id of the room
|
||||||
*/
|
*/
|
||||||
internal fun ThreadSummaryEntity.Companion.findAllThreadsForRoomId(realm: Realm, roomId: String): RealmQuery<ThreadSummaryEntity> =
|
internal fun ThreadSummaryEntity.Companion.findAllThreadsForRoomId(realm: Realm, roomId: String): RealmQuery<ThreadSummaryEntity> =
|
||||||
|
|
|
@ -31,6 +31,9 @@ internal open class LiveLocationShareAggregatedSummaryEntity(
|
||||||
|
|
||||||
var roomId: String = "",
|
var roomId: String = "",
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Indicate whether the live is currently running.
|
||||||
|
*/
|
||||||
var isActive: Boolean? = null,
|
var isActive: Boolean? = null,
|
||||||
|
|
||||||
var endOfLiveTimestampMillis: Long? = null,
|
var endOfLiveTimestampMillis: Long? = null,
|
||||||
|
|
|
@ -55,3 +55,11 @@ internal fun LiveLocationShareAggregatedSummaryEntity.Companion.getOrCreate(
|
||||||
return LiveLocationShareAggregatedSummaryEntity.where(realm, roomId, eventId).findFirst()
|
return LiveLocationShareAggregatedSummaryEntity.where(realm, roomId, eventId).findFirst()
|
||||||
?: LiveLocationShareAggregatedSummaryEntity.create(realm, roomId, eventId)
|
?: LiveLocationShareAggregatedSummaryEntity.create(realm, roomId, eventId)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
internal fun LiveLocationShareAggregatedSummaryEntity.Companion.get(
|
||||||
|
realm: Realm,
|
||||||
|
roomId: String,
|
||||||
|
eventId: String,
|
||||||
|
): LiveLocationShareAggregatedSummaryEntity? {
|
||||||
|
return LiveLocationShareAggregatedSummaryEntity.where(realm, roomId, eventId).findFirst()
|
||||||
|
}
|
||||||
|
|
|
@ -32,6 +32,7 @@ import java.io.IOException
|
||||||
* Execute a request from the requestBlock and handle some of the Exception it could generate
|
* Execute a request from the requestBlock and handle some of the Exception it could generate
|
||||||
* Ref: https://github.com/matrix-org/matrix-js-sdk/blob/develop/src/scheduler.js#L138-L175
|
* Ref: https://github.com/matrix-org/matrix-js-sdk/blob/develop/src/scheduler.js#L138-L175
|
||||||
*
|
*
|
||||||
|
* @param DATA type of data return by the [requestBlock]
|
||||||
* @param globalErrorReceiver will be use to notify error such as invalid token error. See [GlobalError]
|
* @param globalErrorReceiver will be use to notify error such as invalid token error. See [GlobalError]
|
||||||
* @param canRetry if set to true, the request will be executed again in case of error, after a delay
|
* @param canRetry if set to true, the request will be executed again in case of error, after a delay
|
||||||
* @param maxDelayBeforeRetry the max delay to wait before a retry. Note that in the case of a 429, if the provided delay exceeds this value, the error will
|
* @param maxDelayBeforeRetry the max delay to wait before a retry. Note that in the case of a 429, if the provided delay exceeds this value, the error will
|
||||||
|
|
|
@ -119,9 +119,11 @@ internal class RuntimeJsonAdapterFactory<T>(
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
/**
|
/**
|
||||||
|
* @param T the generic type to pass to [RuntimeJsonAdapterFactory]
|
||||||
* @param baseType The base type for which this factory will create adapters. Cannot be Object.
|
* @param baseType The base type for which this factory will create adapters. Cannot be Object.
|
||||||
* @param labelKey The key in the JSON object whose value determines the type to which to map the
|
* @param labelKey The key in the JSON object whose value determines the type to which to map the
|
||||||
* JSON object.
|
* JSON object.
|
||||||
|
* @param fallbackType alternative Type to try in case of the serialization fails
|
||||||
*/
|
*/
|
||||||
@CheckReturnValue
|
@CheckReturnValue
|
||||||
fun <T> of(baseType: Class<T>, labelKey: String, fallbackType: Class<out T>): RuntimeJsonAdapterFactory<T> {
|
fun <T> of(baseType: Class<T>, labelKey: String, fallbackType: Class<out T>): RuntimeJsonAdapterFactory<T> {
|
||||||
|
|
|
@ -94,6 +94,7 @@ internal object CertUtil {
|
||||||
* Convert the fingerprint to an hexa string.
|
* Convert the fingerprint to an hexa string.
|
||||||
*
|
*
|
||||||
* @param fingerprint the fingerprint
|
* @param fingerprint the fingerprint
|
||||||
|
* @param sep the separator character, default to space
|
||||||
* @return the hexa string.
|
* @return the hexa string.
|
||||||
*/
|
*/
|
||||||
fun fingerprintToHexString(fingerprint: ByteArray, sep: Char = ' '): String {
|
fun fingerprintToHexString(fingerprint: ByteArray, sep: Char = ' '): String {
|
||||||
|
|
|
@ -24,11 +24,9 @@ import javax.net.ssl.X509TrustManager
|
||||||
/**
|
/**
|
||||||
* Implements a TrustManager that checks Certificates against an explicit list of known
|
* Implements a TrustManager that checks Certificates against an explicit list of known
|
||||||
* fingerprints.
|
* fingerprints.
|
||||||
*/
|
*
|
||||||
|
* @property fingerprints Not empty array of SHA256 cert fingerprints
|
||||||
/**
|
* @property defaultTrustManager Optional trust manager to fall back on if cert does not match
|
||||||
* @param fingerprints Not empty array of SHA256 cert fingerprints
|
|
||||||
* @param defaultTrustManager Optional trust manager to fall back on if cert does not match
|
|
||||||
* any of the fingerprints. Can be null.
|
* any of the fingerprints. Can be null.
|
||||||
*/
|
*/
|
||||||
internal class PinnedTrustManager(private val fingerprints: List<Fingerprint>,
|
internal class PinnedTrustManager(private val fingerprints: List<Fingerprint>,
|
||||||
|
|
|
@ -28,11 +28,9 @@ import javax.net.ssl.X509ExtendedTrustManager
|
||||||
/**
|
/**
|
||||||
* Implements a TrustManager that checks Certificates against an explicit list of known
|
* Implements a TrustManager that checks Certificates against an explicit list of known
|
||||||
* fingerprints.
|
* fingerprints.
|
||||||
*/
|
*
|
||||||
|
* @property fingerprints An array of SHA256 cert fingerprints
|
||||||
/**
|
* @property defaultTrustManager Optional trust manager to fall back on if cert does not match
|
||||||
* @param fingerprints An array of SHA256 cert fingerprints
|
|
||||||
* @param defaultTrustManager Optional trust manager to fall back on if cert does not match
|
|
||||||
* any of the fingerprints. Can be null.
|
* any of the fingerprints. Can be null.
|
||||||
*/
|
*/
|
||||||
@RequiresApi(Build.VERSION_CODES.N)
|
@RequiresApi(Build.VERSION_CODES.N)
|
||||||
|
|
|
@ -46,6 +46,7 @@ import org.matrix.android.sdk.internal.session.profile.ProfileModule
|
||||||
import org.matrix.android.sdk.internal.session.pushers.AddPusherWorker
|
import org.matrix.android.sdk.internal.session.pushers.AddPusherWorker
|
||||||
import org.matrix.android.sdk.internal.session.pushers.PushersModule
|
import org.matrix.android.sdk.internal.session.pushers.PushersModule
|
||||||
import org.matrix.android.sdk.internal.session.room.RoomModule
|
import org.matrix.android.sdk.internal.session.room.RoomModule
|
||||||
|
import org.matrix.android.sdk.internal.session.room.aggregation.livelocation.DeactivateLiveLocationShareWorker
|
||||||
import org.matrix.android.sdk.internal.session.room.send.MultipleEventSendingDispatcherWorker
|
import org.matrix.android.sdk.internal.session.room.send.MultipleEventSendingDispatcherWorker
|
||||||
import org.matrix.android.sdk.internal.session.room.send.RedactEventWorker
|
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.room.send.SendEventWorker
|
||||||
|
@ -131,6 +132,8 @@ internal interface SessionComponent {
|
||||||
|
|
||||||
fun inject(worker: UpdateTrustWorker)
|
fun inject(worker: UpdateTrustWorker)
|
||||||
|
|
||||||
|
fun inject(worker: DeactivateLiveLocationShareWorker)
|
||||||
|
|
||||||
@Component.Factory
|
@Component.Factory
|
||||||
interface Factory {
|
interface Factory {
|
||||||
fun create(
|
fun create(
|
||||||
|
|
|
@ -87,6 +87,8 @@ import org.matrix.android.sdk.internal.session.integrationmanager.IntegrationMan
|
||||||
import org.matrix.android.sdk.internal.session.openid.DefaultOpenIdService
|
import org.matrix.android.sdk.internal.session.openid.DefaultOpenIdService
|
||||||
import org.matrix.android.sdk.internal.session.permalinks.DefaultPermalinkService
|
import org.matrix.android.sdk.internal.session.permalinks.DefaultPermalinkService
|
||||||
import org.matrix.android.sdk.internal.session.room.EventRelationsAggregationProcessor
|
import org.matrix.android.sdk.internal.session.room.EventRelationsAggregationProcessor
|
||||||
|
import org.matrix.android.sdk.internal.session.room.aggregation.poll.DefaultPollAggregationProcessor
|
||||||
|
import org.matrix.android.sdk.internal.session.room.aggregation.poll.PollAggregationProcessor
|
||||||
import org.matrix.android.sdk.internal.session.room.create.RoomCreateEventProcessor
|
import org.matrix.android.sdk.internal.session.room.create.RoomCreateEventProcessor
|
||||||
import org.matrix.android.sdk.internal.session.room.prune.RedactionEventProcessor
|
import org.matrix.android.sdk.internal.session.room.prune.RedactionEventProcessor
|
||||||
import org.matrix.android.sdk.internal.session.room.send.queue.EventSenderProcessor
|
import org.matrix.android.sdk.internal.session.room.send.queue.EventSenderProcessor
|
||||||
|
@ -385,4 +387,7 @@ internal abstract class SessionModule {
|
||||||
|
|
||||||
@Binds
|
@Binds
|
||||||
abstract fun bindEventSenderProcessor(processor: EventSenderProcessorCoroutine): EventSenderProcessor
|
abstract fun bindEventSenderProcessor(processor: EventSenderProcessorCoroutine): EventSenderProcessor
|
||||||
|
|
||||||
|
@Binds
|
||||||
|
abstract fun bindPollAggregationProcessor(processor: DefaultPollAggregationProcessor): PollAggregationProcessor
|
||||||
}
|
}
|
||||||
|
|
|
@ -55,6 +55,7 @@ internal interface DirectoryAPI {
|
||||||
/**
|
/**
|
||||||
* Add alias to the room.
|
* Add alias to the room.
|
||||||
* @param roomAlias the room alias.
|
* @param roomAlias the room alias.
|
||||||
|
* @param body the Json body
|
||||||
*/
|
*/
|
||||||
@PUT(NetworkConstants.URI_API_PREFIX_PATH_R0 + "directory/room/{roomAlias}")
|
@PUT(NetworkConstants.URI_API_PREFIX_PATH_R0 + "directory/room/{roomAlias}")
|
||||||
suspend fun addRoomAlias(@Path("roomAlias") roomAlias: String,
|
suspend fun addRoomAlias(@Path("roomAlias") roomAlias: String,
|
||||||
|
|
|
@ -32,6 +32,7 @@ internal interface OpenIdAPI {
|
||||||
* Ref: https://matrix.org/docs/spec/client_server/latest#post-matrix-client-r0-user-userid-openid-request-token
|
* Ref: https://matrix.org/docs/spec/client_server/latest#post-matrix-client-r0-user-userid-openid-request-token
|
||||||
*
|
*
|
||||||
* @param userId the user id
|
* @param userId the user id
|
||||||
|
* @param body an empty json body
|
||||||
*/
|
*/
|
||||||
@POST(NetworkConstants.URI_API_PREFIX_PATH_R0 + "user/{userId}/openid/request_token")
|
@POST(NetworkConstants.URI_API_PREFIX_PATH_R0 + "user/{userId}/openid/request_token")
|
||||||
suspend fun openIdToken(@Path("userId") userId: String,
|
suspend fun openIdToken(@Path("userId") userId: String,
|
||||||
|
|
|
@ -35,7 +35,7 @@ internal interface PushRulesApi {
|
||||||
*
|
*
|
||||||
* @param kind the notification kind (sender, room...)
|
* @param kind the notification kind (sender, room...)
|
||||||
* @param ruleId the ruleId
|
* @param ruleId the ruleId
|
||||||
* @param enable the new enable status
|
* @param enabledBody the new enable status
|
||||||
*/
|
*/
|
||||||
@PUT(NetworkConstants.URI_API_PREFIX_PATH_R0 + "pushrules/global/{kind}/{ruleId}/enabled")
|
@PUT(NetworkConstants.URI_API_PREFIX_PATH_R0 + "pushrules/global/{kind}/{ruleId}/enabled")
|
||||||
suspend fun updateEnableRuleStatus(@Path("kind") kind: String,
|
suspend fun updateEnableRuleStatus(@Path("kind") kind: String,
|
||||||
|
|
|
@ -16,7 +16,6 @@
|
||||||
package org.matrix.android.sdk.internal.session.room
|
package org.matrix.android.sdk.internal.session.room
|
||||||
|
|
||||||
import io.realm.Realm
|
import io.realm.Realm
|
||||||
import org.matrix.android.sdk.api.extensions.orFalse
|
|
||||||
import org.matrix.android.sdk.api.query.QueryStringValue
|
import org.matrix.android.sdk.api.query.QueryStringValue
|
||||||
import org.matrix.android.sdk.api.session.crypto.verification.VerificationState
|
import org.matrix.android.sdk.api.session.crypto.verification.VerificationState
|
||||||
import org.matrix.android.sdk.api.session.events.model.AggregatedAnnotation
|
import org.matrix.android.sdk.api.session.events.model.AggregatedAnnotation
|
||||||
|
@ -28,23 +27,16 @@ import org.matrix.android.sdk.api.session.events.model.content.EncryptedEventCon
|
||||||
import org.matrix.android.sdk.api.session.events.model.getRelationContent
|
import org.matrix.android.sdk.api.session.events.model.getRelationContent
|
||||||
import org.matrix.android.sdk.api.session.events.model.toContent
|
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.events.model.toModel
|
||||||
import org.matrix.android.sdk.api.session.room.getTimelineEvent
|
|
||||||
import org.matrix.android.sdk.api.session.room.model.PollSummaryContent
|
|
||||||
import org.matrix.android.sdk.api.session.room.model.PowerLevelsContent
|
import org.matrix.android.sdk.api.session.room.model.PowerLevelsContent
|
||||||
import org.matrix.android.sdk.api.session.room.model.ReferencesAggregatedContent
|
import org.matrix.android.sdk.api.session.room.model.ReferencesAggregatedContent
|
||||||
import org.matrix.android.sdk.api.session.room.model.VoteInfo
|
|
||||||
import org.matrix.android.sdk.api.session.room.model.VoteSummary
|
|
||||||
import org.matrix.android.sdk.api.session.room.model.message.MessageBeaconInfoContent
|
import org.matrix.android.sdk.api.session.room.model.message.MessageBeaconInfoContent
|
||||||
import org.matrix.android.sdk.api.session.room.model.message.MessageBeaconLocationDataContent
|
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.MessageEndPollContent
|
|
||||||
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.MessagePollResponseContent
|
import org.matrix.android.sdk.api.session.room.model.message.MessagePollResponseContent
|
||||||
import org.matrix.android.sdk.api.session.room.model.message.MessageRelationContent
|
import org.matrix.android.sdk.api.session.room.model.message.MessageRelationContent
|
||||||
import org.matrix.android.sdk.api.session.room.model.relation.ReactionContent
|
import org.matrix.android.sdk.api.session.room.model.relation.ReactionContent
|
||||||
import org.matrix.android.sdk.api.session.room.powerlevels.PowerLevelsHelper
|
import org.matrix.android.sdk.api.session.room.powerlevels.PowerLevelsHelper
|
||||||
import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent
|
|
||||||
import org.matrix.android.sdk.api.session.room.timeline.getLastMessageContent
|
|
||||||
import org.matrix.android.sdk.internal.SessionManager
|
import org.matrix.android.sdk.internal.SessionManager
|
||||||
import org.matrix.android.sdk.internal.crypto.verification.toState
|
import org.matrix.android.sdk.internal.crypto.verification.toState
|
||||||
import org.matrix.android.sdk.internal.database.helper.findRootThreadEvent
|
import org.matrix.android.sdk.internal.database.helper.findRootThreadEvent
|
||||||
|
@ -55,7 +47,6 @@ import org.matrix.android.sdk.internal.database.model.EditionOfEvent
|
||||||
import org.matrix.android.sdk.internal.database.model.EventAnnotationsSummaryEntity
|
import org.matrix.android.sdk.internal.database.model.EventAnnotationsSummaryEntity
|
||||||
import org.matrix.android.sdk.internal.database.model.EventEntity
|
import org.matrix.android.sdk.internal.database.model.EventEntity
|
||||||
import org.matrix.android.sdk.internal.database.model.EventInsertType
|
import org.matrix.android.sdk.internal.database.model.EventInsertType
|
||||||
import org.matrix.android.sdk.internal.database.model.PollResponseAggregatedSummaryEntity
|
|
||||||
import org.matrix.android.sdk.internal.database.model.ReactionAggregatedSummaryEntity
|
import org.matrix.android.sdk.internal.database.model.ReactionAggregatedSummaryEntity
|
||||||
import org.matrix.android.sdk.internal.database.model.ReactionAggregatedSummaryEntityFields
|
import org.matrix.android.sdk.internal.database.model.ReactionAggregatedSummaryEntityFields
|
||||||
import org.matrix.android.sdk.internal.database.model.ReferencesAggregatedSummaryEntity
|
import org.matrix.android.sdk.internal.database.model.ReferencesAggregatedSummaryEntity
|
||||||
|
@ -68,6 +59,7 @@ import org.matrix.android.sdk.internal.di.SessionId
|
||||||
import org.matrix.android.sdk.internal.di.UserId
|
import org.matrix.android.sdk.internal.di.UserId
|
||||||
import org.matrix.android.sdk.internal.session.EventInsertLiveProcessor
|
import org.matrix.android.sdk.internal.session.EventInsertLiveProcessor
|
||||||
import org.matrix.android.sdk.internal.session.room.aggregation.livelocation.LiveLocationAggregationProcessor
|
import org.matrix.android.sdk.internal.session.room.aggregation.livelocation.LiveLocationAggregationProcessor
|
||||||
|
import org.matrix.android.sdk.internal.session.room.aggregation.poll.PollAggregationProcessor
|
||||||
import org.matrix.android.sdk.internal.session.room.state.StateEventDataSource
|
import org.matrix.android.sdk.internal.session.room.state.StateEventDataSource
|
||||||
import org.matrix.android.sdk.internal.util.time.Clock
|
import org.matrix.android.sdk.internal.util.time.Clock
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
|
@ -79,6 +71,7 @@ internal class EventRelationsAggregationProcessor @Inject constructor(
|
||||||
@SessionId private val sessionId: String,
|
@SessionId private val sessionId: String,
|
||||||
private val sessionManager: SessionManager,
|
private val sessionManager: SessionManager,
|
||||||
private val liveLocationAggregationProcessor: LiveLocationAggregationProcessor,
|
private val liveLocationAggregationProcessor: LiveLocationAggregationProcessor,
|
||||||
|
private val pollAggregationProcessor: PollAggregationProcessor,
|
||||||
private val clock: Clock,
|
private val clock: Clock,
|
||||||
) : EventInsertLiveProcessor {
|
) : EventInsertLiveProcessor {
|
||||||
|
|
||||||
|
@ -162,9 +155,8 @@ internal class EventRelationsAggregationProcessor @Inject constructor(
|
||||||
// A replace!
|
// A replace!
|
||||||
handleReplace(realm, event, it, roomId, isLocalEcho, encryptedEventContent.relatesTo.eventId)
|
handleReplace(realm, event, it, roomId, isLocalEcho, encryptedEventContent.relatesTo.eventId)
|
||||||
} else if (event.getClearType() in EventType.POLL_RESPONSE) {
|
} else if (event.getClearType() in EventType.POLL_RESPONSE) {
|
||||||
event.getClearContent().toModel<MessagePollResponseContent>(catchError = true)?.let { pollResponseContent ->
|
sessionManager.getSessionComponent(sessionId)?.session()?.let { session ->
|
||||||
Timber.v("###RESPONSE in room $roomId for event ${event.eventId}")
|
pollAggregationProcessor.handlePollResponseEvent(session, realm, event)
|
||||||
handleResponse(realm, event, pollResponseContent, roomId, isLocalEcho, encryptedEventContent.relatesTo.eventId)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -184,12 +176,16 @@ internal class EventRelationsAggregationProcessor @Inject constructor(
|
||||||
}
|
}
|
||||||
in EventType.POLL_RESPONSE -> {
|
in EventType.POLL_RESPONSE -> {
|
||||||
event.getClearContent().toModel<MessagePollResponseContent>(catchError = true)?.let {
|
event.getClearContent().toModel<MessagePollResponseContent>(catchError = true)?.let {
|
||||||
handleResponse(realm, event, it, roomId, isLocalEcho, event.getRelationContent()?.eventId)
|
sessionManager.getSessionComponent(sessionId)?.session()?.let { session ->
|
||||||
|
pollAggregationProcessor.handlePollResponseEvent(session, realm, event)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
in EventType.POLL_END -> {
|
in EventType.POLL_END -> {
|
||||||
event.content.toModel<MessageEndPollContent>(catchError = true)?.let {
|
sessionManager.getSessionComponent(sessionId)?.session()?.let { session ->
|
||||||
handleEndPoll(realm, event, it, roomId, isLocalEcho)
|
getPowerLevelsHelper(event.roomId)?.let {
|
||||||
|
pollAggregationProcessor.handlePollEndEvent(session, it, realm, event)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
in EventType.BEACON_LOCATION_DATA -> {
|
in EventType.BEACON_LOCATION_DATA -> {
|
||||||
|
@ -245,12 +241,16 @@ internal class EventRelationsAggregationProcessor @Inject constructor(
|
||||||
}
|
}
|
||||||
in EventType.POLL_RESPONSE -> {
|
in EventType.POLL_RESPONSE -> {
|
||||||
event.content.toModel<MessagePollResponseContent>(catchError = true)?.let {
|
event.content.toModel<MessagePollResponseContent>(catchError = true)?.let {
|
||||||
handleResponse(realm, event, it, roomId, isLocalEcho)
|
sessionManager.getSessionComponent(sessionId)?.session()?.let { session ->
|
||||||
|
pollAggregationProcessor.handlePollResponseEvent(session, realm, event)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
in EventType.POLL_END -> {
|
in EventType.POLL_END -> {
|
||||||
event.content.toModel<MessageEndPollContent>(catchError = true)?.let {
|
sessionManager.getSessionComponent(sessionId)?.session()?.let { session ->
|
||||||
handleEndPoll(realm, event, it, roomId, isLocalEcho)
|
getPowerLevelsHelper(event.roomId)?.let {
|
||||||
|
pollAggregationProcessor.handlePollEndEvent(session, it, realm, event)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
in EventType.STATE_ROOM_BEACON_INFO -> {
|
in EventType.STATE_ROOM_BEACON_INFO -> {
|
||||||
|
@ -318,22 +318,6 @@ internal class EventRelationsAggregationProcessor @Inject constructor(
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
ContentMapper
|
|
||||||
.map(eventAnnotationsSummaryEntity.pollResponseSummary?.aggregatedContent)
|
|
||||||
?.toModel<PollSummaryContent>()
|
|
||||||
?.let { existingPollSummaryContent ->
|
|
||||||
eventAnnotationsSummaryEntity.pollResponseSummary?.aggregatedContent = ContentMapper.map(
|
|
||||||
PollSummaryContent(
|
|
||||||
myVote = existingPollSummaryContent.myVote,
|
|
||||||
votes = emptyList(),
|
|
||||||
votesSummary = emptyMap(),
|
|
||||||
totalVotes = 0,
|
|
||||||
winnerVoteCount = 0,
|
|
||||||
)
|
|
||||||
.toContent()
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
val txId = event.unsignedData?.transactionId
|
val txId = event.unsignedData?.transactionId
|
||||||
// is it a remote echo?
|
// is it a remote echo?
|
||||||
if (!isLocalEcho && existingSummary.editions.any { it.eventId == txId }) {
|
if (!isLocalEcho && existingSummary.editions.any { it.eventId == txId }) {
|
||||||
|
@ -363,6 +347,10 @@ internal class EventRelationsAggregationProcessor @Inject constructor(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (event.getClearType() in EventType.POLL_START) {
|
||||||
|
pollAggregationProcessor.handlePollStartEvent(realm, event)
|
||||||
|
}
|
||||||
|
|
||||||
if (!isLocalEcho) {
|
if (!isLocalEcho) {
|
||||||
val replaceEvent = TimelineEventEntity
|
val replaceEvent = TimelineEventEntity
|
||||||
.where(realm, roomId, eventId)
|
.where(realm, roomId, eventId)
|
||||||
|
@ -376,6 +364,7 @@ internal class EventRelationsAggregationProcessor @Inject constructor(
|
||||||
* Check if the edition is on the latest thread event, and update it accordingly.
|
* Check if the edition is on the latest thread event, and update it accordingly.
|
||||||
* @param editedEvent The event that will be changed
|
* @param editedEvent The event that will be changed
|
||||||
* @param replaceEvent The new event
|
* @param replaceEvent The new event
|
||||||
|
* @param editions list of edition of event
|
||||||
*/
|
*/
|
||||||
private fun handleThreadSummaryEdition(editedEvent: EventEntity?,
|
private fun handleThreadSummaryEdition(editedEvent: EventEntity?,
|
||||||
replaceEvent: TimelineEventEntity?,
|
replaceEvent: TimelineEventEntity?,
|
||||||
|
@ -392,173 +381,10 @@ internal class EventRelationsAggregationProcessor @Inject constructor(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun handleResponse(realm: Realm,
|
private fun getPowerLevelsHelper(roomId: String): PowerLevelsHelper? {
|
||||||
event: Event,
|
return stateEventDataSource.getStateEvent(roomId, EventType.STATE_ROOM_POWER_LEVELS, QueryStringValue.NoCondition)
|
||||||
content: MessagePollResponseContent,
|
|
||||||
roomId: String,
|
|
||||||
isLocalEcho: Boolean,
|
|
||||||
relatedEventId: String? = null) {
|
|
||||||
val eventId = event.eventId ?: return
|
|
||||||
val senderId = event.senderId ?: return
|
|
||||||
val targetEventId = relatedEventId ?: content.relatesTo?.eventId ?: return
|
|
||||||
val eventTimestamp = event.originServerTs ?: return
|
|
||||||
|
|
||||||
val targetPollContent = getPollContent(roomId, targetEventId) ?: return
|
|
||||||
|
|
||||||
// ok, this is a poll response
|
|
||||||
var existing = EventAnnotationsSummaryEntity.where(realm, roomId, targetEventId).findFirst()
|
|
||||||
if (existing == null) {
|
|
||||||
Timber.v("## POLL creating new relation summary for $targetEventId")
|
|
||||||
existing = EventAnnotationsSummaryEntity.create(realm, roomId, targetEventId)
|
|
||||||
}
|
|
||||||
|
|
||||||
// we have it
|
|
||||||
val existingPollSummary = existing.pollResponseSummary
|
|
||||||
?: realm.createObject(PollResponseAggregatedSummaryEntity::class.java).also {
|
|
||||||
existing.pollResponseSummary = it
|
|
||||||
}
|
|
||||||
|
|
||||||
val closedTime = existingPollSummary.closedTime
|
|
||||||
if (closedTime != null && eventTimestamp > closedTime) {
|
|
||||||
Timber.v("## POLL is closed ignore event poll:$targetEventId, event :${event.eventId}")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
val currentModel = ContentMapper.map(existingPollSummary.aggregatedContent).toModel<PollSummaryContent>()
|
|
||||||
|
|
||||||
if (existingPollSummary.sourceEvents.contains(eventId)) {
|
|
||||||
// ignore this event, we already know it (??)
|
|
||||||
Timber.v("## POLL ignoring event for summary, it's known eventId:$eventId")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
val txId = event.unsignedData?.transactionId
|
|
||||||
// is it a remote echo?
|
|
||||||
if (!isLocalEcho && existingPollSummary.sourceLocalEchoEvents.contains(txId)) {
|
|
||||||
// ok it has already been managed
|
|
||||||
Timber.v("## POLL Receiving remote echo of response eventId:$eventId")
|
|
||||||
existingPollSummary.sourceLocalEchoEvents.remove(txId)
|
|
||||||
existingPollSummary.sourceEvents.add(event.eventId)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
val option = content.getBestResponse()?.answers?.first() ?: return Unit.also {
|
|
||||||
Timber.d("## POLL Ignoring malformed response no option eventId:$eventId content: ${event.content}")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check if this option is in available options
|
|
||||||
if (!targetPollContent.getBestPollCreationInfo()?.answers?.map { it.id }?.contains(option).orFalse()) {
|
|
||||||
Timber.v("## POLL $targetEventId doesn't contain option $option")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
val votes = currentModel?.votes.orEmpty().toMutableList()
|
|
||||||
|
|
||||||
var myVote: String? = null
|
|
||||||
val existingVoteIndex = votes.indexOfFirst { it.userId == senderId }
|
|
||||||
if (existingVoteIndex != -1) {
|
|
||||||
// Is the vote newer?
|
|
||||||
val existingVote = votes[existingVoteIndex]
|
|
||||||
if (existingVote.voteTimestamp < eventTimestamp) {
|
|
||||||
// Take the new one
|
|
||||||
votes[existingVoteIndex] = VoteInfo(senderId, option, eventTimestamp)
|
|
||||||
if (userId == senderId) {
|
|
||||||
myVote = option
|
|
||||||
}
|
|
||||||
Timber.v("## POLL adding vote $option for user $senderId in poll :$targetEventId ")
|
|
||||||
} else {
|
|
||||||
Timber.v("## POLL Ignoring vote (older than known one) eventId:$eventId ")
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
votes.add(VoteInfo(senderId, option, eventTimestamp))
|
|
||||||
if (userId == senderId) {
|
|
||||||
myVote = option
|
|
||||||
}
|
|
||||||
Timber.v("## POLL adding vote $option for user $senderId in poll :$targetEventId ")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Precompute the percentage of votes for all options
|
|
||||||
val totalVotes = votes.size
|
|
||||||
val newVotesSummary = votes
|
|
||||||
.groupBy({ it.option }, { it.userId })
|
|
||||||
.mapValues {
|
|
||||||
VoteSummary(
|
|
||||||
total = it.value.size,
|
|
||||||
percentage = if (totalVotes == 0 && it.value.isEmpty()) 0.0 else it.value.size.toDouble() / totalVotes
|
|
||||||
)
|
|
||||||
}
|
|
||||||
val newWinnerVoteCount = newVotesSummary.maxOf { it.value.total }
|
|
||||||
|
|
||||||
if (isLocalEcho) {
|
|
||||||
existingPollSummary.sourceLocalEchoEvents.add(eventId)
|
|
||||||
} else {
|
|
||||||
existingPollSummary.sourceEvents.add(eventId)
|
|
||||||
}
|
|
||||||
|
|
||||||
val newSumModel = PollSummaryContent(
|
|
||||||
myVote = myVote,
|
|
||||||
votes = votes,
|
|
||||||
votesSummary = newVotesSummary,
|
|
||||||
totalVotes = totalVotes,
|
|
||||||
winnerVoteCount = newWinnerVoteCount
|
|
||||||
)
|
|
||||||
|
|
||||||
existingPollSummary.aggregatedContent = ContentMapper.map(newSumModel.toContent())
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun handleEndPoll(realm: Realm,
|
|
||||||
event: Event,
|
|
||||||
content: MessageEndPollContent,
|
|
||||||
roomId: String,
|
|
||||||
isLocalEcho: Boolean) {
|
|
||||||
val pollEventId = content.relatesTo?.eventId ?: return
|
|
||||||
val pollOwnerId = getPollEvent(roomId, pollEventId)?.root?.senderId
|
|
||||||
val isPollOwner = pollOwnerId == event.senderId
|
|
||||||
val powerLevelsHelper = stateEventDataSource.getStateEvent(roomId, EventType.STATE_ROOM_POWER_LEVELS, QueryStringValue.NoCondition)
|
|
||||||
?.content?.toModel<PowerLevelsContent>()
|
?.content?.toModel<PowerLevelsContent>()
|
||||||
?.let { PowerLevelsHelper(it) }
|
?.let { PowerLevelsHelper(it) }
|
||||||
|
|
||||||
if (!isPollOwner && !powerLevelsHelper?.isUserAbleToRedact(event.senderId ?: "").orFalse()) {
|
|
||||||
Timber.v("## Received poll.end event $pollEventId but user ${event.senderId} doesn't have enough power level in room $roomId")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
var existingPoll = EventAnnotationsSummaryEntity.where(realm, roomId, pollEventId).findFirst()
|
|
||||||
if (existingPoll == null) {
|
|
||||||
Timber.v("## POLL creating new relation summary for $pollEventId")
|
|
||||||
existingPoll = EventAnnotationsSummaryEntity.create(realm, roomId, pollEventId)
|
|
||||||
}
|
|
||||||
|
|
||||||
// we have it
|
|
||||||
val existingPollSummary = existingPoll.pollResponseSummary
|
|
||||||
?: realm.createObject(PollResponseAggregatedSummaryEntity::class.java).also {
|
|
||||||
existingPoll.pollResponseSummary = it
|
|
||||||
}
|
|
||||||
|
|
||||||
val txId = event.unsignedData?.transactionId
|
|
||||||
existingPollSummary.closedTime = event.originServerTs
|
|
||||||
|
|
||||||
// is it a remote echo?
|
|
||||||
if (!isLocalEcho && existingPollSummary.sourceLocalEchoEvents.contains(txId)) {
|
|
||||||
// ok it has already been managed
|
|
||||||
Timber.v("## POLL Receiving remote echo of response eventId:$pollEventId")
|
|
||||||
existingPollSummary.sourceLocalEchoEvents.remove(txId)
|
|
||||||
existingPollSummary.sourceEvents.add(event.eventId)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun getPollEvent(roomId: String, eventId: String): TimelineEvent? {
|
|
||||||
val session = sessionManager.getSessionComponent(sessionId)?.session()
|
|
||||||
return session?.roomService()?.getRoom(roomId)?.getTimelineEvent(eventId) ?: return null.also {
|
|
||||||
Timber.v("## POLL target poll event $eventId not found in room $roomId")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun getPollContent(roomId: String, eventId: String): MessagePollContent? {
|
|
||||||
val pollEvent = getPollEvent(roomId, eventId) ?: return null
|
|
||||||
|
|
||||||
return pollEvent.getLastMessageContent() as? MessagePollContent ?: return null.also {
|
|
||||||
Timber.v("## POLL target poll event $eventId content is malformed")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun handleInitialAggregatedRelations(realm: Realm,
|
private fun handleInitialAggregatedRelations(realm: Realm,
|
||||||
|
|
|
@ -179,6 +179,7 @@ internal interface RoomAPI {
|
||||||
* Invite a user to a room, using a ThreePid
|
* Invite a user to a room, using a ThreePid
|
||||||
* Ref: https://matrix.org/docs/spec/client_server/r0.6.1#id101
|
* Ref: https://matrix.org/docs/spec/client_server/r0.6.1#id101
|
||||||
* @param roomId Required. The room identifier (not alias) to which to invite the user.
|
* @param roomId Required. The room identifier (not alias) to which to invite the user.
|
||||||
|
* @param body the Json body
|
||||||
*/
|
*/
|
||||||
@POST(NetworkConstants.URI_API_PREFIX_PATH_R0 + "rooms/{roomId}/invite")
|
@POST(NetworkConstants.URI_API_PREFIX_PATH_R0 + "rooms/{roomId}/invite")
|
||||||
suspend fun invite3pid(@Path("roomId") roomId: String,
|
suspend fun invite3pid(@Path("roomId") roomId: String,
|
||||||
|
@ -221,8 +222,13 @@ internal interface RoomAPI {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Paginate relations for event based in normal topological order.
|
* Paginate relations for event based in normal topological order.
|
||||||
|
* @param roomId the room Id
|
||||||
|
* @param eventId the event Id
|
||||||
* @param relationType filter for this relation type
|
* @param relationType filter for this relation type
|
||||||
* @param eventType filter for this event type
|
* @param eventType filter for this event type
|
||||||
|
* @param from from token
|
||||||
|
* @param to to token
|
||||||
|
* @param limit max number of Event to retrieve
|
||||||
*/
|
*/
|
||||||
@GET(NetworkConstants.URI_API_PREFIX_PATH_UNSTABLE + "rooms/{roomId}/relations/{eventId}/{relationType}/{eventType}")
|
@GET(NetworkConstants.URI_API_PREFIX_PATH_UNSTABLE + "rooms/{roomId}/relations/{eventId}/{relationType}/{eventType}")
|
||||||
suspend fun getRelations(@Path("roomId") roomId: String,
|
suspend fun getRelations(@Path("roomId") roomId: String,
|
||||||
|
@ -236,7 +242,13 @@ internal interface RoomAPI {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Paginate relations for thread events based in normal topological order.
|
* Paginate relations for thread events based in normal topological order.
|
||||||
|
*
|
||||||
|
* @param roomId the room Id
|
||||||
|
* @param eventId the event Id
|
||||||
* @param relationType filter for this relation type
|
* @param relationType filter for this relation type
|
||||||
|
* @param from from token
|
||||||
|
* @param to to token
|
||||||
|
* @param limit max number of Event to retrieve
|
||||||
*/
|
*/
|
||||||
@GET(NetworkConstants.URI_API_PREFIX_PATH_UNSTABLE + "rooms/{roomId}/relations/{eventId}/{relationType}")
|
@GET(NetworkConstants.URI_API_PREFIX_PATH_UNSTABLE + "rooms/{roomId}/relations/{eventId}/{relationType}")
|
||||||
suspend fun getThreadsRelations(@Path("roomId") roomId: String,
|
suspend fun getThreadsRelations(@Path("roomId") roomId: String,
|
||||||
|
|
|
@ -35,7 +35,7 @@ internal class RoomAvatarResolver @Inject constructor(@UserId private val userId
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Compute the room avatar url.
|
* Compute the room avatar url.
|
||||||
* @param realm: the current instance of realm
|
* @param realm the current instance of realm
|
||||||
* @param roomId the roomId of the room to resolve avatar
|
* @param roomId the roomId of the room to resolve avatar
|
||||||
* @return the room avatar url, can be a fallback to a room member avatar or null
|
* @return the room avatar url, can be a fallback to a room member avatar or null
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -0,0 +1,97 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 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.session.room.aggregation.livelocation
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import androidx.work.WorkerParameters
|
||||||
|
import com.squareup.moshi.JsonClass
|
||||||
|
import io.realm.RealmConfiguration
|
||||||
|
import org.matrix.android.sdk.api.util.md5
|
||||||
|
import org.matrix.android.sdk.internal.SessionManager
|
||||||
|
import org.matrix.android.sdk.internal.database.awaitTransaction
|
||||||
|
import org.matrix.android.sdk.internal.database.model.livelocation.LiveLocationShareAggregatedSummaryEntity
|
||||||
|
import org.matrix.android.sdk.internal.database.query.get
|
||||||
|
import org.matrix.android.sdk.internal.di.SessionDatabase
|
||||||
|
import org.matrix.android.sdk.internal.session.SessionComponent
|
||||||
|
import org.matrix.android.sdk.internal.worker.SessionSafeCoroutineWorker
|
||||||
|
import org.matrix.android.sdk.internal.worker.SessionWorkerParams
|
||||||
|
import timber.log.Timber
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Worker dedicated to update live location summary data so that it is considered as deactivated.
|
||||||
|
* For the context: it is needed since a live location share should be deactivated after a certain timeout.
|
||||||
|
*/
|
||||||
|
internal class DeactivateLiveLocationShareWorker(context: Context, params: WorkerParameters, sessionManager: SessionManager) :
|
||||||
|
SessionSafeCoroutineWorker<DeactivateLiveLocationShareWorker.Params>(
|
||||||
|
context,
|
||||||
|
params,
|
||||||
|
sessionManager,
|
||||||
|
Params::class.java
|
||||||
|
) {
|
||||||
|
|
||||||
|
@JsonClass(generateAdapter = true)
|
||||||
|
internal data class Params(
|
||||||
|
override val sessionId: String,
|
||||||
|
override val lastFailureMessage: String? = null,
|
||||||
|
val eventId: String,
|
||||||
|
val roomId: String
|
||||||
|
) : SessionWorkerParams
|
||||||
|
|
||||||
|
@SessionDatabase
|
||||||
|
@Inject lateinit var realmConfiguration: RealmConfiguration
|
||||||
|
|
||||||
|
override fun injectWith(injector: SessionComponent) {
|
||||||
|
injector.inject(this)
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun doSafeWork(params: Params): Result {
|
||||||
|
return runCatching {
|
||||||
|
deactivateLiveLocationShare(params)
|
||||||
|
}.fold(
|
||||||
|
onSuccess = {
|
||||||
|
Result.success()
|
||||||
|
},
|
||||||
|
onFailure = {
|
||||||
|
Timber.e("failed to deactivate live, eventId: ${params.eventId}, roomId: ${params.roomId}")
|
||||||
|
Result.failure()
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private suspend fun deactivateLiveLocationShare(params: Params) {
|
||||||
|
awaitTransaction(realmConfiguration) { realm ->
|
||||||
|
val aggregatedSummary = LiveLocationShareAggregatedSummaryEntity.get(
|
||||||
|
realm = realm,
|
||||||
|
roomId = params.roomId,
|
||||||
|
eventId = params.eventId
|
||||||
|
)
|
||||||
|
aggregatedSummary?.isActive = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun buildErrorParams(params: Params, message: String): Params {
|
||||||
|
return params.copy(lastFailureMessage = params.lastFailureMessage ?: message)
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
fun getWorkName(eventId: String, roomId: String): String {
|
||||||
|
val hash = "$eventId$roomId".md5()
|
||||||
|
return "DeactivateLiveLocationWork-$hash"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -16,6 +16,7 @@
|
||||||
|
|
||||||
package org.matrix.android.sdk.internal.session.room.aggregation.livelocation
|
package org.matrix.android.sdk.internal.session.room.aggregation.livelocation
|
||||||
|
|
||||||
|
import androidx.work.ExistingWorkPolicy
|
||||||
import io.realm.Realm
|
import io.realm.Realm
|
||||||
import org.matrix.android.sdk.api.extensions.orTrue
|
import org.matrix.android.sdk.api.extensions.orTrue
|
||||||
import org.matrix.android.sdk.api.session.events.model.Event
|
import org.matrix.android.sdk.api.session.events.model.Event
|
||||||
|
@ -26,17 +27,27 @@ import org.matrix.android.sdk.api.session.room.model.message.MessageBeaconLocati
|
||||||
import org.matrix.android.sdk.internal.database.mapper.ContentMapper
|
import org.matrix.android.sdk.internal.database.mapper.ContentMapper
|
||||||
import org.matrix.android.sdk.internal.database.model.livelocation.LiveLocationShareAggregatedSummaryEntity
|
import org.matrix.android.sdk.internal.database.model.livelocation.LiveLocationShareAggregatedSummaryEntity
|
||||||
import org.matrix.android.sdk.internal.database.query.getOrCreate
|
import org.matrix.android.sdk.internal.database.query.getOrCreate
|
||||||
|
import org.matrix.android.sdk.internal.di.SessionId
|
||||||
|
import org.matrix.android.sdk.internal.di.WorkManagerProvider
|
||||||
|
import org.matrix.android.sdk.internal.util.time.Clock
|
||||||
|
import org.matrix.android.sdk.internal.worker.WorkerParamsFactory
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
|
import java.util.concurrent.TimeUnit
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
internal class LiveLocationAggregationProcessor @Inject constructor() {
|
internal class LiveLocationAggregationProcessor @Inject constructor(
|
||||||
|
@SessionId private val sessionId: String,
|
||||||
|
private val workManagerProvider: WorkManagerProvider,
|
||||||
|
private val clock: Clock,
|
||||||
|
) {
|
||||||
|
|
||||||
fun handleBeaconInfo(realm: Realm, event: Event, content: MessageBeaconInfoContent, roomId: String, isLocalEcho: Boolean) {
|
fun handleBeaconInfo(realm: Realm, event: Event, content: MessageBeaconInfoContent, roomId: String, isLocalEcho: Boolean) {
|
||||||
if (event.senderId.isNullOrEmpty() || isLocalEcho) {
|
if (event.senderId.isNullOrEmpty() || isLocalEcho) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
val targetEventId = if (content.isLive.orTrue()) {
|
val isLive = content.isLive.orTrue()
|
||||||
|
val targetEventId = if (isLive) {
|
||||||
event.eventId
|
event.eventId
|
||||||
} else {
|
} else {
|
||||||
// when live is set to false, we use the id of the event that should have been replaced
|
// when live is set to false, we use the id of the event that should have been replaced
|
||||||
|
@ -56,8 +67,39 @@ internal class LiveLocationAggregationProcessor @Inject constructor() {
|
||||||
|
|
||||||
Timber.d("updating summary of id=$targetEventId with isLive=${content.isLive}")
|
Timber.d("updating summary of id=$targetEventId with isLive=${content.isLive}")
|
||||||
|
|
||||||
aggregatedSummary.endOfLiveTimestampMillis = content.getBestTimestampMillis()?.let { it + (content.timeout ?: 0) }
|
val endOfLiveTimestampMillis = content.getBestTimestampMillis()?.let { it + (content.timeout ?: 0) }
|
||||||
aggregatedSummary.isActive = content.isLive
|
aggregatedSummary.endOfLiveTimestampMillis = endOfLiveTimestampMillis
|
||||||
|
aggregatedSummary.isActive = isLive
|
||||||
|
|
||||||
|
if (isLive) {
|
||||||
|
scheduleDeactivationAfterTimeout(targetEventId, roomId, endOfLiveTimestampMillis)
|
||||||
|
} else {
|
||||||
|
cancelDeactivationAfterTimeout(targetEventId, roomId)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun scheduleDeactivationAfterTimeout(eventId: String, roomId: String, endOfLiveTimestampMillis: Long?) {
|
||||||
|
endOfLiveTimestampMillis ?: return
|
||||||
|
|
||||||
|
val workParams = DeactivateLiveLocationShareWorker.Params(sessionId = sessionId, eventId = eventId, roomId = roomId)
|
||||||
|
val workData = WorkerParamsFactory.toData(workParams)
|
||||||
|
val workName = DeactivateLiveLocationShareWorker.getWorkName(eventId = eventId, roomId = roomId)
|
||||||
|
val workDelayMillis = (endOfLiveTimestampMillis - clock.epochMillis()).coerceAtLeast(0)
|
||||||
|
val workRequest = workManagerProvider.matrixOneTimeWorkRequestBuilder<DeactivateLiveLocationShareWorker>()
|
||||||
|
.setInitialDelay(workDelayMillis, TimeUnit.MILLISECONDS)
|
||||||
|
.setInputData(workData)
|
||||||
|
.build()
|
||||||
|
|
||||||
|
workManagerProvider.workManager.enqueueUniqueWork(
|
||||||
|
workName,
|
||||||
|
ExistingWorkPolicy.REPLACE,
|
||||||
|
workRequest
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun cancelDeactivationAfterTimeout(eventId: String, roomId: String) {
|
||||||
|
val workName = DeactivateLiveLocationShareWorker.getWorkName(eventId = eventId, roomId = roomId)
|
||||||
|
workManagerProvider.workManager.cancelUniqueWork(workName)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun handleBeaconLocationData(
|
fun handleBeaconLocationData(
|
||||||
|
|
|
@ -0,0 +1,203 @@
|
||||||
|
/*
|
||||||
|
* 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.session.room.aggregation.poll
|
||||||
|
|
||||||
|
import io.realm.Realm
|
||||||
|
import org.matrix.android.sdk.api.extensions.orFalse
|
||||||
|
import org.matrix.android.sdk.api.session.Session
|
||||||
|
import org.matrix.android.sdk.api.session.events.model.Event
|
||||||
|
import org.matrix.android.sdk.api.session.events.model.LocalEcho
|
||||||
|
import org.matrix.android.sdk.api.session.events.model.RelationType
|
||||||
|
import org.matrix.android.sdk.api.session.events.model.getRelationContent
|
||||||
|
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.getTimelineEvent
|
||||||
|
import org.matrix.android.sdk.api.session.room.model.PollSummaryContent
|
||||||
|
import org.matrix.android.sdk.api.session.room.model.VoteInfo
|
||||||
|
import org.matrix.android.sdk.api.session.room.model.VoteSummary
|
||||||
|
import org.matrix.android.sdk.api.session.room.model.message.MessageEndPollContent
|
||||||
|
import org.matrix.android.sdk.api.session.room.model.message.MessagePollContent
|
||||||
|
import org.matrix.android.sdk.api.session.room.model.message.MessagePollResponseContent
|
||||||
|
import org.matrix.android.sdk.api.session.room.powerlevels.PowerLevelsHelper
|
||||||
|
import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent
|
||||||
|
import org.matrix.android.sdk.api.session.room.timeline.getLastMessageContent
|
||||||
|
import org.matrix.android.sdk.internal.database.mapper.ContentMapper
|
||||||
|
import org.matrix.android.sdk.internal.database.model.EventAnnotationsSummaryEntity
|
||||||
|
import org.matrix.android.sdk.internal.database.model.PollResponseAggregatedSummaryEntity
|
||||||
|
import org.matrix.android.sdk.internal.database.query.create
|
||||||
|
import org.matrix.android.sdk.internal.database.query.getOrCreate
|
||||||
|
import org.matrix.android.sdk.internal.database.query.where
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
class DefaultPollAggregationProcessor @Inject constructor() : PollAggregationProcessor {
|
||||||
|
|
||||||
|
override fun handlePollStartEvent(realm: Realm, event: Event): Boolean {
|
||||||
|
val content = event.getClearContent()?.toModel<MessagePollContent>()
|
||||||
|
if (content?.relatesTo?.type != RelationType.REPLACE) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
val roomId = event.roomId ?: return false
|
||||||
|
val targetEventId = content.relatesTo.eventId ?: return false
|
||||||
|
|
||||||
|
EventAnnotationsSummaryEntity.getOrCreate(realm, roomId, targetEventId).let { eventAnnotationsSummaryEntity ->
|
||||||
|
ContentMapper
|
||||||
|
.map(eventAnnotationsSummaryEntity.pollResponseSummary?.aggregatedContent)
|
||||||
|
?.toModel<PollSummaryContent>()
|
||||||
|
?.let { existingPollSummaryContent ->
|
||||||
|
eventAnnotationsSummaryEntity.pollResponseSummary?.aggregatedContent = ContentMapper.map(
|
||||||
|
PollSummaryContent(
|
||||||
|
myVote = existingPollSummaryContent.myVote,
|
||||||
|
votes = emptyList(),
|
||||||
|
votesSummary = emptyMap(),
|
||||||
|
totalVotes = 0,
|
||||||
|
winnerVoteCount = 0,
|
||||||
|
)
|
||||||
|
.toContent()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun handlePollResponseEvent(session: Session, realm: Realm, event: Event): Boolean {
|
||||||
|
val content = event.getClearContent()?.toModel<MessagePollResponseContent>() ?: return false
|
||||||
|
val roomId = event.roomId ?: return false
|
||||||
|
val senderId = event.senderId ?: return false
|
||||||
|
val targetEventId = (event.getRelationContent() ?: content.relatesTo)?.eventId ?: return false
|
||||||
|
val targetPollContent = getPollContent(session, roomId, targetEventId) ?: return false
|
||||||
|
|
||||||
|
val annotationsSummaryEntity = getAnnotationsSummaryEntity(realm, roomId, targetEventId)
|
||||||
|
val aggregatedPollSummaryEntity = getAggregatedPollSummaryEntity(realm, annotationsSummaryEntity)
|
||||||
|
|
||||||
|
val closedTime = aggregatedPollSummaryEntity.closedTime
|
||||||
|
val responseTime = event.originServerTs ?: return false
|
||||||
|
if (closedTime != null && responseTime > closedTime) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
if (aggregatedPollSummaryEntity.sourceEvents.contains(event.eventId)) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
val txId = event.unsignedData?.transactionId
|
||||||
|
val isLocalEcho = LocalEcho.isLocalEchoId(event.eventId ?: "")
|
||||||
|
if (!isLocalEcho && aggregatedPollSummaryEntity.sourceLocalEchoEvents.contains(txId)) {
|
||||||
|
aggregatedPollSummaryEntity.sourceLocalEchoEvents.remove(txId)
|
||||||
|
aggregatedPollSummaryEntity.sourceEvents.add(event.eventId)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
val vote = content.getBestResponse()?.answers?.first() ?: return false
|
||||||
|
if (!targetPollContent.getBestPollCreationInfo()?.answers?.map { it.id }?.contains(vote).orFalse()) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
val pollSummaryModel = ContentMapper.map(aggregatedPollSummaryEntity.aggregatedContent).toModel<PollSummaryContent>()
|
||||||
|
val existingVotes = pollSummaryModel?.votes.orEmpty().toMutableList()
|
||||||
|
val existingVoteIndex = existingVotes.indexOfFirst { it.userId == senderId }
|
||||||
|
|
||||||
|
if (existingVoteIndex != -1) {
|
||||||
|
val existingVote = existingVotes[existingVoteIndex]
|
||||||
|
if (existingVote.voteTimestamp > responseTime) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
existingVotes[existingVoteIndex] = VoteInfo(senderId, vote, responseTime)
|
||||||
|
} else {
|
||||||
|
existingVotes.add(VoteInfo(senderId, vote, responseTime))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Precompute the percentage of votes for all options
|
||||||
|
val totalVotes = existingVotes.size
|
||||||
|
val newVotesSummary = existingVotes
|
||||||
|
.groupBy({ it.option }, { it.userId })
|
||||||
|
.mapValues {
|
||||||
|
VoteSummary(
|
||||||
|
total = it.value.size,
|
||||||
|
percentage = if (totalVotes == 0 && it.value.isEmpty()) 0.0 else it.value.size.toDouble() / totalVotes
|
||||||
|
)
|
||||||
|
}
|
||||||
|
val newWinnerVoteCount = newVotesSummary.maxOf { it.value.total }
|
||||||
|
|
||||||
|
if (isLocalEcho) {
|
||||||
|
aggregatedPollSummaryEntity.sourceLocalEchoEvents.add(event.eventId)
|
||||||
|
} else {
|
||||||
|
aggregatedPollSummaryEntity.sourceEvents.add(event.eventId)
|
||||||
|
}
|
||||||
|
|
||||||
|
val myVote = existingVotes.find { it.userId == session.myUserId }?.option
|
||||||
|
|
||||||
|
val newSumModel = PollSummaryContent(
|
||||||
|
myVote = myVote,
|
||||||
|
votes = existingVotes,
|
||||||
|
votesSummary = newVotesSummary,
|
||||||
|
totalVotes = totalVotes,
|
||||||
|
winnerVoteCount = newWinnerVoteCount
|
||||||
|
)
|
||||||
|
aggregatedPollSummaryEntity.aggregatedContent = ContentMapper.map(newSumModel.toContent())
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun handlePollEndEvent(session: Session, powerLevelsHelper: PowerLevelsHelper, realm: Realm, event: Event): Boolean {
|
||||||
|
val content = event.getClearContent()?.toModel<MessageEndPollContent>() ?: return false
|
||||||
|
val roomId = event.roomId ?: return false
|
||||||
|
val pollEventId = content.relatesTo?.eventId ?: return false
|
||||||
|
val pollOwnerId = getPollEvent(session, roomId, pollEventId)?.root?.senderId
|
||||||
|
val isPollOwner = pollOwnerId == event.senderId
|
||||||
|
|
||||||
|
if (!isPollOwner && !powerLevelsHelper.isUserAbleToRedact(event.senderId ?: "")) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
val annotationsSummaryEntity = getAnnotationsSummaryEntity(realm, roomId, pollEventId)
|
||||||
|
val aggregatedPollSummaryEntity = getAggregatedPollSummaryEntity(realm, annotationsSummaryEntity)
|
||||||
|
|
||||||
|
val txId = event.unsignedData?.transactionId
|
||||||
|
aggregatedPollSummaryEntity.closedTime = event.originServerTs
|
||||||
|
|
||||||
|
val isLocalEcho = LocalEcho.isLocalEchoId(event.eventId ?: "")
|
||||||
|
if (!isLocalEcho && aggregatedPollSummaryEntity.sourceLocalEchoEvents.contains(txId)) {
|
||||||
|
aggregatedPollSummaryEntity.sourceLocalEchoEvents.remove(txId)
|
||||||
|
aggregatedPollSummaryEntity.sourceEvents.add(event.eventId)
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getPollEvent(session: Session, roomId: String, eventId: String): TimelineEvent? {
|
||||||
|
return session.roomService().getRoom(roomId)?.getTimelineEvent(eventId)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getPollContent(session: Session, roomId: String, eventId: String): MessagePollContent? {
|
||||||
|
val pollEvent = getPollEvent(session, roomId, eventId)
|
||||||
|
return pollEvent?.getLastMessageContent() as? MessagePollContent
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getAnnotationsSummaryEntity(realm: Realm, roomId: String, eventId: String): EventAnnotationsSummaryEntity {
|
||||||
|
return EventAnnotationsSummaryEntity.where(realm, roomId, eventId).findFirst()
|
||||||
|
?: EventAnnotationsSummaryEntity.create(realm, roomId, eventId)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getAggregatedPollSummaryEntity(realm: Realm,
|
||||||
|
eventAnnotationsSummaryEntity: EventAnnotationsSummaryEntity): PollResponseAggregatedSummaryEntity {
|
||||||
|
return eventAnnotationsSummaryEntity.pollResponseSummary
|
||||||
|
?: realm.createObject(PollResponseAggregatedSummaryEntity::class.java).also {
|
||||||
|
eventAnnotationsSummaryEntity.pollResponseSummary = it
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,55 @@
|
||||||
|
/*
|
||||||
|
* 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.session.room.aggregation.poll
|
||||||
|
|
||||||
|
import io.realm.Realm
|
||||||
|
import org.matrix.android.sdk.api.session.Session
|
||||||
|
import org.matrix.android.sdk.api.session.events.model.Event
|
||||||
|
import org.matrix.android.sdk.api.session.room.powerlevels.PowerLevelsHelper
|
||||||
|
|
||||||
|
interface PollAggregationProcessor {
|
||||||
|
/**
|
||||||
|
* Poll start events don't need to be processed by the aggregator.
|
||||||
|
* This function will only handle if the poll is edited and will update the poll summary entity.
|
||||||
|
* Returns true if the event is aggregated.
|
||||||
|
*/
|
||||||
|
fun handlePollStartEvent(
|
||||||
|
realm: Realm,
|
||||||
|
event: Event
|
||||||
|
): Boolean
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Aggregates poll response event after many conditional checks like if the poll is ended, if the user is changing his/her vote etc.
|
||||||
|
* Returns true if the event is aggregated.
|
||||||
|
*/
|
||||||
|
fun handlePollResponseEvent(
|
||||||
|
session: Session,
|
||||||
|
realm: Realm,
|
||||||
|
event: Event
|
||||||
|
): Boolean
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates poll summary entity and mark it is ended after many conditional checks like if the poll is already ended etc.
|
||||||
|
* Returns true if the event is aggregated.
|
||||||
|
*/
|
||||||
|
fun handlePollEndEvent(
|
||||||
|
session: Session,
|
||||||
|
powerLevelsHelper: PowerLevelsHelper,
|
||||||
|
realm: Realm,
|
||||||
|
event: Event
|
||||||
|
): Boolean
|
||||||
|
}
|
|
@ -52,8 +52,8 @@ internal class RoomDisplayNameResolver @Inject constructor(
|
||||||
/**
|
/**
|
||||||
* Compute the room display name.
|
* Compute the room display name.
|
||||||
*
|
*
|
||||||
* @param realm: the current instance of realm
|
* @param realm the current instance of realm
|
||||||
* @param roomId: the roomId to resolve the name of.
|
* @param roomId the roomId to resolve the name of.
|
||||||
* @return the room display name
|
* @return the room display name
|
||||||
*/
|
*/
|
||||||
fun resolve(realm: Realm, roomId: String): RoomName {
|
fun resolve(realm: Realm, roomId: String): RoomName {
|
||||||
|
|
|
@ -21,8 +21,8 @@ import timber.log.Timber
|
||||||
import java.util.concurrent.atomic.AtomicInteger
|
import java.util.concurrent.atomic.AtomicInteger
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param queueIdentifier String value to identify a unique Queue
|
* @property queueIdentifier String value to identify a unique Queue
|
||||||
* @param taskIdentifier String value to identify a unique Task. Should be different from queueIdentifier
|
* @property taskIdentifier String value to identify a unique Task. Should be different from queueIdentifier
|
||||||
*/
|
*/
|
||||||
internal abstract class QueuedTask(
|
internal abstract class QueuedTask(
|
||||||
val queueIdentifier: String,
|
val queueIdentifier: String,
|
||||||
|
|
|
@ -24,11 +24,12 @@ import retrofit2.http.Query
|
||||||
internal interface SpaceApi {
|
internal interface SpaceApi {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
* @param spaceId the space Id
|
||||||
* @param suggestedOnly Optional. If true, return only child events and rooms where the m.space.child event has suggested: true.
|
* @param suggestedOnly Optional. If true, return only child events and rooms where the m.space.child event has suggested: true.
|
||||||
* @param limit: Optional: a client-defined limit to the maximum number of rooms to return per page. Must be a non-negative integer.
|
* @param limit Optional: a client-defined limit to the maximum number of rooms to return per page. Must be a non-negative integer.
|
||||||
* @param maxDepth: Optional: The maximum depth in the tree (from the root room) to return.
|
* @param maxDepth Optional: The maximum depth in the tree (from the root room) to return.
|
||||||
* The deepest depth returned will not include children events. Defaults to no-limit. Must be a non-negative integer.
|
* The deepest depth returned will not include children events. Defaults to no-limit. Must be a non-negative integer.
|
||||||
* @param from: Optional. Pagination token given to retrieve the next set of rooms.
|
* @param from Optional. Pagination token given to retrieve the next set of rooms.
|
||||||
* Note that if a pagination token is provided, then the parameters given for suggested_only and max_depth must be the same.
|
* Note that if a pagination token is provided, then the parameters given for suggested_only and max_depth must be the same.
|
||||||
*/
|
*/
|
||||||
@GET(NetworkConstants.URI_API_PREFIX_PATH_V1 + "rooms/{roomId}/hierarchy")
|
@GET(NetworkConstants.URI_API_PREFIX_PATH_V1 + "rooms/{roomId}/hierarchy")
|
||||||
|
|
|
@ -520,9 +520,10 @@ internal class RoomSyncHandler @Inject constructor(
|
||||||
|
|
||||||
private fun decryptIfNeeded(event: Event, roomId: String) {
|
private fun decryptIfNeeded(event: Event, roomId: String) {
|
||||||
try {
|
try {
|
||||||
|
val timelineId = generateTimelineId(roomId)
|
||||||
// Event from sync does not have roomId, so add it to the event first
|
// Event from sync does not have roomId, so add it to the event first
|
||||||
// note: runBlocking should be used here while we are in realm single thread executor, to avoid thread switching
|
// note: runBlocking should be used here while we are in realm single thread executor, to avoid thread switching
|
||||||
val result = runBlocking { cryptoService.decryptEvent(event.copy(roomId = roomId), "") }
|
val result = runBlocking { cryptoService.decryptEvent(event.copy(roomId = roomId), timelineId) }
|
||||||
event.mxDecryptionResult = OlmDecryptionResult(
|
event.mxDecryptionResult = OlmDecryptionResult(
|
||||||
payload = result.clearEvent,
|
payload = result.clearEvent,
|
||||||
senderKey = result.senderCurve25519Key,
|
senderKey = result.senderCurve25519Key,
|
||||||
|
@ -537,6 +538,10 @@ internal class RoomSyncHandler @Inject constructor(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun generateTimelineId(roomId: String): String {
|
||||||
|
return "RoomSyncHandler$roomId"
|
||||||
|
}
|
||||||
|
|
||||||
data class EphemeralResult(
|
data class EphemeralResult(
|
||||||
val typingUserIds: List<String> = emptyList()
|
val typingUserIds: List<String> = emptyList()
|
||||||
)
|
)
|
||||||
|
|
|
@ -206,6 +206,8 @@ internal class ThreadsAwarenessHandler @Inject constructor(
|
||||||
/**
|
/**
|
||||||
* Handle for not thread events that we have marked them as root.
|
* Handle for not thread events that we have marked them as root.
|
||||||
* Find relations and inject them accordingly
|
* Find relations and inject them accordingly
|
||||||
|
* @param realm the realm instance
|
||||||
|
* @param roomId the current room Id
|
||||||
* @param eventEntity the current eventEntity received
|
* @param eventEntity the current eventEntity received
|
||||||
* @param event the current event received
|
* @param event the current event received
|
||||||
* @return The content to inject in the roomSyncHandler live events
|
* @return The content to inject in the roomSyncHandler live events
|
||||||
|
@ -229,9 +231,12 @@ internal class ThreadsAwarenessHandler @Inject constructor(
|
||||||
* This function is responsible to check if there is any event that relates to our current event.
|
* This function is responsible to check if there is any event that relates to our current event.
|
||||||
* This is useful when we receive an event that relates to a missing parent, so when later we receive the parent
|
* This is useful when we receive an event that relates to a missing parent, so when later we receive the parent
|
||||||
* we can update the child as well.
|
* we can update the child as well.
|
||||||
|
* @param realm the realm instance
|
||||||
|
* @param roomId the current room Id
|
||||||
* @param event the current event that we examine
|
* @param event the current event that we examine
|
||||||
* @param eventBody the current body of the event
|
* @param eventBody the current body of the event
|
||||||
* @param isFromCache determines whether or not we already know this is root thread event
|
* @param isFromCache determines whether or not we already know this is root thread event
|
||||||
|
* @param threadRelation the information about thread
|
||||||
* @return The content to inject in the roomSyncHandler live events
|
* @return The content to inject in the roomSyncHandler live events
|
||||||
*/
|
*/
|
||||||
private fun handleEventsThatRelatesTo(
|
private fun handleEventsThatRelatesTo(
|
||||||
|
@ -291,9 +296,12 @@ internal class ThreadsAwarenessHandler @Inject constructor(
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Injecting $eventToInject decrypted content as a reply to $event.
|
* Injecting [eventToInject] decrypted content as a reply to event.
|
||||||
* @param eventToInject the event that will inject
|
* @param roomId the room id
|
||||||
* @param eventBody the actual event body
|
* @param eventBody the actual event body
|
||||||
|
* @param eventToInject the event that will inject
|
||||||
|
* @param eventToInjectBody the event body to inject
|
||||||
|
* @param threadRelation the information about thread
|
||||||
* @return The final content with the injected event
|
* @return The final content with the injected event
|
||||||
*/
|
*/
|
||||||
private fun injectEvent(roomId: String,
|
private fun injectEvent(roomId: String,
|
||||||
|
|
|
@ -116,6 +116,8 @@ internal class DefaultWidgetPostAPIMediator @Inject constructor(private val mosh
|
||||||
/**
|
/**
|
||||||
* Send an object response.
|
* Send an object response.
|
||||||
*
|
*
|
||||||
|
* @param T the Json type
|
||||||
|
* @param type the type
|
||||||
* @param response the response
|
* @param response the response
|
||||||
* @param eventData the modular data
|
* @param eventData the modular data
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -27,6 +27,7 @@ internal interface WidgetsAPI {
|
||||||
* Register to the server.
|
* Register to the server.
|
||||||
*
|
*
|
||||||
* @param body the body content (Ref: https://github.com/matrix-org/matrix-doc/pull/1961)
|
* @param body the body content (Ref: https://github.com/matrix-org/matrix-doc/pull/1961)
|
||||||
|
* @param version the widget API version
|
||||||
*/
|
*/
|
||||||
@POST("register")
|
@POST("register")
|
||||||
suspend fun register(@Body body: OpenIdToken,
|
suspend fun register(@Body body: OpenIdToken,
|
||||||
|
|
|
@ -24,6 +24,7 @@ import kotlinx.coroutines.sync.withPermit
|
||||||
*/
|
*/
|
||||||
internal interface CoroutineSequencer {
|
internal interface CoroutineSequencer {
|
||||||
/**
|
/**
|
||||||
|
* @param T generic type
|
||||||
* @param block the suspendable block to execute
|
* @param block the suspendable block to execute
|
||||||
* @return the result of the block
|
* @return the result of the block
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -53,7 +53,7 @@ internal object JsonCanonicalizer {
|
||||||
/**
|
/**
|
||||||
* Canonicalize a JSON element.
|
* Canonicalize a JSON element.
|
||||||
*
|
*
|
||||||
* @param src the src
|
* @param any the src
|
||||||
* @return the canonicalize element
|
* @return the canonicalize element
|
||||||
*/
|
*/
|
||||||
private fun canonicalizeRecursive(any: Any): String {
|
private fun canonicalizeRecursive(any: Any): String {
|
||||||
|
|
|
@ -75,7 +75,8 @@ internal class DefaultGetWellknownTask @Inject constructor(
|
||||||
* - validate homeserver url and identity server url if provide in .well-known result
|
* - validate homeserver url and identity server url if provide in .well-known result
|
||||||
* - return action and .well-known data
|
* - return action and .well-known data
|
||||||
*
|
*
|
||||||
* @param domain: homeserver domain, deduced from mx userId (ex: "matrix.org" from userId "@user:matrix.org")
|
* @param domain homeserver domain, deduced from mx userId (ex: "matrix.org" from userId "@user:matrix.org")
|
||||||
|
* @param client Http client to perform the request
|
||||||
*/
|
*/
|
||||||
private suspend fun findClientConfig(domain: String, client: OkHttpClient): WellknownResult {
|
private suspend fun findClientConfig(domain: String, client: OkHttpClient): WellknownResult {
|
||||||
val wellKnownAPI = retrofitFactory.create(client, "https://dummy.org")
|
val wellKnownAPI = retrofitFactory.create(client, "https://dummy.org")
|
||||||
|
|
|
@ -27,6 +27,7 @@ import org.matrix.android.sdk.internal.di.MatrixScope
|
||||||
import org.matrix.android.sdk.internal.session.content.UploadContentWorker
|
import org.matrix.android.sdk.internal.session.content.UploadContentWorker
|
||||||
import org.matrix.android.sdk.internal.session.group.GetGroupDataWorker
|
import org.matrix.android.sdk.internal.session.group.GetGroupDataWorker
|
||||||
import org.matrix.android.sdk.internal.session.pushers.AddPusherWorker
|
import org.matrix.android.sdk.internal.session.pushers.AddPusherWorker
|
||||||
|
import org.matrix.android.sdk.internal.session.room.aggregation.livelocation.DeactivateLiveLocationShareWorker
|
||||||
import org.matrix.android.sdk.internal.session.room.send.MultipleEventSendingDispatcherWorker
|
import org.matrix.android.sdk.internal.session.room.send.MultipleEventSendingDispatcherWorker
|
||||||
import org.matrix.android.sdk.internal.session.room.send.RedactEventWorker
|
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.room.send.SendEventWorker
|
||||||
|
@ -66,6 +67,8 @@ internal class MatrixWorkerFactory @Inject constructor(private val sessionManage
|
||||||
UpdateTrustWorker(appContext, workerParameters, sessionManager)
|
UpdateTrustWorker(appContext, workerParameters, sessionManager)
|
||||||
UploadContentWorker::class.java.name ->
|
UploadContentWorker::class.java.name ->
|
||||||
UploadContentWorker(appContext, workerParameters, sessionManager)
|
UploadContentWorker(appContext, workerParameters, sessionManager)
|
||||||
|
DeactivateLiveLocationShareWorker::class.java.name ->
|
||||||
|
DeactivateLiveLocationShareWorker(appContext, workerParameters, sessionManager)
|
||||||
else -> {
|
else -> {
|
||||||
Timber.w("No worker defined on MatrixWorkerFactory for $workerClassName will delegate to default.")
|
Timber.w("No worker defined on MatrixWorkerFactory for $workerClassName will delegate to default.")
|
||||||
// Return null to delegate to the default WorkerFactory.
|
// Return null to delegate to the default WorkerFactory.
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue