diff --git a/CHANGES.md b/CHANGES.md
index c8677b1ae4..4ff45e9c62 100644
--- a/CHANGES.md
+++ b/CHANGES.md
@@ -36,7 +36,7 @@ Other changes
- 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))
- 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)
diff --git a/build.gradle b/build.gradle
index fe71865ef1..023f7a909c 100644
--- a/build.gradle
+++ b/build.gradle
@@ -27,7 +27,7 @@ buildscript {
classpath 'com.google.gms:google-services:4.3.10'
classpath 'org.sonarsource.scanner.gradle:sonarqube-gradle-plugin:3.3'
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.jetbrains.dokka:dokka-gradle-plugin:1.6.21"
classpath "org.jetbrains.kotlinx:kotlinx-knit:0.4.0"
diff --git a/changelog.d/5278.wip b/changelog.d/5278.wip
new file mode 100644
index 0000000000..c6014dc9ac
--- /dev/null
+++ b/changelog.d/5278.wip
@@ -0,0 +1 @@
+Adds email input and verification screens to the new FTUE onboarding flow
diff --git a/changelog.d/5283.wip b/changelog.d/5283.wip
new file mode 100644
index 0000000000..1c2fbfcd61
--- /dev/null
+++ b/changelog.d/5283.wip
@@ -0,0 +1 @@
+FTUE - Adds the redesigned Sign In screen
diff --git a/changelog.d/5783.wip b/changelog.d/5783.wip
new file mode 100644
index 0000000000..6f44813d81
--- /dev/null
+++ b/changelog.d/5783.wip
@@ -0,0 +1 @@
+FTUE - Overrides sign up flow ordering for matrix.org only
diff --git a/changelog.d/5856.bugfix b/changelog.d/5856.bugfix
new file mode 100644
index 0000000000..87f10ac9b2
--- /dev/null
+++ b/changelog.d/5856.bugfix
@@ -0,0 +1 @@
+Use fixed text size in read receipt counter
diff --git a/changelog.d/6012.wip b/changelog.d/6012.wip
new file mode 100644
index 0000000000..9c67d562fe
--- /dev/null
+++ b/changelog.d/6012.wip
@@ -0,0 +1 @@
+Live location sharing: navigation from timeline to map screen
diff --git a/changelog.d/6032.bugfix b/changelog.d/6032.bugfix
new file mode 100644
index 0000000000..c20d7ddd08
--- /dev/null
+++ b/changelog.d/6032.bugfix
@@ -0,0 +1 @@
+Revert: Use member name instead of room name in DM creation item
diff --git a/changelog.d/6074.bugfix b/changelog.d/6074.bugfix
new file mode 100644
index 0000000000..692dce28d7
--- /dev/null
+++ b/changelog.d/6074.bugfix
@@ -0,0 +1 @@
+Poll refactoring with unit tests
diff --git a/changelog.d/6077.sdk b/changelog.d/6077.sdk
new file mode 100644
index 0000000000..80310a28f5
--- /dev/null
+++ b/changelog.d/6077.sdk
@@ -0,0 +1 @@
+Improve replay attacks and reduce duplicate message index errors
diff --git a/changelog.d/6089.misc b/changelog.d/6089.misc
new file mode 100644
index 0000000000..19b951c1a3
--- /dev/null
+++ b/changelog.d/6089.misc
@@ -0,0 +1 @@
+Test: Ensure calling 'fail()' is not caught by the catch block
diff --git a/changelog.d/6098.feature b/changelog.d/6098.feature
new file mode 100644
index 0000000000..659da42094
--- /dev/null
+++ b/changelog.d/6098.feature
@@ -0,0 +1 @@
+Labs flag for enabling live location sharing
diff --git a/changelog.d/6100.misc b/changelog.d/6100.misc
new file mode 100644
index 0000000000..2fb5ecf34d
--- /dev/null
+++ b/changelog.d/6100.misc
@@ -0,0 +1 @@
+Excludes transitive optional non FOSS google location dependency from fdroid builds
diff --git a/changelog.d/6103.bugfix b/changelog.d/6103.bugfix
new file mode 100644
index 0000000000..12e6836460
--- /dev/null
+++ b/changelog.d/6103.bugfix
@@ -0,0 +1 @@
+Glide - Use current drawable while loading new static map image
diff --git a/changelog.d/6109.bugfix b/changelog.d/6109.bugfix
new file mode 100644
index 0000000000..43b1d610c7
--- /dev/null
+++ b/changelog.d/6109.bugfix
@@ -0,0 +1 @@
+Fix sending multiple invites to a room reaching only one or two people
diff --git a/changelog.d/6123.wip b/changelog.d/6123.wip
new file mode 100644
index 0000000000..680498280f
--- /dev/null
+++ b/changelog.d/6123.wip
@@ -0,0 +1 @@
+[Live location sharing] Update entity in DB when a live is timed out
diff --git a/changelog.d/6140.bugfix b/changelog.d/6140.bugfix
new file mode 100644
index 0000000000..247e69f837
--- /dev/null
+++ b/changelog.d/6140.bugfix
@@ -0,0 +1 @@
+Prevent widget web view from reloading on screen / orientation change
diff --git a/changelog.d/6141.misc b/changelog.d/6141.misc
new file mode 100644
index 0000000000..2f0a91b451
--- /dev/null
+++ b/changelog.d/6141.misc
@@ -0,0 +1 @@
+Downgrade gradle from 7.2.0 to 7.1.3
diff --git a/changelog.d/6148.bugfix b/changelog.d/6148.bugfix
new file mode 100644
index 0000000000..3aa623315a
--- /dev/null
+++ b/changelog.d/6148.bugfix
@@ -0,0 +1 @@
+Fix decrypting redacted event from sending errors
diff --git a/dependencies.gradle b/dependencies.gradle
index 36d7e425ac..1d6bfcd3a5 100644
--- a/dependencies.gradle
+++ b/dependencies.gradle
@@ -7,7 +7,10 @@ ext.versions = [
'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
def kotlin = "1.6.21"
def kotlinCoroutines = "1.6.1"
@@ -23,7 +26,7 @@ def mavericks = "2.6.1"
def glide = "4.13.2"
def bigImageViewer = "1.8.1"
def jjwt = "0.11.5"
-def vanniktechEmoji = "0.9.0"
+def vanniktechEmoji = "0.13.0"
// Testing
def mockk = "1.12.4"
@@ -51,7 +54,7 @@ ext.libs = [
'recyclerview' : "androidx.recyclerview:recyclerview:1.2.1",
'exifinterface' : "androidx.exifinterface:exifinterface:1.3.3",
'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",
'autoFill' : "androidx.autofill:autofill:1.1.0",
'preferenceKtx' : "androidx.preference:preference-ktx:1.2.0",
@@ -110,6 +113,10 @@ ext.libs = [
'mavericks' : "com.airbnb.android:mavericks:$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' : "io.mockk:mockk:$mockk",
'mockkAndroid' : "io.mockk:mockk-android:$mockk"
diff --git a/library/ui-styles/src/main/res/drawable/bg_carousel_page_dark.xml b/library/ui-styles/src/main/res/drawable/bg_color_background.xml
similarity index 100%
rename from library/ui-styles/src/main/res/drawable/bg_carousel_page_dark.xml
rename to library/ui-styles/src/main/res/drawable/bg_color_background.xml
diff --git a/library/ui-styles/src/main/res/drawable/bg_waiting_for_email_verification.xml b/library/ui-styles/src/main/res/drawable/bg_waiting_for_email_verification.xml
new file mode 100644
index 0000000000..cdd4c20a4d
--- /dev/null
+++ b/library/ui-styles/src/main/res/drawable/bg_waiting_for_email_verification.xml
@@ -0,0 +1,19 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/library/ui-styles/src/main/res/values/styles_timeline.xml b/library/ui-styles/src/main/res/values/styles_timeline.xml
index c86eeb8efb..20c375c2d6 100644
--- a/library/ui-styles/src/main/res/values/styles_timeline.xml
+++ b/library/ui-styles/src/main/res/values/styles_timeline.xml
@@ -1,5 +1,5 @@
-
+
+
-
\ No newline at end of file
+
diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/Util.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/Util.kt
new file mode 100644
index 0000000000..5e2c2ba25f
--- /dev/null
+++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/Util.kt
@@ -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)
+ }
+}
diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/common/CommonTestHelper.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/common/CommonTestHelper.kt
index 6678b109a6..96ea99d92f 100644
--- a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/common/CommonTestHelper.kt
+++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/common/CommonTestHelper.kt
@@ -96,7 +96,7 @@ class CommonTestHelper(context: Context) {
/**
* This methods init the event stream and check for initial sync
*
- * @param session the session to sync
+ * @param session the session to sync
*/
fun syncSession(session: Session, timeout: Long = TestConstants.timeOutMillis * 10) {
val lock = CountDownLatch(1)
@@ -119,7 +119,7 @@ class CommonTestHelper(context: Context) {
/**
* This methods clear the cache and waits for initialSync
*
- * @param session the session to sync
+ * @param session the session to sync
*/
fun clearCacheAndSync(session: Session, timeout: Long = TestConstants.timeOutMillis) {
waitWithLatch(timeout) { latch ->
@@ -142,8 +142,8 @@ class CommonTestHelper(context: Context) {
/**
* Sends text messages in a room
*
- * @param room the room where to send the messages
- * @param message the message to send
+ * @param room the room where to send the messages
+ * @param message the message to send
* @param nbOfMessages the number of time the message will be sent
*/
fun sendTextMessage(room: Room, message: String, nbOfMessages: Int, timeout: Long = TestConstants.timeOutMillis): List {
@@ -207,8 +207,8 @@ class CommonTestHelper(context: Context) {
/**
* Reply in a thread
- * @param room the room where to send the messages
- * @param message the message to send
+ * @param room the room where to send the messages
+ * @param message the message to send
* @param numberOfMessages the number of time the message will be sent
*/
fun replyInThreadMessage(
@@ -232,8 +232,8 @@ class CommonTestHelper(context: Context) {
* Creates a unique account
*
* @param userNamePrefix the user name prefix
- * @param password the password
- * @param testParams test params about the session
+ * @param password the password
+ * @param testParams test params about the session
* @return the session associated with the newly created account
*/
private fun createAccount(userNamePrefix: String,
@@ -251,8 +251,8 @@ class CommonTestHelper(context: Context) {
/**
* Logs into an existing account
*
- * @param userId the userId to log in
- * @param password the password to log in
+ * @param userId the userId to log in
+ * @param password the password to log in
* @param testParams test params about the session
* @return the session associated with the existing account
*/
@@ -267,8 +267,8 @@ class CommonTestHelper(context: Context) {
/**
* Create an account and a dedicated session
*
- * @param userName the account username
- * @param password the password
+ * @param userName the account username
+ * @param password the password
* @param sessionTestParams parameters for the test
*/
private fun createAccountAndSync(userName: String,
@@ -305,8 +305,8 @@ class CommonTestHelper(context: Context) {
/**
* Start an account login
*
- * @param userName the account username
- * @param password the password
+ * @param userName the account username
+ * @param password the password
* @param sessionTestParams session test params
*/
private fun logAccountAndSync(userName: String,
diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/common/RetryTestRule.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/common/RetryTestRule.kt
index b16ab98e6c..39f49a9ccc 100644
--- a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/common/RetryTestRule.kt
+++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/common/RetryTestRule.kt
@@ -40,6 +40,9 @@ class RetryTestRule(val retryCount: Int = 3) : TestRule {
for (i in 0 until retryCount) {
try {
base.evaluate()
+ if (i > 0) {
+ println("Retried test $i times")
+ }
return
} catch (t: Throwable) {
caughtThrowable = t
diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/DecryptRedactedEventTest.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/DecryptRedactedEventTest.kt
new file mode 100644
index 0000000000..de5fa41581
--- /dev/null
+++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/DecryptRedactedEventTest.kt
@@ -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()?.unsignedData?.redactedEvent?.content?.get("reason")
+ )
+ Assert.assertEquals(
+ "Unexpected Redacted event id",
+ timelineEvent.eventId,
+ result.clearEvent.toModel()?.unsignedData?.redactedEvent?.redacts
+ )
+ } catch (failure: Throwable) {
+ Assert.fail("Should not throw when decrypting a redacted event")
+ }
+ }
+ }
+}
diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/E2eeSanityTests.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/E2eeSanityTests.kt
index 38597269cb..74c218af58 100644
--- a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/E2eeSanityTests.kt
+++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/E2eeSanityTests.kt
@@ -23,6 +23,7 @@ import org.amshove.kluent.fail
import org.amshove.kluent.internal.assertEquals
import org.junit.Assert
import org.junit.FixMethodOrder
+import org.junit.Ignore
import org.junit.Rule
import org.junit.Test
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.TestConstants
import org.matrix.android.sdk.common.TestMatrixCallback
+import org.matrix.android.sdk.mustFail
import java.util.concurrent.CountDownLatch
@RunWith(JUnit4::class)
@FixMethodOrder(MethodSorters.JVM)
@LargeTest
+@Ignore("This test fails with an unhandled exception thrown from a coroutine which terminates the entire test run.")
class E2eeSanityTests : InstrumentedTest {
@get:Rule val rule = RetryTestRule(3)
@@ -525,10 +528,8 @@ class E2eeSanityTests : InstrumentedTest {
// Confirm we can decrypt one but not the other
testHelper.runBlockingTest {
- try {
+ mustFail(message = "Should not be able to decrypt event") {
newBobSession.cryptoService().decryptEvent(firstEventNewBobPov.root, "")
- fail("Should not be able to decrypt event")
- } catch (_: MXCryptoError) {
}
}
diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/gossiping/KeyShareTests.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/gossiping/KeyShareTests.kt
index 1bd2a46381..895f95aeac 100644
--- a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/gossiping/KeyShareTests.kt
+++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/gossiping/KeyShareTests.kt
@@ -21,7 +21,6 @@ import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.LargeTest
import junit.framework.TestCase.assertNotNull
import junit.framework.TestCase.assertTrue
-import junit.framework.TestCase.fail
import org.amshove.kluent.internal.assertEquals
import org.junit.Assert
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.SessionTestParams
import org.matrix.android.sdk.common.TestConstants
+import org.matrix.android.sdk.mustFail
@RunWith(AndroidJUnit4::class)
@FixMethodOrder(MethodSorters.JVM)
@@ -95,12 +95,10 @@ class KeyShareTests : InstrumentedTest {
assertNotNull(receivedEvent)
assert(receivedEvent!!.isEncrypted())
- try {
- commonTestHelper.runBlockingTest {
+ commonTestHelper.runBlockingTest {
+ mustFail {
aliceSession2.cryptoService().decryptEvent(receivedEvent.root, "foo")
}
- fail("should fail")
- } catch (failure: Throwable) {
}
val outgoingRequestsBefore = aliceSession2.cryptoService().getOutgoingRoomKeyRequests()
@@ -168,12 +166,10 @@ class KeyShareTests : InstrumentedTest {
}
}
- try {
- commonTestHelper.runBlockingTest {
+ commonTestHelper.runBlockingTest {
+ mustFail {
aliceSession2.cryptoService().decryptEvent(receivedEvent.root, "foo")
}
- fail("should fail")
- } catch (failure: Throwable) {
}
// Mark the device as trusted
diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/gossiping/WithHeldTests.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/gossiping/WithHeldTests.kt
index 1c3c6c46e7..13133b726c 100644
--- a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/gossiping/WithHeldTests.kt
+++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/gossiping/WithHeldTests.kt
@@ -42,6 +42,7 @@ import org.matrix.android.sdk.common.MockOkHttpInterceptor
import org.matrix.android.sdk.common.RetryTestRule
import org.matrix.android.sdk.common.SessionTestParams
import org.matrix.android.sdk.common.TestConstants
+import org.matrix.android.sdk.mustFail
@RunWith(AndroidJUnit4::class)
@FixMethodOrder(MethodSorters.JVM)
@@ -96,17 +97,19 @@ class WithHeldTests : InstrumentedTest {
// =============================
// Bob should not be able to decrypt because the keys is withheld
- try {
- // .. might need to wait a bit for stability?
- testHelper.runBlockingTest {
+ // .. might need to wait a bit for stability?
+ testHelper.runBlockingTest {
+ mustFail(
+ message = "This session should not be able to decrypt",
+ failureBlock = { failure ->
+ val type = (failure as MXCryptoError.Base).errorType
+ val technicalMessage = failure.technicalMessage
+ Assert.assertEquals("Error should be withheld", MXCryptoError.ErrorType.KEYS_WITHHELD, type)
+ Assert.assertEquals("Cause should be unverified", WithHeldCode.UNVERIFIED.value, technicalMessage)
+ }
+ ) {
bobUnverifiedSession.cryptoService().decryptEvent(eventBobPOV.root, "")
}
- Assert.fail("This session should not be able to decrypt")
- } catch (failure: Throwable) {
- val type = (failure as MXCryptoError.Base).errorType
- val technicalMessage = failure.technicalMessage
- Assert.assertEquals("Error should be withheld", MXCryptoError.ErrorType.KEYS_WITHHELD, type)
- Assert.assertEquals("Cause should be unverified", WithHeldCode.UNVERIFIED.value, technicalMessage)
}
// Let's see if the reply we got from bob first session is unverified
@@ -137,17 +140,18 @@ class WithHeldTests : InstrumentedTest {
}
// Previous message should still be undecryptable (partially withheld session)
- try {
- // .. might need to wait a bit for stability?
- testHelper.runBlockingTest {
+ // .. might need to wait a bit for stability?
+ testHelper.runBlockingTest {
+ mustFail(
+ message = "This session should not be able to decrypt",
+ failureBlock = { failure ->
+ val type = (failure as MXCryptoError.Base).errorType
+ val technicalMessage = failure.technicalMessage
+ Assert.assertEquals("Error should be withheld", MXCryptoError.ErrorType.KEYS_WITHHELD, type)
+ Assert.assertEquals("Cause should be unverified", WithHeldCode.UNVERIFIED.value, technicalMessage)
+ }) {
bobUnverifiedSession.cryptoService().decryptEvent(eventBobPOV.root, "")
}
- Assert.fail("This session should not be able to decrypt")
- } catch (failure: Throwable) {
- val type = (failure as MXCryptoError.Base).errorType
- val technicalMessage = failure.technicalMessage
- Assert.assertEquals("Error should be withheld", MXCryptoError.ErrorType.KEYS_WITHHELD, type)
- Assert.assertEquals("Cause should be unverified", WithHeldCode.UNVERIFIED.value, technicalMessage)
}
testHelper.signOutAndClose(aliceSession)
@@ -190,17 +194,18 @@ class WithHeldTests : InstrumentedTest {
// Previous message should still be undecryptable (partially withheld session)
val eventBobPOV = bobSession.getRoom(testData.roomId)?.getTimelineEvent(eventId)
- try {
- // .. might need to wait a bit for stability?
- testHelper.runBlockingTest {
+ // .. might need to wait a bit for stability?
+ testHelper.runBlockingTest {
+ mustFail(
+ message = "This session should not be able to decrypt",
+ failureBlock = { failure ->
+ val type = (failure as MXCryptoError.Base).errorType
+ val technicalMessage = failure.technicalMessage
+ Assert.assertEquals("Error should be withheld", MXCryptoError.ErrorType.KEYS_WITHHELD, type)
+ Assert.assertEquals("Cause should be unverified", WithHeldCode.NO_OLM.value, technicalMessage)
+ }) {
bobSession.cryptoService().decryptEvent(eventBobPOV!!.root, "")
}
- Assert.fail("This session should not be able to decrypt")
- } catch (failure: Throwable) {
- val type = (failure as MXCryptoError.Base).errorType
- val technicalMessage = failure.technicalMessage
- Assert.assertEquals("Error should be withheld", MXCryptoError.ErrorType.KEYS_WITHHELD, type)
- Assert.assertEquals("Cause should be unverified", WithHeldCode.NO_OLM.value, technicalMessage)
}
// Ensure that alice has marked the session to be shared with bob
diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/keysbackup/KeysBackupTest.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/keysbackup/KeysBackupTest.kt
index 9136272b1e..d5fd299f1f 100644
--- a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/keysbackup/KeysBackupTest.kt
+++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/keysbackup/KeysBackupTest.kt
@@ -24,6 +24,7 @@ import org.junit.Assert.assertNotNull
import org.junit.Assert.assertNull
import org.junit.Assert.assertTrue
import org.junit.FixMethodOrder
+import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
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.common.CommonTestHelper
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.TestMatrixCallback
import java.util.Collections
@@ -55,6 +57,8 @@ import java.util.concurrent.CountDownLatch
@LargeTest
class KeysBackupTest : InstrumentedTest {
+ @get:Rule val rule = RetryTestRule(3)
+
/**
* - From doE2ETestWithAliceAndBobInARoomWithEncryptedMessages, we should have no backed up keys
* - Check backup keys after having marked one as backed up
diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/replayattack/ReplayAttackTest.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/replayattack/ReplayAttackTest.kt
new file mode 100644
index 0000000000..09c340a14f
--- /dev/null
+++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/crypto/replayattack/ReplayAttackTest.kt
@@ -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 {
+ // 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)
+ }
+}
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/data/HomeServerConnectionConfig.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/data/HomeServerConnectionConfig.kt
index c2c1f043bb..c3f0221bb8 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/data/HomeServerConnectionConfig.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/data/HomeServerConnectionConfig.kt
@@ -195,7 +195,7 @@ data class HomeServerConnectionConfig(
* - https://www.ssi.gouv.fr/uploads/2017/07/anssi-guide-recommandations_de_securite_relatives_a_tls-v1.2.pdf
* - https://developer.android.com/reference/javax/net/ssl/SSLEngine
*
- * @param tlsLimitations true to use Tls limitations
+ * @param tlsLimitations true to use Tls limitations
* @param enableCompatibilityMode set to true for Android < 20
* @return this builder
*/
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/content/ContentUrlResolver.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/content/ContentUrlResolver.kt
index e59e676ed9..20f977e86e 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/content/ContentUrlResolver.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/content/ContentUrlResolver.kt
@@ -36,7 +36,7 @@ interface ContentUrlResolver {
/**
* Get the actual URL for accessing the full-size image of a Matrix media content URI.
*
- * @param contentUrl the Matrix media content URI (in the form of "mxc://...").
+ * @param contentUrl the Matrix media content URI (in the form of "mxc://...").
* @return the URL to access the described resource, or null if the url is invalid.
*/
fun resolveFullSize(contentUrl: String?): String?
@@ -44,7 +44,7 @@ interface ContentUrlResolver {
/**
* Get the ResolvedMethod to download a URL.
*
- * @param contentUrl the Matrix media content URI (in the form of "mxc://...").
+ * @param contentUrl the Matrix media content URI (in the form of "mxc://...").
* @param elementToDecrypt Encryption data may be required if you use a content scanner
* @return the Method to access resource, or null if invalid
*/
@@ -54,9 +54,9 @@ interface ContentUrlResolver {
* Get the actual URL for accessing the thumbnail image of a given Matrix media content URI.
*
* @param contentUrl the Matrix media content URI (in the form of "mxc://...").
- * @param width the desired width
- * @param height the desired height
- * @param method the desired method (METHOD_CROP or METHOD_SCALE)
+ * @param width the desired width
+ * @param height the desired height
+ * @param method the desired method (METHOD_CROP or METHOD_SCALE)
* @return the URL to access the described resource, or null if the url is invalid.
*/
fun resolveThumbnail(contentUrl: String?, width: Int, height: Int, method: ThumbnailMethod): String?
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/contentscanner/ContentScannerService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/contentscanner/ContentScannerService.kt
index 7a85a89058..22250628d5 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/contentscanner/ContentScannerService.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/contentscanner/ContentScannerService.kt
@@ -33,7 +33,7 @@ interface ContentScannerService {
/**
* 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 getScanResultForAttachment(mxcUrl: String, fileInfo: ElementToDecrypt? = null): ScanStatusInfo
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/keysbackup/KeysBackupService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/keysbackup/KeysBackupService.kt
index 0d40490c3e..9029c7f8a3 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/keysbackup/KeysBackupService.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/keysbackup/KeysBackupService.kt
@@ -34,7 +34,7 @@ interface KeysBackupService {
* Create a new keys backup version and enable it, using the information return from [prepareKeysBackupVersion].
*
* @param keysBackupCreationInfo the info object from [prepareKeysBackupVersion].
- * @param callback Asynchronous callback
+ * @param callback Asynchronous callback
*/
fun createKeysBackupVersion(keysBackupCreationInfo: MegolmBackupCreationInfo,
callback: MatrixCallback)
@@ -122,7 +122,7 @@ interface KeysBackupService {
* Delete a keys backup version. It will delete all backed up keys on the server, and the backup itself.
* If we are backing up to this version. Backup will be stopped.
*
- * @param version the backup version to delete.
+ * @param version the backup version to delete.
* @param callback Asynchronous callback
*/
fun deleteBackup(version: String,
@@ -173,12 +173,12 @@ interface KeysBackupService {
/**
* Restore a backup with a recovery key from a given backup version stored on the homeserver.
*
- * @param keysVersionResult the backup version to restore from.
- * @param recoveryKey the recovery key to decrypt the retrieved backup.
- * @param roomId the id of the room to get backup data from.
- * @param sessionId the id of the session to restore.
+ * @param keysVersionResult the backup version to restore from.
+ * @param recoveryKey the recovery key to decrypt the retrieved backup.
+ * @param roomId the id of the room to get backup data from.
+ * @param sessionId the id of the session to restore.
* @param stepProgressListener the step progress listener
- * @param callback Callback. It provides the number of found keys and the number of successfully imported keys.
+ * @param callback Callback. It provides the number of found keys and the number of successfully imported keys.
*/
fun restoreKeysWithRecoveryKey(keysVersionResult: KeysVersionResult,
recoveryKey: String, roomId: String?,
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/model/IncomingRoomKeyRequest.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/model/IncomingRoomKeyRequest.kt
index 0c19d275cc..4ff196dd07 100755
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/model/IncomingRoomKeyRequest.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/model/IncomingRoomKeyRequest.kt
@@ -48,8 +48,7 @@ data class IncomingRoomKeyRequest(
/**
* Factory.
*
- * @param event the event
- * @param currentTimeMillis the current time in milliseconds
+ * @param trail the AuditTrail data
*/
fun fromEvent(trail: AuditTrail): IncomingRoomKeyRequest? {
return trail
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/model/MXUsersDevicesMap.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/model/MXUsersDevicesMap.kt
index 744fe74d0d..736ae6b318 100755
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/model/MXUsersDevicesMap.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/model/MXUsersDevicesMap.kt
@@ -46,8 +46,8 @@ class MXUsersDevicesMap {
/**
* Provides the object for a device id and a user Id.
*
+ * @param userId the user id
* @param deviceId the device id
- * @param userId the object id
* @return the object
*/
fun getObject(userId: String?, deviceId: String?): E? {
@@ -59,9 +59,9 @@ class MXUsersDevicesMap {
/**
* Set an object for a dedicated user Id and device Id.
*
- * @param userId the user Id
+ * @param userId the user Id
* @param deviceId the device id
- * @param o the object to set
+ * @param o the object to set
*/
fun setObject(userId: String?, deviceId: String?, o: E?) {
if (null != o && userId?.isNotBlank() == true && deviceId?.isNotBlank() == true) {
@@ -73,8 +73,8 @@ class MXUsersDevicesMap {
/**
* Defines the objects map for a user Id.
*
+ * @param userId the user id
* @param objectsPerDevices the objects maps
- * @param userId the user id
*/
fun setObjects(userId: String?, objectsPerDevices: Map?) {
if (!userId.isNullOrBlank()) {
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/file/FileService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/file/FileService.kt
index 84a9990826..a7c81136e3 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/file/FileService.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/file/FileService.kt
@@ -33,7 +33,7 @@ interface FileService {
/**
* The original file is in cache, but the decrypted files can be deleted for security reason.
* To decrypt the file again, call [downloadFile], the encrypted file will not be downloaded again
- * @param decryptedFileInCache true if the decrypted file is available. Always true for clear files.
+ * @property decryptedFileInCache true if the decrypted file is available. Always true for clear files.
*/
data class InCache(val decryptedFileInCache: Boolean) : FileState()
object Downloading : FileState()
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/identity/IdentityService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/identity/IdentityService.kt
index c03b42e6c8..2fb35d38e3 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/identity/IdentityService.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/identity/IdentityService.kt
@@ -74,6 +74,7 @@ interface IdentityService {
/**
* Submit the code that the identity server has sent to the user (in email or SMS).
* Once successful, you will have to call [finalizeBindThreePid]
+ * @param threePid the three pid
* @param code the code sent to the user
*/
suspend fun submitValidationToken(threePid: ThreePid, code: String)
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/integrationmanager/IntegrationManagerService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/integrationmanager/IntegrationManagerService.kt
index 60af93888e..5b15a0cb13 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/integrationmanager/IntegrationManagerService.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/integrationmanager/IntegrationManagerService.kt
@@ -99,6 +99,7 @@ interface IntegrationManagerService {
* Offers to allow or disallow a native widget domain.
* @param widgetType the widget type to check for
* @param domain the domain to check for
+ * @param allowed true or false
*/
suspend fun setNativeWidgetDomainAllowed(widgetType: String, domain: String, allowed: Boolean)
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/permalinks/MatrixLinkify.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/permalinks/MatrixLinkify.kt
index c5d919407a..c428e40203 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/permalinks/MatrixLinkify.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/permalinks/MatrixLinkify.kt
@@ -29,6 +29,7 @@ object MatrixLinkify {
* 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 callback listener to be notified when the span is clicked
*/
@Suppress("UNUSED_PARAMETER")
fun addLinks(spannable: Spannable, callback: MatrixPermalinkSpan.Callback?): Boolean {
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/permalinks/MatrixPermalinkSpan.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/permalinks/MatrixPermalinkSpan.kt
index 2f8f5f99a5..48b30dfa21 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/permalinks/MatrixPermalinkSpan.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/permalinks/MatrixPermalinkSpan.kt
@@ -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.
- * @param url the permalink url tied to the span
- * @param callback the callback to use.
+ * @property url the permalink url tied to the span
+ * @property callback the callback to use.
*/
class MatrixPermalinkSpan(private val url: String,
private val callback: Callback? = null) : ClickableSpan() {
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/permalinks/PermalinkService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/permalinks/PermalinkService.kt
index b49b80df09..1788bf7bd2 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/permalinks/PermalinkService.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/permalinks/PermalinkService.kt
@@ -60,6 +60,7 @@ interface PermalinkService {
* Creates a permalink for a roomId, including the via parameters.
*
* @param roomId the room id
+ * @param viaServers the via parameter
* @param forceMatrixTo whether we should force using matrix.to base URL
*
* @return the permalink, or null in case of error
@@ -70,7 +71,7 @@ interface PermalinkService {
* Creates a permalink for an event. If you have an event you can use [createPermalink]
* Ex: "https://matrix.to/#/!nbzmcXAqpxBXjAdgoX:matrix.org/$1531497316352799BevdV:matrix.org?via=matrix.org"
*
- * @param roomId the id of the room
+ * @param roomId the id of the room
* @param eventId the id of the event
* @param forceMatrixTo whether we should force using matrix.to base URL
*
@@ -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.
* Ex: "%2\$s" 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
*
* @return the created template
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/pushers/PushersService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/pushers/PushersService.kt
index 5f9857eb2f..5cb7857021 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/pushers/PushersService.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/pushers/PushersService.kt
@@ -47,12 +47,12 @@ interface PushersService {
* Add a new Email pusher.
* Ref: https://matrix.org/docs/spec/client_server/latest#post-matrix-client-r0-pushers-set
*
- * @param email The email address to send notifications to.
- * @param lang The preferred language for receiving notifications (e.g. "en" or "en-US").
- * @param emailBranding The branding placeholder to include in the email communications.
- * @param appDisplayName A human readable string that will allow the user to identify what application owns this pusher.
+ * @param email The email address to send notifications to.
+ * @param lang The preferred language for receiving notifications (e.g. "en" or "en-US").
+ * @param emailBranding The branding placeholder to include in the email communications.
+ * @param appDisplayName A human readable string that will allow the user to identify what application owns this pusher.
* @param deviceDisplayName A human readable string that will allow the user to identify what device owns this pusher.
- * @param append If true, the homeserver should add another pusher with the given pushkey and App ID in addition
+ * @param append If true, the homeserver should add another pusher with the given pushkey and App ID in addition
* to any others with different user IDs. Otherwise, the homeserver must remove any other pushers
* with the same App ID and pushkey for different users. Typically We always want to append for
* email pushers since we don't want to stop other accounts notifying to the same email address.
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/pushrules/PushRuleService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/pushrules/PushRuleService.kt
index bc4860be11..7ffbc89559 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/pushrules/PushRuleService.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/pushrules/PushRuleService.kt
@@ -34,10 +34,11 @@ interface PushRuleService {
/**
* 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 actions Actions to update if not null
*/
-
suspend fun updatePushRuleActions(kind: RuleKind, ruleId: String, enable: Boolean, actions: List?)
suspend fun removePushRule(kind: RuleKind, ruleId: String)
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/pushrules/rest/RuleSet.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/pushrules/rest/RuleSet.kt
index 5bf42b8252..9498ed002c 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/pushrules/rest/RuleSet.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/pushrules/rest/RuleSet.kt
@@ -67,7 +67,7 @@ data class RuleSet(
/**
* Find a rule from its rule Id.
*
- * @param rules the rules list.
+ * @param rules the rules list.
* @param ruleId the rule Id.
* @return the bing rule if it exists, else null.
*/
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/crypto/RoomCryptoService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/crypto/RoomCryptoService.kt
index 6967e0c455..6064643820 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/crypto/RoomCryptoService.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/crypto/RoomCryptoService.kt
@@ -28,7 +28,8 @@ interface RoomCryptoService {
/**
* 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
* be used by admins to fix misconfigured encryption.
*/
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/livelocation/LiveLocationShareAggregatedSummary.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/livelocation/LiveLocationShareAggregatedSummary.kt
index 0b28d62f56..059fe21471 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/livelocation/LiveLocationShareAggregatedSummary.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/livelocation/LiveLocationShareAggregatedSummary.kt
@@ -22,6 +22,9 @@ import org.matrix.android.sdk.api.session.room.model.message.MessageBeaconLocati
* Aggregation info concerning a live location share.
*/
data class LiveLocationShareAggregatedSummary(
+ /**
+ * Indicate whether the live is currently running.
+ */
val isActive: Boolean?,
val endOfLiveTimestampMillis: Long?,
val lastLocationDataContent: MessageBeaconLocationDataContent?,
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/relation/RelationService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/relation/RelationService.kt
index 0d094b835b..02c597ee63 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/relation/RelationService.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/relation/RelationService.kt
@@ -71,8 +71,8 @@ interface RelationService {
/**
* Edit a poll.
- * @param pollType indicates open or closed polls
* @param targetEvent The poll event to edit
+ * @param pollType indicates open or closed polls
* @param question The edited question
* @param options The edited options
*/
@@ -84,7 +84,9 @@ interface RelationService {
/**
* Edit a text message body. Limited to "m.text" contentType.
* @param targetEvent The event to edit
+ * @param msgType the message type
* @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
*/
fun editTextMessage(targetEvent: TimelineEvent,
@@ -153,8 +155,8 @@ interface RelationService {
* @param rootThreadEventId the root thread eventId
* @param replyInThreadText the reply text
* @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 formattedText The formatted body using MessageType#FORMAT_MATRIX_HTML
* @param eventReplied the event referenced by the reply within a thread
*/
fun replyInThread(rootThreadEventId: String,
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/powerlevels/PowerLevelsHelper.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/powerlevels/PowerLevelsHelper.kt
index 165a912b7f..36993074aa 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/powerlevels/PowerLevelsHelper.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/powerlevels/PowerLevelsHelper.kt
@@ -58,7 +58,7 @@ class PowerLevelsHelper(private val powerLevelsContent: PowerLevelsContent) {
/**
* Tell if an user can send an event of a certain type.
*
- * @param userId the id of the user to check for.
+ * @param userId the id of the user to check for.
* @param isState true if the event is a state event (ie. state key is not null)
* @param eventType the event type to check for
* @return true if the user can send this type of event
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/read/ReadService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/read/ReadService.kt
index 036628c02f..dac1a1a773 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/read/ReadService.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/read/ReadService.kt
@@ -71,7 +71,7 @@ interface ReadService {
/**
* Returns a live list of read receipts for a given event.
- * @param eventId: the event
+ * @param eventId the event
*/
fun getEventReadReceiptsLive(eventId: String): LiveData>
}
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/send/SendService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/send/SendService.kt
index 4bb8abef8a..c2e3ded2fa 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/send/SendService.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/send/SendService.kt
@@ -62,6 +62,7 @@ interface SendService {
* @param quotedEvent The event to which we will quote it's content.
* @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 rootThreadEventId when this param is not null, the message will be sent in this specific thread
* @return a [Cancelable]
*/
fun sendQuotedTextMessage(quotedEvent: TimelineEvent, text: String, autoMarkdown: Boolean, rootThreadEventId: String? = null): Cancelable
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/state/StateService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/state/StateService.kt
index f6b56128d3..c79171f156 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/state/StateService.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/state/StateService.kt
@@ -90,23 +90,29 @@ interface StateService {
/**
* 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?
/**
* 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>
/**
* Get state events of the room.
* @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, stateKey: QueryStringValue = QueryStringValue.NoCondition): List
/**
* Get live state events of the room.
* @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, stateKey: QueryStringValue = QueryStringValue.NoCondition): LiveData>
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/timeline/TimelineEvent.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/timeline/TimelineEvent.kt
index d05fdb951f..d4ade9b5b9 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/timeline/TimelineEvent.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/timeline/TimelineEvent.kt
@@ -90,6 +90,7 @@ data class TimelineEvent(
/**
* Get the metadata associated with a key.
+ * @param T type to cast the metadata to
* @param key the key to get the metadata
* @return the metadata
*/
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/securestorage/SharedSecretStorageService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/securestorage/SharedSecretStorageService.kt
index 528e071966..e3a9860523 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/securestorage/SharedSecretStorageService.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/securestorage/SharedSecretStorageService.kt
@@ -92,7 +92,7 @@ interface SharedSecretStorageService {
* Clients MUST ensure that the key is trusted before using it to encrypt secrets.
*
* @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.
*/
suspend fun storeSecret(name: String, secretBase64: String, keys: List)
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/space/SpaceService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/space/SpaceService.kt
index 8f16b3b9c3..38e55664d2 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/space/SpaceService.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/space/SpaceService.kt
@@ -44,8 +44,8 @@ interface SpaceService {
roomAliasLocalPart: String? = null): String
/**
- * Get a space from a roomId.
- * @param spaceId the roomId to look for.
+ * Get a space from a spaceId.
+ * @param spaceId the spaceId to look for.
* @return a space with spaceId or null if room type is not space
*/
fun getSpace(spaceId: String): Space?
@@ -54,21 +54,24 @@ interface SpaceService {
* Try to resolve (peek) rooms and subspace in this space.
* Use this call get preview of children of this space, particularly useful to get a
* preview of rooms that you did not join yet.
+ * @param spaceId the spaceId to look for.
*/
suspend fun peekSpace(spaceId: String): SpacePeekResult
/**
* 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 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.
+ * @param knownStateList when paginating, pass back the m.space.child state events
*/
suspend fun querySpaceChildren(spaceId: String,
suggestedOnly: Boolean? = null,
limit: Int? = null,
from: String? = null,
- // when paginating, pass back the m.space.child state events
knownStateList: List? = null): SpaceHierarchyData
/**
@@ -98,7 +101,10 @@ interface SpaceService {
/**
* 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 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:
* if multiple are present the client should select the one with the lowest room ID, as determined via a lexicographic utf-8 ordering.
*/
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/widgets/WidgetPostAPIMediator.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/widgets/WidgetPostAPIMediator.kt
index edb49f4797..0c224ff17c 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/widgets/WidgetPostAPIMediator.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/widgets/WidgetPostAPIMediator.kt
@@ -49,7 +49,7 @@ interface WidgetPostAPIMediator {
/**
* Send a boolean response.
*
- * @param response the response
+ * @param response the response
* @param eventData the modular data
*/
fun sendBoolResponse(response: Boolean, eventData: JsonDict)
@@ -57,7 +57,7 @@ interface WidgetPostAPIMediator {
/**
* Send an integer response.
*
- * @param response the response
+ * @param response the response
* @param eventData the modular data
*/
fun sendIntegerResponse(response: Int, eventData: JsonDict)
@@ -65,8 +65,9 @@ interface WidgetPostAPIMediator {
/**
* Send an object response.
*
- * @param klass the class of the response
- * @param response the response
+ * @param T the generic type
+ * @param type the type of the response
+ * @param response the response
* @param eventData the modular data
*/
fun sendObjectResponse(type: Type, response: T?, eventData: JsonDict)
@@ -81,7 +82,7 @@ interface WidgetPostAPIMediator {
/**
* Send an error.
*
- * @param message the error message
+ * @param message the error message
* @param eventData the modular data
*/
fun sendError(message: String, eventData: JsonDict)
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/widgets/WidgetService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/widgets/WidgetService.kt
index b06f8f7bc6..8ad6500d25 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/widgets/WidgetService.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/widgets/WidgetService.kt
@@ -111,8 +111,8 @@ interface WidgetService {
/**
* 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 widgetId: the widget to deactivate.
+ * @param roomId the room where you want to deactivate the widget.
+ * @param widgetId the widget to deactivate.
*/
suspend fun destroyRoomWidget(roomId: String, widgetId: String)
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/CryptoSessionInfoProvider.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/CryptoSessionInfoProvider.kt
index b3e9eab988..eee1ee70aa 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/CryptoSessionInfoProvider.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/CryptoSessionInfoProvider.kt
@@ -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
*/
fun getRoomUserIds(roomId: String, allActive: Boolean): List {
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/DefaultCryptoService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/DefaultCryptoService.kt
index 11fa93dbe0..824478f1d3 100755
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/DefaultCryptoService.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/DefaultCryptoService.kt
@@ -508,7 +508,7 @@ internal class DefaultCryptoService @Inject constructor(
/**
* Provides the device information for a user id and a device Id.
*
- * @param userId the user id
+ * @param userId the user id
* @param deviceId the device id
*/
override fun getDeviceInfo(userId: String, deviceId: String?): CryptoDeviceInfo? {
@@ -538,7 +538,7 @@ internal class DefaultCryptoService @Inject constructor(
/**
* Set the devices as known.
*
- * @param devices the devices. Note that the verified member of the devices in this list will not be updated by this method.
+ * @param devices the devices. Note that the verified member of the devices in this list will not be updated by this method.
* @param callback the asynchronous callback
*/
override fun setDevicesKnown(devices: List, callback: MatrixCallback?) {
@@ -576,8 +576,8 @@ internal class DefaultCryptoService @Inject constructor(
* Update the blocked/verified state of the given device.
*
* @param trustLevel the new trust level
- * @param userId the owner of the device
- * @param deviceId the unique identifier for the device.
+ * @param userId the owner of the device
+ * @param deviceId the unique identifier for the device.
*/
override fun setDeviceVerification(trustLevel: DeviceTrustLevel, userId: String, deviceId: String) {
setDeviceVerificationAction.handle(trustLevel, userId, deviceId)
@@ -586,10 +586,10 @@ internal class DefaultCryptoService @Inject constructor(
/**
* Configure a room to use encryption.
*
- * @param roomId the room id to enable encryption in.
- * @param algorithm the encryption config for the room.
+ * @param roomId the room id to enable encryption in.
+ * @param algorithm the encryption config for the room.
* @param inhibitDeviceQuery true to suppress device list query for users in the room (for now)
- * @param membersId list of members to start tracking their devices
+ * @param membersId list of members to start tracking their devices
* @return true if the operation succeeds.
*/
private suspend fun setEncryptionInRoom(roomId: String,
@@ -687,9 +687,9 @@ internal class DefaultCryptoService @Inject constructor(
* Encrypt an event content according to the configuration of the room.
*
* @param eventContent the content of the event.
- * @param eventType the type of the event.
- * @param roomId the room identifier the event will be sent.
- * @param callback the asynchronous callback
+ * @param eventType the type of the event.
+ * @param roomId the room identifier the event will be sent.
+ * @param callback the asynchronous callback
*/
override fun encryptEventContent(eventContent: Content,
eventType: String,
@@ -742,7 +742,7 @@ internal class DefaultCryptoService @Inject constructor(
/**
* Decrypt an event.
*
- * @param event the raw event.
+ * @param event the raw event.
* @param timeline the id of the timeline where the event is decrypted. It is used to prevent replay attack.
* @return the MXEventDecryptionResult data, or throw in case of error
*/
@@ -754,7 +754,7 @@ internal class DefaultCryptoService @Inject constructor(
/**
* Decrypt an event asynchronously.
*
- * @param event the raw event.
+ * @param event the raw event.
* @param timeline the id of the timeline where the event is decrypted. It is used to prevent replay attack.
* @param callback the callback to return data or null
*/
@@ -765,7 +765,7 @@ internal class DefaultCryptoService @Inject constructor(
/**
* Decrypt an event.
*
- * @param event the raw event.
+ * @param event the raw event.
* @param timeline the id of the timeline where the event is decrypted. It is used to prevent replay attack.
* @return the MXEventDecryptionResult data, or null in case of error
*/
@@ -905,6 +905,7 @@ internal class DefaultCryptoService @Inject constructor(
/**
* Handle an m.room.encryption event.
*
+ * @param roomId the room Id
* @param event the encryption 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.
*
+ * @param roomId the room Id
* @param event the membership event causing the change
*/
private fun onRoomMembershipEvent(roomId: String, event: Event) {
@@ -996,7 +998,7 @@ internal class DefaultCryptoService @Inject constructor(
/**
* Export the crypto keys.
*
- * @param password the password
+ * @param password the password
* @param anIterationCount the encryption iteration count (0 means no encryption)
*/
private suspend fun exportRoomKeys(password: String, anIterationCount: Int): ByteArray {
@@ -1015,8 +1017,8 @@ internal class DefaultCryptoService @Inject constructor(
/**
* Import the room keys.
*
- * @param roomKeysAsArray the room keys as array.
- * @param password the password
+ * @param roomKeysAsArray the room keys as array.
+ * @param password the password
* @param progressListener the progress listener
* @return the result ImportRoomKeysResult
*/
@@ -1066,7 +1068,7 @@ internal class DefaultCryptoService @Inject constructor(
* A success means there is no unknown devices.
* If there are some unknown devices, a MXCryptoError.UnknownDevice exception is triggered.
*
- * @param userIds the user ids list
+ * @param userIds the user ids list
* @param callback the asynchronous callback.
*/
fun checkUnknownDevices(userIds: List, callback: MatrixCallback) {
@@ -1089,7 +1091,7 @@ internal class DefaultCryptoService @Inject constructor(
* If false, it can still be overridden per-room.
* If true, it overrides the per-room settings.
*
- * @param block true to unilaterally blacklist all
+ * @param block true to unilaterally blacklist all
*/
override fun setGlobalBlacklistUnverifiedDevices(block: Boolean) {
cryptoStore.setGlobalBlacklistUnverifiedDevices(block)
@@ -1129,8 +1131,8 @@ internal class DefaultCryptoService @Inject constructor(
/**
* Manages the room black-listing for unverified devices.
*
- * @param roomId the room id
- * @param add true to add the room id to the list, false to remove it.
+ * @param roomId the room id
+ * @param add true to add the room id to the list, false to remove it.
*/
private fun setRoomBlacklistUnverifiedDevices(roomId: String, add: Boolean) {
val roomIds = cryptoStore.getRoomsListBlacklistUnverifiedDevices().toMutableList()
@@ -1149,7 +1151,7 @@ internal class DefaultCryptoService @Inject constructor(
/**
* Add this room to the ones which don't encrypt messages to unverified devices.
*
- * @param roomId the room id
+ * @param roomId the room id
*/
override fun setRoomBlacklistUnverifiedDevices(roomId: String) {
setRoomBlacklistUnverifiedDevices(roomId, true)
@@ -1158,7 +1160,7 @@ internal class DefaultCryptoService @Inject constructor(
/**
* Remove this room to the ones which don't encrypt messages to unverified devices.
*
- * @param roomId the room id
+ * @param roomId the room id
*/
override fun setRoomUnBlacklistUnverifiedDevices(roomId: String) {
setRoomBlacklistUnverifiedDevices(roomId, false)
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/DeviceListManager.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/DeviceListManager.kt
index cd4e2a6d52..18b815b3d8 100755
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/DeviceListManager.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/DeviceListManager.kt
@@ -170,7 +170,7 @@ internal class DeviceListManager @Inject constructor(
* Update the devices list statuses.
*
* @param changed the user ids list which have new devices
- * @param left the user ids list which left a room
+ * @param left the user ids list which left a room
*/
fun handleDeviceListsChanges(changed: Collection, left: Collection) {
Timber.v("## CRYPTO: handleDeviceListsChanges changed: ${changed.logLimit()} / left: ${left.logLimit()}")
@@ -223,7 +223,7 @@ internal class DeviceListManager @Inject constructor(
/**
* The keys download succeeded.
*
- * @param userIds the userIds list
+ * @param userIds the userIds list
* @param failures the failure map.
*/
private fun onKeysDownloadSucceed(userIds: List, failures: Map>?): MXUsersDevicesMap {
@@ -276,7 +276,7 @@ internal class DeviceListManager @Inject constructor(
* Download the device keys for a list of users and stores the keys in the MXStore.
* It must be called in getEncryptingThreadHandler() thread.
*
- * @param userIds The users to fetch.
+ * @param userIds The users to fetch.
* @param forceDownload Always download the keys even if cached.
*/
suspend fun downloadKeys(userIds: List?, forceDownload: Boolean): MXUsersDevicesMap {
@@ -421,9 +421,9 @@ internal class DeviceListManager @Inject constructor(
* Validate device keys.
* This method must called on getEncryptingThreadHandler() thread.
*
- * @param deviceKeys the device keys to validate.
- * @param userId the id of the user of the device.
- * @param deviceId the id of the device.
+ * @param deviceKeys the device keys to validate.
+ * @param userId the id of the user of the device.
+ * @param deviceId the id of the device.
* @param previouslyStoredDeviceKeys the device keys we received before for this device
* @return true if succeeds
*/
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/EventDecryptor.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/EventDecryptor.kt
index d6f881211c..c1d04eb22b 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/EventDecryptor.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/EventDecryptor.kt
@@ -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.EventType
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.internal.crypto.actions.EnsureOlmSessionsForDevicesAction
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 val loggerTag = LoggerTag("CryptoSyncHandler", LoggerTag.CRYPTO)
+private val loggerTag = LoggerTag("EventDecryptor", LoggerTag.CRYPTO)
@SessionScope
internal class EventDecryptor @Inject constructor(
@@ -72,7 +73,7 @@ internal class EventDecryptor @Inject constructor(
/**
* Decrypt an event.
*
- * @param event the raw event.
+ * @param event the raw event.
* @param timeline the id of the timeline where the event is decrypted. It is used to prevent replay attack.
* @return the MXEventDecryptionResult data, or throw in case of error
*/
@@ -84,7 +85,7 @@ internal class EventDecryptor @Inject constructor(
/**
* Decrypt an event asynchronously.
*
- * @param event the raw event.
+ * @param event the raw event.
* @param timeline the id of the timeline where the event is decrypted. It is used to prevent replay attack.
* @param callback the callback to return data or null
*/
@@ -100,7 +101,7 @@ internal class EventDecryptor @Inject constructor(
/**
* Decrypt an event.
*
- * @param event the raw event.
+ * @param event the raw event.
* @param timeline the id of the timeline where the event is decrypted. It is used to prevent replay attack.
* @return the MXEventDecryptionResult data, or null in case of error
*/
@@ -110,6 +111,16 @@ internal class EventDecryptor @Inject constructor(
if (eventContent == null) {
Timber.tag(loggerTag.value).e("decryptEvent : empty event content")
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(),
+ "unsigned" to event.unsignedData.toContent()
+ )
+ )
} else {
val algorithm = eventContent["algorithm"]?.toString()
val alg = roomDecryptorProvider.getOrCreateRoomDecryptor(event.roomId, algorithm)
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/MXMegolmExportEncryption.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/MXMegolmExportEncryption.kt
index e4a0f0376e..e0d6c25d70 100755
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/MXMegolmExportEncryption.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/MXMegolmExportEncryption.kt
@@ -68,7 +68,7 @@ internal object MXMegolmExportEncryption {
/**
* Decrypt a megolm key file.
*
- * @param data the data to decrypt
+ * @param data the data to decrypt
* @param password the password.
* @return the decrypted output.
* @throws Exception the failure reason
@@ -138,9 +138,9 @@ internal object MXMegolmExportEncryption {
/**
* Encrypt a string into the megolm export format.
*
- * @param data the data to encrypt.
- * @param password the password
- * @param kdf_rounds the iteration count
+ * @param data the data to encrypt.
+ * @param password the password
+ * @param kdfRounds the iteration count
* @return the encrypted data
* @throws Exception the failure reason
*/
@@ -304,9 +304,9 @@ internal object MXMegolmExportEncryption {
/**
* Derive the AES and HMAC-SHA-256 keys for the file.
*
- * @param salt salt for pbkdf
+ * @param salt salt for pbkdf
* @param iterations number of pbkdf iterations
- * @param password password
+ * @param password password
* @return the derived keys
*/
@Throws(Exception::class)
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/MXOlmDevice.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/MXOlmDevice.kt
index 5620cbf769..1d25d82549 100755
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/MXOlmDevice.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/MXOlmDevice.kt
@@ -96,8 +96,9 @@ internal class MXOlmDevice @Inject constructor(
// So, store these message indexes per timeline id.
//
// The first level keys are timeline ids.
- // The second level keys are strings of form "||"
- private val inboundGroupSessionMessageIndexes: MutableMap> = HashMap()
+ // The second level values is a Map that represents:
+ // "|||" --> eventId
+ private val inboundGroupSessionMessageIndexes: MutableMap> = HashMap()
init {
// Retrieve the account from the store
@@ -260,7 +261,7 @@ internal class MXOlmDevice @Inject constructor(
* The new session will be stored in the MXStore.
*
* @param theirIdentityKey the remote user's Curve25519 identity key
- * @param theirOneTimeKey the remote user's one-time Curve25519 key
+ * @param theirOneTimeKey the remote user's one-time Curve25519 key
* @return the session id for the outbound session.
*/
fun createOutboundSession(theirIdentityKey: String, theirOneTimeKey: String): String? {
@@ -299,8 +300,8 @@ internal class MXOlmDevice @Inject constructor(
* Generate a new inbound session, given an incoming message.
*
* @param theirDeviceIdentityKey the remote user's Curve25519 identity key.
- * @param messageType the message_type field from the received message (must be 0).
- * @param ciphertext base64-encoded body from the received message.
+ * @param messageType the message_type field from the received message (must be 0).
+ * @param ciphertext base64-encoded body from the received message.
* @return {{payload: string, session_id: string}} decrypted payload, and session id of new session.
*/
fun createInboundSession(theirDeviceIdentityKey: String, messageType: Int, ciphertext: String): Map? {
@@ -394,8 +395,8 @@ internal class MXOlmDevice @Inject constructor(
* Encrypt an outgoing message using an existing session.
*
* @param theirDeviceIdentityKey the Curve25519 identity key for the remote device.
- * @param sessionId the id of the active session
- * @param payloadString the payload to be encrypted and sent
+ * @param sessionId the id of the active session
+ * @param payloadString the payload to be encrypted and sent
* @return the cipher text
*/
suspend fun encryptMessage(theirDeviceIdentityKey: String, sessionId: String, payloadString: String): Map? {
@@ -427,10 +428,10 @@ internal class MXOlmDevice @Inject constructor(
/**
* Decrypt an incoming message using an existing session.
*
- * @param ciphertext the base64-encoded body from the received message.
- * @param messageType message_type field from the received message.
+ * @param ciphertext the base64-encoded body from the received message.
+ * @param messageType message_type field from the received message.
+ * @param sessionId the id of the active session.
* @param theirDeviceIdentityKey the Curve25519 identity key for the remote device.
- * @param sessionId the id of the active session.
* @return the decrypted payload.
*/
@kotlin.jvm.Throws
@@ -460,9 +461,9 @@ internal class MXOlmDevice @Inject constructor(
* Determine if an incoming messages is a prekey message matching an existing session.
*
* @param theirDeviceIdentityKey the Curve25519 identity key for the remote device.
- * @param sessionId the id of the active session.
- * @param messageType message_type field from the received message.
- * @param ciphertext the base64-encoded body from the received message.
+ * @param sessionId the id of the active session.
+ * @param messageType message_type field from the received message.
+ * @param ciphertext the base64-encoded body from the received message.
* @return YES if the received message is a prekey message which matchesthe given session.
*/
fun matchesSession(theirDeviceIdentityKey: String, sessionId: String, messageType: Int, ciphertext: String): Boolean {
@@ -563,7 +564,7 @@ internal class MXOlmDevice @Inject constructor(
/**
* Encrypt an outgoing message with an outbound group session.
*
- * @param sessionId the id of the outbound group session.
+ * @param sessionId the id of the outbound group session.
* @param payloadString the payload to be encrypted and sent.
* @return ciphertext
*/
@@ -590,13 +591,13 @@ internal class MXOlmDevice @Inject constructor(
/**
* Add an inbound group session to the session store.
*
- * @param sessionId the session identifier.
- * @param sessionKey base64-encoded secret key.
- * @param roomId the id of the room in which this session will be used.
- * @param senderKey the base64-encoded curve25519 key of the sender.
+ * @param sessionId the session identifier.
+ * @param sessionKey base64-encoded secret key.
+ * @param roomId the id of the room in which this session will be used.
+ * @param senderKey the base64-encoded curve25519 key of the sender.
* @param forwardingCurve25519KeyChain Devices involved in forwarding this session to us.
- * @param keysClaimed Other keys the sender claims.
- * @param exportFormat true if the megolm keys are in export format
+ * @param keysClaimed Other keys the sender claims.
+ * @param exportFormat true if the megolm keys are in export format
* @return true if the operation succeeds.
*/
fun addInboundGroupSession(sessionId: String,
@@ -752,70 +753,75 @@ internal class MXOlmDevice @Inject constructor(
/**
* Decrypt a received message with an inbound group session.
*
- * @param body the base64-encoded body of the encrypted message.
- * @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 body the base64-encoded body of the encrypted message.
+ * @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 eventId the eventId of the message that will be decrypted
* @param sessionId the session identifier.
* @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)
suspend fun decryptGroupMessage(body: String,
roomId: String,
timeline: String?,
+ eventId: String,
sessionId: String,
senderKey: String): OlmDecryptionResult {
val sessionHolder = getInboundGroupSession(sessionId, senderKey, roomId)
val wrapper = sessionHolder.wrapper
val inboundGroupSession = wrapper.olmInboundGroupSession
?: throw MXCryptoError.Base(MXCryptoError.ErrorType.UNABLE_TO_DECRYPT, "Session is null")
- // Check that the room id matches the original one for the session. This stops
- // the HS pretending a message was targeting a different room.
- if (roomId == wrapper.roomId) {
- val decryptResult = try {
- sessionHolder.mutex.withLock {
- inboundGroupSession.decryptMessage(body)
- }
- } catch (e: OlmException) {
- Timber.tag(loggerTag.value).e(e, "## decryptGroupMessage () : decryptMessage failed")
- throw MXCryptoError.OlmError(e)
- }
-
- if (timeline?.isNotBlank() == true) {
- val timelineSet = inboundGroupSessionMessageIndexes.getOrPut(timeline) { mutableSetOf() }
-
- val messageIndexKey = senderKey + "|" + sessionId + "|" + decryptResult.mIndex
-
- if (timelineSet.contains(messageIndexKey)) {
- val reason = String.format(MXCryptoError.DUPLICATE_MESSAGE_INDEX_REASON, decryptResult.mIndex)
- Timber.tag(loggerTag.value).e("## decryptGroupMessage() timelineId=$timeline: $reason")
- throw MXCryptoError.Base(MXCryptoError.ErrorType.DUPLICATED_MESSAGE_INDEX, reason)
- }
-
- timelineSet.add(messageIndexKey)
- }
-
- inboundGroupSessionStore.storeInBoundGroupSession(sessionHolder, sessionId, senderKey)
- val payload = try {
- val adapter = MoshiProvider.providesMoshi().adapter(JSON_DICT_PARAMETERIZED_TYPE)
- val payloadString = convertFromUTF8(decryptResult.mDecryptedMessage)
- adapter.fromJson(payloadString)
- } catch (e: Exception) {
- Timber.tag(loggerTag.value).e("## decryptGroupMessage() : fails to parse the payload")
- throw MXCryptoError.Base(MXCryptoError.ErrorType.BAD_DECRYPTED_FORMAT, MXCryptoError.BAD_DECRYPTED_FORMAT_TEXT_REASON)
- }
-
- return OlmDecryptionResult(
- payload,
- wrapper.keysClaimed,
- senderKey,
- wrapper.forwardingCurve25519KeyChain
- )
- } else {
+ if (roomId != wrapper.roomId) {
+ // Check that the room id matches the original one for the session. This stops
+ // the HS pretending a message was targeting a different room.
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 {
+ sessionHolder.mutex.withLock {
+ inboundGroupSession.decryptMessage(body)
+ }
+ } catch (e: OlmException) {
+ Timber.tag(loggerTag.value).e(e, "## decryptGroupMessage () : decryptMessage failed")
+ 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) {
+ val replayAttackMap = inboundGroupSessionMessageIndexes.getOrPut(timeline) { mutableMapOf() }
+ if (replayAttackMap.contains(messageIndexKey) && replayAttackMap[messageIndexKey] != eventId) {
+ val reason = String.format(MXCryptoError.DUPLICATE_MESSAGE_INDEX_REASON, decryptResult.mIndex)
+ Timber.tag(loggerTag.value).e("## decryptGroupMessage() timelineId=$timeline: $reason")
+ throw MXCryptoError.Base(MXCryptoError.ErrorType.DUPLICATED_MESSAGE_INDEX, reason)
+ }
+ replayAttackMap[messageIndexKey] = eventId
+ }
+ inboundGroupSessionStore.storeInBoundGroupSession(sessionHolder, sessionId, senderKey)
+ val payload = try {
+ val adapter = MoshiProvider.providesMoshi().adapter(JSON_DICT_PARAMETERIZED_TYPE)
+ val payloadString = convertFromUTF8(decryptResult.mDecryptedMessage)
+ adapter.fromJson(payloadString)
+ } catch (e: Exception) {
+ Timber.tag(loggerTag.value).e("## decryptGroupMessage() : fails to parse the payload")
+ throw MXCryptoError.Base(MXCryptoError.ErrorType.BAD_DECRYPTED_FORMAT, MXCryptoError.BAD_DECRYPTED_FORMAT_TEXT_REASON)
+ }
+
+ return OlmDecryptionResult(
+ payload,
+ wrapper.keysClaimed,
+ senderKey,
+ wrapper.forwardingCurve25519KeyChain
+ )
}
/**
@@ -834,9 +840,9 @@ internal class MXOlmDevice @Inject constructor(
/**
* Verify an ed25519 signature on a JSON object.
*
- * @param key the ed25519 key.
+ * @param key the ed25519 key.
* @param jsonDictionary the JSON object which was signed.
- * @param signature the base64-encoded signature to be checked.
+ * @param signature the base64-encoded signature to be checked.
* @throws Exception the exception
*/
@Throws(Exception::class)
@@ -859,7 +865,7 @@ internal class MXOlmDevice @Inject constructor(
* Search an OlmSession.
*
* @param theirDeviceIdentityKey the device key
- * @param sessionId the session Id
+ * @param sessionId the session Id
* @return the olm session
*/
private fun getSessionForDevice(theirDeviceIdentityKey: String, sessionId: String): OlmSessionWrapper? {
@@ -873,9 +879,9 @@ internal class MXOlmDevice @Inject constructor(
* Extract an InboundGroupSession from the session store and do some check.
* inboundGroupSessionWithIdError describes the failure reason.
*
- * @param roomId the room where the session is used.
* @param sessionId the session identifier.
* @param senderKey the base64-encoded curve25519 key of the sender.
+ * @param roomId the room where the session is used.
* @return the inbound group session.
*/
fun getInboundGroupSession(sessionId: String?, senderKey: String?, roomId: String?): InboundGroupSessionHolder {
@@ -905,7 +911,7 @@ internal class MXOlmDevice @Inject constructor(
/**
* Determine if we have the keys for a given megolm session.
*
- * @param roomId room in which the message was received
+ * @param roomId room in which the message was received
* @param senderKey base64-encoded curve25519 key of the sender
* @param sessionId session identifier
* @return true if the unbound session keys are known.
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/OlmSessionStore.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/OlmSessionStore.kt
index fe280416ea..4401a07192 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/OlmSessionStore.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/OlmSessionStore.kt
@@ -39,7 +39,7 @@ internal class OlmSessionStore @Inject constructor(private val store: IMXCryptoS
* Store a session between our own device and another device.
* This will be called after the session has been created but also every time it has been used
* in order to persist the correct state for next run
- * @param olmSessionWrapper the end-to-end session.
+ * @param olmSessionWrapper the end-to-end session.
* @param deviceKey the public key of the other device.
*/
@Synchronized
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/RoomDecryptorProvider.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/RoomDecryptorProvider.kt
index c2f494b4b3..a80bafbe79 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/RoomDecryptorProvider.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/RoomDecryptorProvider.kt
@@ -49,7 +49,7 @@ internal class RoomDecryptorProvider @Inject constructor(
* If we already have a decryptor for the given room and algorithm, return
* it. Otherwise try to instantiate it.
*
- * @param roomId the room id
+ * @param roomId the room id
* @param algorithm the crypto algorithm
* @return the decryptor
* // TODO Create another method for the case of roomId is null
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/actions/EnsureOlmSessionsForUsersAction.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/actions/EnsureOlmSessionsForUsersAction.kt
index fc211537a6..4c5720daf2 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/actions/EnsureOlmSessionsForUsersAction.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/actions/EnsureOlmSessionsForUsersAction.kt
@@ -29,7 +29,7 @@ internal class EnsureOlmSessionsForUsersAction @Inject constructor(private val o
/**
* Try to make sure we have established olm sessions for the given users.
- * @param users a list of user ids.
+ * @param users a list of user ids.
*/
suspend fun handle(users: List): MXUsersDevicesMap {
Timber.v("## ensureOlmSessionsForUsers() : ensureOlmSessionsForUsers $users")
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/actions/MegolmSessionDataImporter.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/actions/MegolmSessionDataImporter.kt
index 22c4e59b18..67d73c21ed 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/actions/MegolmSessionDataImporter.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/actions/MegolmSessionDataImporter.kt
@@ -45,8 +45,8 @@ internal class MegolmSessionDataImporter @Inject constructor(private val olmDevi
* Must be call on the crypto coroutine thread
*
* @param megolmSessionsData megolm sessions.
- * @param fromBackup true if the imported keys are already backed up on the server.
- * @param progressListener the progress listener
+ * @param fromBackup true if the imported keys are already backed up on the server.
+ * @param progressListener the progress listener
* @return import room keys result
*/
@WorkerThread
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/actions/MessageEncrypter.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/actions/MessageEncrypter.kt
index 9bbbab4992..919e38c391 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/actions/MessageEncrypter.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/actions/MessageEncrypter.kt
@@ -42,7 +42,7 @@ internal class MessageEncrypter @Inject constructor(
* This method must be called from the getCryptoHandler() thread.
*
* @param payloadFields fields to include in the encrypted payload.
- * @param deviceInfos list of device infos to encrypt for.
+ * @param deviceInfos list of device infos to encrypt for.
* @return the content for an m.room.encrypted event.
*/
suspend fun encryptMessage(payloadFields: Content, deviceInfos: List): EncryptedMessage {
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/IMXDecrypting.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/IMXDecrypting.kt
index 34006ecfde..6847a46369 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/IMXDecrypting.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/IMXDecrypting.kt
@@ -29,7 +29,7 @@ internal interface IMXDecrypting {
/**
* Decrypt an event.
*
- * @param event the raw event.
+ * @param event the raw event.
* @param timeline the id of the timeline where the event is decrypted. It is used to prevent replay attack.
* @return the decryption information, or an error
*/
@@ -40,6 +40,7 @@ internal interface IMXDecrypting {
* Handle a key event.
*
* @param event the key event.
+ * @param defaultKeysBackupService the keys backup service
*/
fun onRoomKeyEvent(event: Event, defaultKeysBackupService: DefaultKeysBackupService) {}
}
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/IMXEncrypting.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/IMXEncrypting.kt
index 1d84120208..73ce5a5004 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/IMXEncrypting.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/IMXEncrypting.kt
@@ -27,8 +27,8 @@ internal interface IMXEncrypting {
* Encrypt an event content according to the configuration of the room.
*
* @param eventContent the content of the event.
- * @param eventType the type of the event.
- * @param userIds the room members the event will be sent to.
+ * @param eventType the type of the event.
+ * @param userIds the room members the event will be sent to.
* @return the encrypted content
*/
suspend fun encryptEventContent(eventContent: Content, eventType: String, userIds: List): Content
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/IMXGroupEncryption.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/IMXGroupEncryption.kt
index 6f488def0a..8cf01f1972 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/IMXGroupEncryption.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/IMXGroupEncryption.kt
@@ -38,7 +38,7 @@ internal interface IMXGroupEncryption {
* Re-shares a session key with devices if the key has already been
* 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 deviceId The id of the target device.
* @param senderKey The key of the originating device for the session.
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/megolm/MXMegolmDecryption.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/megolm/MXMegolmDecryption.kt
index 8321b73b75..722462bf0e 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/megolm/MXMegolmDecryption.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/megolm/MXMegolmDecryption.kt
@@ -78,6 +78,7 @@ internal class MXMegolmDecryption(
encryptedEventContent.ciphertext,
event.roomId,
timeline,
+ eventId = event.eventId.orEmpty(),
encryptedEventContent.sessionId,
encryptedEventContent.senderKey
)
@@ -176,6 +177,7 @@ internal class MXMegolmDecryption(
* Handle a key event.
*
* @param event the key event.
+ * @param defaultKeysBackupService the keys backup service
*/
override fun onRoomKeyEvent(event: Event, defaultKeysBackupService: DefaultKeysBackupService) {
Timber.tag(loggerTag.value).v("onRoomKeyEvent()")
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/megolm/MXMegolmEncryption.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/megolm/MXMegolmEncryption.kt
index 79e907945f..8b4e9df607 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/megolm/MXMegolmEncryption.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/megolm/MXMegolmEncryption.kt
@@ -198,7 +198,7 @@ internal class MXMegolmEncryption(
/**
* Share the device key to a list of users.
*
- * @param session the session info
+ * @param session the session info
* @param devicesByUsers the devices map
*/
private suspend fun shareKey(session: MXOutboundSessionInfo,
@@ -227,7 +227,7 @@ internal class MXMegolmEncryption(
/**
* Share the device keys of a an user.
*
- * @param session the session info
+ * @param session the session info
* @param devicesByUser the devices map
*/
private suspend fun shareUserDevicesKey(session: MXOutboundSessionInfo,
@@ -387,7 +387,7 @@ internal class MXMegolmEncryption(
* Get the list of devices which can encrypt data to.
* This method must be called in getDecryptingThreadHandler() thread.
*
- * @param userIds the user ids whose devices must be checked.
+ * @param userIds the user ids whose devices must be checked.
*/
private suspend fun getDevicesInRoom(userIds: List): DeviceInRoomInfo {
// We are happy to use a cached version here: we assume that if we already
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/olm/MXOlmDecryption.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/olm/MXOlmDecryption.kt
index 1e66fe84c9..23c8f0e905 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/olm/MXOlmDecryption.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/olm/MXOlmDecryption.kt
@@ -180,8 +180,8 @@ internal class MXOlmDecryption(
/**
* Attempt to decrypt an Olm message.
*
+ * @param message message object, with 'type' and 'body' fields.
* @param theirDeviceIdentityKey the Curve25519 identity key of the sender.
- * @param message message object, with 'type' and 'body' fields.
* @return payload, if decrypted successfully.
*/
private suspend fun decryptMessage(message: JsonDict, theirDeviceIdentityKey: String): String? {
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/olm/MXOlmEncryption.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/olm/MXOlmEncryption.kt
index 3c9706abe1..bde1d65093 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/olm/MXOlmEncryption.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/algorithms/olm/MXOlmEncryption.kt
@@ -70,7 +70,7 @@ internal class MXOlmEncryption(
/**
* Ensure that the session.
*
- * @param users the user ids list
+ * @param users the user ids list
*/
private suspend fun ensureSession(users: List) {
deviceListManager.downloadKeys(users, false)
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/api/CryptoApi.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/api/CryptoApi.kt
index f21f5e05e1..f5ead35933 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/api/CryptoApi.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/api/CryptoApi.kt
@@ -103,7 +103,7 @@ internal interface CryptoApi {
* Claim one-time keys.
* 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")
suspend fun claimOneTimeKeysForUsersDevices(@Body body: KeysClaimBody): KeysClaimResponse
@@ -112,9 +112,9 @@ internal interface CryptoApi {
* Send an event to a specific list of devices
* Doc: https://matrix.org/docs/spec/client_server/r0.4.0.html#put-matrix-client-r0-sendtodevice-eventtype-txnid
*
- * @param eventType the type of event to send
+ * @param eventType the type of event to send
* @param transactionId the transaction ID for this event
- * @param body the body
+ * @param body the body
*/
@PUT(NetworkConstants.URI_API_PREFIX_PATH_R0 + "sendToDevice/{eventType}/{txnId}")
suspend fun sendToDevice(@Path("eventType") eventType: String,
@@ -126,7 +126,7 @@ internal interface CryptoApi {
* Doc: https://matrix.org/docs/spec/client_server/r0.4.0.html#delete-matrix-client-r0-devices-deviceid
*
* @param deviceId the device id
- * @param params the deletion parameters
+ * @param params the deletion parameters
*/
@HTTP(path = NetworkConstants.URI_API_PREFIX_PATH_R0 + "devices/{device_id}", method = "DELETE", hasBody = true)
suspend fun deleteDevice(@Path("device_id") deviceId: String,
@@ -137,7 +137,7 @@ internal interface CryptoApi {
* Doc: https://matrix.org/docs/spec/client_server/r0.4.0.html#put-matrix-client-r0-devices-deviceid
*
* @param deviceId the device id
- * @param params the params
+ * @param params the params
*/
@PUT(NetworkConstants.URI_API_PREFIX_PATH_R0 + "devices/{device_id}")
suspend fun updateDeviceInfo(@Path("device_id") deviceId: String,
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/attachments/MXEncryptedAttachments.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/attachments/MXEncryptedAttachments.kt
index b4cbd15109..7ff08cd127 100755
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/attachments/MXEncryptedAttachments.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/attachments/MXEncryptedAttachments.kt
@@ -159,6 +159,7 @@ internal object MXEncryptedAttachments {
* Encrypt an attachment stream.
* DO NOT USE for big files, it will load all in memory
* @param attachmentStream the attachment stream. Will be closed after this method call.
+ * @param clock a clock to retrieve current time
* @return the encryption file info
*/
fun encryptAttachment(attachmentStream: InputStream, clock: Clock): EncryptionResult {
@@ -231,7 +232,8 @@ internal object MXEncryptedAttachments {
*
* @param attachmentStream the attachment stream. Will be closed after this method call.
* @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
*/
fun decryptAttachment(attachmentStream: InputStream?,
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/DefaultKeysBackupService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/DefaultKeysBackupService.kt
index 5ea4695da2..813adf7459 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/DefaultKeysBackupService.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/DefaultKeysBackupService.kt
@@ -1105,6 +1105,7 @@ internal class DefaultKeysBackupService @Inject constructor(
*
* @param password the password.
* @param keysBackupData the backup and its auth data.
+ * @param progressListener listener to track progress
*
* @return the recovery key if successful, null in other cases
*/
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/KeysBackupPassword.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/KeysBackupPassword.kt
index d5bab33180..f821fdcf6d 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/KeysBackupPassword.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/KeysBackupPassword.kt
@@ -44,6 +44,7 @@ internal data class GeneratePrivateKeyResult(
* Compute a private key from a password.
*
* @param password the password to use.
+ * @param progressListener a listener to track progress
*
* @return a {privateKey, salt, iterations} tuple.
*/
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/api/RoomKeysApi.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/api/RoomKeysApi.kt
index ea23be5923..d9c63b46ab 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/api/RoomKeysApi.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/keysbackup/api/RoomKeysApi.kt
@@ -60,19 +60,19 @@ internal interface RoomKeysApi {
* Get information about the given version.
* If not supported by the server, an error is returned: {"errcode":"M_NOT_FOUND","error":"No backup found"}
*
- * @param version version
+ * @param version version
*/
@GET(NetworkConstants.URI_API_PREFIX_PATH_UNSTABLE + "room_keys/version/{version}")
suspend fun getKeysBackupVersion(@Path("version") version: String): KeysVersionResult
/**
* Update information about the given version.
- * @param version version
+ * @param version version
* @param updateKeysBackupVersionBody the body
*/
@PUT(NetworkConstants.URI_API_PREFIX_PATH_UNSTABLE + "room_keys/version/{version}")
suspend fun updateKeysBackupVersion(@Path("version") version: String,
- @Body keysBackupVersionBody: UpdateKeysBackupVersionBody)
+ @Body updateKeysBackupVersionBody: UpdateKeysBackupVersionBody)
/* ==========================================================================================
* Storing keys
@@ -87,9 +87,9 @@ internal interface RoomKeysApi {
* flag (true is better than false), then by the first_message_index (a lower number is better), and finally by
* forwarded_count (a lower number is better).
*
- * @param roomId the room id
- * @param sessionId the session id
- * @param version the version of the backup
+ * @param roomId the room id
+ * @param sessionId the session id
+ * @param version the version of the backup
* @param keyBackupData the data to send
*/
@PUT(NetworkConstants.URI_API_PREFIX_PATH_UNSTABLE + "room_keys/keys/{roomId}/{sessionId}")
@@ -101,8 +101,8 @@ internal interface RoomKeysApi {
/**
* Store several keys for the given room, using the given backup version.
*
- * @param roomId the room id
- * @param version the version of the backup
+ * @param roomId the room id
+ * @param version the version of the backup
* @param roomKeysBackupData the data to send
*/
@PUT(NetworkConstants.URI_API_PREFIX_PATH_UNSTABLE + "room_keys/keys/{roomId}")
@@ -113,7 +113,7 @@ internal interface RoomKeysApi {
/**
* Store several keys, using the given backup version.
*
- * @param version the version of the backup
+ * @param version the version of the backup
* @param keysBackupData the data to send
*/
@PUT(NetworkConstants.URI_API_PREFIX_PATH_UNSTABLE + "room_keys/keys")
@@ -127,9 +127,9 @@ internal interface RoomKeysApi {
/**
* Retrieve the key for the given session in the given room from the backup.
*
- * @param roomId the room id
+ * @param roomId the room id
* @param sessionId the session id
- * @param version the version of the backup, or empty String to retrieve the last version
+ * @param version the version of the backup, or empty String to retrieve the last version
*/
@GET(NetworkConstants.URI_API_PREFIX_PATH_UNSTABLE + "room_keys/keys/{roomId}/{sessionId}")
suspend fun getRoomSessionData(@Path("roomId") roomId: String,
@@ -139,8 +139,8 @@ internal interface RoomKeysApi {
/**
* Retrieve all the keys for the given room from the backup.
*
- * @param roomId the room id
- * @param version the version of the backup, or empty String to retrieve the last version
+ * @param roomId the room id
+ * @param version the version of the backup, or empty String to retrieve the last version
*/
@GET(NetworkConstants.URI_API_PREFIX_PATH_UNSTABLE + "room_keys/keys/{roomId}")
suspend fun getRoomSessionsData(@Path("roomId") roomId: String,
@@ -149,7 +149,7 @@ internal interface RoomKeysApi {
/**
* Retrieve all the keys from the backup.
*
- * @param version the version of the backup, or empty String to retrieve the last version
+ * @param version the version of the backup, or empty String to retrieve the last version
*/
@GET(NetworkConstants.URI_API_PREFIX_PATH_UNSTABLE + "room_keys/keys")
suspend fun getSessionsData(@Query("version") version: String): KeysBackupData
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/MXKey.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/MXKey.kt
index 6bfa56ae8d..6b747d19f2 100755
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/MXKey.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/MXKey.kt
@@ -59,7 +59,7 @@ internal data class MXKey(
/**
* Returns a signature for an user Id and a signkey.
*
- * @param userId the user id
+ * @param userId the user id
* @param signkey the sign key
* @return the signature
*/
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/IMXCryptoStore.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/IMXCryptoStore.kt
index 480009dbce..9b1c785059 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/IMXCryptoStore.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/IMXCryptoStore.kt
@@ -164,16 +164,14 @@ internal interface IMXCryptoStore {
/**
* Store the end to end account for the logged-in user.
- *
- * @param account the account to save
*/
fun saveOlmAccount()
/**
* Retrieve a device for a user.
*
+ * @param userId the user's id.
* @param deviceId the device id.
- * @param userId the user's id.
* @return the device
*/
fun getUserDevice(userId: String, deviceId: String): CryptoDeviceInfo?
@@ -189,7 +187,7 @@ internal interface IMXCryptoStore {
/**
* Store the known devices for a user.
*
- * @param userId The user's id.
+ * @param userId The user's id.
* @param devices A map from device id to 'MXDevice' object for the device.
*/
fun storeUserDevices(userId: String, devices: Map?)
@@ -225,7 +223,7 @@ internal interface IMXCryptoStore {
/**
* Store the crypto algorithm for a room.
*
- * @param roomId the id of the room.
+ * @param roomId the id of the room.
* @param algorithm the algorithm.
*/
fun storeRoomAlgorithm(roomId: String, algorithm: String?)
@@ -253,7 +251,7 @@ internal interface IMXCryptoStore {
/**
* Store a session between the logged-in user and another device.
*
- * @param olmSessionWrapper the end-to-end session.
+ * @param olmSessionWrapper the end-to-end session.
* @param deviceKey the public key of the other device.
*/
fun storeSession(olmSessionWrapper: OlmSessionWrapper, deviceKey: String)
@@ -331,7 +329,7 @@ internal interface IMXCryptoStore {
/**
* Mark inbound group sessions as backed up on the user homeserver.
*
- * @param sessions the sessions
+ * @param olmInboundGroupSessionWrappers the sessions
*/
fun markBackupDoneForInboundGroupSessions(olmInboundGroupSessionWrappers: List)
@@ -361,7 +359,7 @@ internal interface IMXCryptoStore {
/**
* Get the tracking status of a specified userId devices.
*
- * @param userId the user id
+ * @param userId the user id
* @param defaultValue the default value
* @return the tracking status
*/
@@ -380,7 +378,9 @@ internal interface IMXCryptoStore {
/**
* 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.
*/
fun getOrAddOutgoingRoomKeyRequest(requestBody: RoomKeyRequestBody, recipients: Map>, fromIndex: Int): OutgoingKeyRequest
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/tools/HkdfSha256.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/tools/HkdfSha256.kt
index 4ab7e0e30c..04fb6c4858 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/tools/HkdfSha256.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/tools/HkdfSha256.kt
@@ -35,7 +35,7 @@ internal object HkdfSha256 {
/**
* HkdfSha256-Extract(salt, IKM) -> PRK.
*
- * @param salt optional salt value (a non-secret random value);
+ * @param salt optional salt value (a non-secret random value);
* if not provided, it is set to a string of HashLen (size in octets) zeros.
* @param ikm input keying material
*/
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/verification/VerificationTransport.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/verification/VerificationTransport.kt
index 8538e5a5af..69dec12ef3 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/verification/VerificationTransport.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/verification/VerificationTransport.kt
@@ -35,6 +35,11 @@ internal interface VerificationTransport {
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
*/
fun sendVerificationRequest(supportedMethods: List,
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/helper/ThreadEventsHelper.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/helper/ThreadEventsHelper.kt
index db3647c3fa..5db859bce2 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/helper/ThreadEventsHelper.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/helper/ThreadEventsHelper.kt
@@ -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.
* Note: Redactions are handled by RedactionEventProcessor.
+ * @param realm the realm database
* @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
*/
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.
+ * @param realm the realm instance
* @param roomId The room that all stored root threads will be returned
*/
internal fun TimelineEventEntity.Companion.findAllThreadsForRoomId(realm: Realm, roomId: String): RealmQuery =
@@ -218,6 +221,7 @@ internal fun List.mapEventsWithEdition(realm: Realm, roomId: Stri
/**
* 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
*/
internal fun TimelineEventEntity.Companion.findAllLocalThreadNotificationsForRoomId(realm: Realm, roomId: String): RealmQuery =
@@ -232,6 +236,7 @@ internal fun TimelineEventEntity.Companion.findAllLocalThreadNotificationsForRoo
/**
* 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 rootThreadEventId the thread that the search will be done
* @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.
+ * @param realm the realm instance
* @param roomId the room that the thread exists
* @param rootThreadEventId the thread that the search will be done
* @param userId the user that will try to find if there is a mention
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/helper/ThreadSummaryHelper.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/helper/ThreadSummaryHelper.kt
index 3bf574c207..5b4fe287cb 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/helper/ThreadSummaryHelper.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/helper/ThreadSummaryHelper.kt
@@ -303,6 +303,7 @@ private fun getLatestEvent(rootThreadEvent: Event): Event? {
/**
* 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.
+ * @param realm the realm instance
* @param roomId The id of the room
*/
internal fun ThreadSummaryEntity.Companion.findAllThreadsForRoomId(realm: Realm, roomId: String): RealmQuery =
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/livelocation/LiveLocationShareAggregatedSummaryEntity.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/livelocation/LiveLocationShareAggregatedSummaryEntity.kt
index 4d0d2c5c64..e84337693f 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/livelocation/LiveLocationShareAggregatedSummaryEntity.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/livelocation/LiveLocationShareAggregatedSummaryEntity.kt
@@ -31,6 +31,9 @@ internal open class LiveLocationShareAggregatedSummaryEntity(
var roomId: String = "",
+ /**
+ * Indicate whether the live is currently running.
+ */
var isActive: Boolean? = null,
var endOfLiveTimestampMillis: Long? = null,
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/query/LiveLocationShareAggregatedSummaryEntityQuery.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/query/LiveLocationShareAggregatedSummaryEntityQuery.kt
index 2e2e939fa2..816b5f4392 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/query/LiveLocationShareAggregatedSummaryEntityQuery.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/query/LiveLocationShareAggregatedSummaryEntityQuery.kt
@@ -55,3 +55,11 @@ internal fun LiveLocationShareAggregatedSummaryEntity.Companion.getOrCreate(
return LiveLocationShareAggregatedSummaryEntity.where(realm, roomId, eventId).findFirst()
?: LiveLocationShareAggregatedSummaryEntity.create(realm, roomId, eventId)
}
+
+internal fun LiveLocationShareAggregatedSummaryEntity.Companion.get(
+ realm: Realm,
+ roomId: String,
+ eventId: String,
+): LiveLocationShareAggregatedSummaryEntity? {
+ return LiveLocationShareAggregatedSummaryEntity.where(realm, roomId, eventId).findFirst()
+}
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/legacy/riot/HomeServerConnectionConfig.java b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/legacy/riot/HomeServerConnectionConfig.java
index a1b46f6c09..b2bb852cd1 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/legacy/riot/HomeServerConnectionConfig.java
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/legacy/riot/HomeServerConnectionConfig.java
@@ -612,7 +612,7 @@ public class HomeServerConnectionConfig {
* - https://www.ssi.gouv.fr/uploads/2017/02/security-recommendations-for-tls_v1.1.pdf
* - https://developer.android.com/reference/javax/net/ssl/SSLEngine
*
- * @param tlsLimitations true to use Tls limitations
+ * @param tlsLimitations true to use Tls limitations
* @param enableCompatibilityMode set to true for Android < 20
* @return this builder
*/
@@ -649,7 +649,7 @@ public class HomeServerConnectionConfig {
/**
* @param proxyHostname Proxy Hostname
- * @param proxyPort Proxy Port
+ * @param proxyPort Proxy Port
* @return this builder
*/
public Builder withProxy(@Nullable String proxyHostname, int proxyPort) {
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/network/Request.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/network/Request.kt
index 695e7525af..87a98e03f6 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/network/Request.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/network/Request.kt
@@ -32,6 +32,7 @@ import java.io.IOException
* 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
*
+ * @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 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
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/network/parsing/RuntimeJsonAdapterFactory.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/network/parsing/RuntimeJsonAdapterFactory.kt
index 40d174ee2d..dd41b9f6fc 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/network/parsing/RuntimeJsonAdapterFactory.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/network/parsing/RuntimeJsonAdapterFactory.kt
@@ -119,9 +119,11 @@ internal class RuntimeJsonAdapterFactory(
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 labelKey The key in the JSON object whose value determines the type to which to map the
* JSON object.
+ * @param fallbackType alternative Type to try in case of the serialization fails
*/
@CheckReturnValue
fun of(baseType: Class, labelKey: String, fallbackType: Class): RuntimeJsonAdapterFactory {
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/network/ssl/CertUtil.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/network/ssl/CertUtil.kt
index 2ef40fe2a3..e5659fd76b 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/network/ssl/CertUtil.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/network/ssl/CertUtil.kt
@@ -94,6 +94,7 @@ internal object CertUtil {
* Convert the fingerprint to an hexa string.
*
* @param fingerprint the fingerprint
+ * @param sep the separator character, default to space
* @return the hexa string.
*/
fun fingerprintToHexString(fingerprint: ByteArray, sep: Char = ' '): String {
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/network/ssl/PinnedTrustManager.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/network/ssl/PinnedTrustManager.kt
index ccae5ad14f..539570cdd9 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/network/ssl/PinnedTrustManager.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/network/ssl/PinnedTrustManager.kt
@@ -24,11 +24,9 @@ import javax.net.ssl.X509TrustManager
/**
* Implements a TrustManager that checks Certificates against an explicit list of known
* fingerprints.
- */
-
-/**
- * @param fingerprints Not empty array of SHA256 cert fingerprints
- * @param defaultTrustManager Optional trust manager to fall back on if cert does not match
+ *
+ * @property fingerprints Not empty array of SHA256 cert fingerprints
+ * @property defaultTrustManager Optional trust manager to fall back on if cert does not match
* any of the fingerprints. Can be null.
*/
internal class PinnedTrustManager(private val fingerprints: List,
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/network/ssl/PinnedTrustManagerApi24.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/network/ssl/PinnedTrustManagerApi24.kt
index 574f1ef81d..191bb90a67 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/network/ssl/PinnedTrustManagerApi24.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/network/ssl/PinnedTrustManagerApi24.kt
@@ -28,11 +28,9 @@ import javax.net.ssl.X509ExtendedTrustManager
/**
* Implements a TrustManager that checks Certificates against an explicit list of known
* fingerprints.
- */
-
-/**
- * @param fingerprints An array of SHA256 cert fingerprints
- * @param defaultTrustManager Optional trust manager to fall back on if cert does not match
+ *
+ * @property fingerprints An array of SHA256 cert fingerprints
+ * @property defaultTrustManager Optional trust manager to fall back on if cert does not match
* any of the fingerprints. Can be null.
*/
@RequiresApi(Build.VERSION_CODES.N)
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/SessionComponent.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/SessionComponent.kt
index 050480e6c9..1e3566f49e 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/SessionComponent.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/SessionComponent.kt
@@ -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.PushersModule
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.RedactEventWorker
import org.matrix.android.sdk.internal.session.room.send.SendEventWorker
@@ -131,6 +132,8 @@ internal interface SessionComponent {
fun inject(worker: UpdateTrustWorker)
+ fun inject(worker: DeactivateLiveLocationShareWorker)
+
@Component.Factory
interface Factory {
fun create(
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/SessionModule.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/SessionModule.kt
index b5f49d7f9c..9208ff219b 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/SessionModule.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/SessionModule.kt
@@ -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.permalinks.DefaultPermalinkService
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.prune.RedactionEventProcessor
import org.matrix.android.sdk.internal.session.room.send.queue.EventSenderProcessor
@@ -385,4 +387,7 @@ internal abstract class SessionModule {
@Binds
abstract fun bindEventSenderProcessor(processor: EventSenderProcessorCoroutine): EventSenderProcessor
+
+ @Binds
+ abstract fun bindPollAggregationProcessor(processor: DefaultPollAggregationProcessor): PollAggregationProcessor
}
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/directory/DirectoryAPI.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/directory/DirectoryAPI.kt
index 19b9130fc4..0db6812609 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/directory/DirectoryAPI.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/directory/DirectoryAPI.kt
@@ -55,6 +55,7 @@ internal interface DirectoryAPI {
/**
* Add alias to the room.
* @param roomAlias the room alias.
+ * @param body the Json body
*/
@PUT(NetworkConstants.URI_API_PREFIX_PATH_R0 + "directory/room/{roomAlias}")
suspend fun addRoomAlias(@Path("roomAlias") roomAlias: String,
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/filter/FilterApi.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/filter/FilterApi.kt
index dab801360f..d1df77d14a 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/filter/FilterApi.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/filter/FilterApi.kt
@@ -28,7 +28,7 @@ internal interface FilterApi {
* Upload FilterBody to get a filter_id which can be used for /sync requests.
*
* @param userId the user id
- * @param body the Json representation of a FilterBody object
+ * @param body the Json representation of a FilterBody object
*/
@POST(NetworkConstants.URI_API_PREFIX_PATH_R0 + "user/{userId}/filter")
suspend fun uploadFilter(@Path("userId") userId: String,
@@ -37,7 +37,7 @@ internal interface FilterApi {
/**
* Gets a filter with a given filterId from the homeserver.
*
- * @param userId the user id
+ * @param userId the user id
* @param filterId the filterID
* @return Filter
*/
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/filter/FilterUtil.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/filter/FilterUtil.kt
index 562fea88b6..2017a86c39 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/filter/FilterUtil.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/filter/FilterUtil.kt
@@ -25,7 +25,7 @@ internal object FilterUtil {
* FIXME New expected filter:
* "{\"room\": {\"ephemeral\": {\"notTypes\": [\"m.typing\"]}}, \"presence\":{\"notTypes\": [\"*\"]}}"
*
- * @param filterBody filterBody to patch
+ * @param filterBody filterBody to patch
* @param useDataSaveMode true to enable data save mode
*/
/*
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/openid/OpenIdAPI.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/openid/OpenIdAPI.kt
index eb8c841d57..c3caaefdec 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/openid/OpenIdAPI.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/openid/OpenIdAPI.kt
@@ -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
*
* @param userId the user id
+ * @param body an empty json body
*/
@POST(NetworkConstants.URI_API_PREFIX_PATH_R0 + "user/{userId}/openid/request_token")
suspend fun openIdToken(@Path("userId") userId: String,
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/PushRulesApi.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/PushRulesApi.kt
index 40b4ee269a..fbae04a1f1 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/PushRulesApi.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/pushers/PushRulesApi.kt
@@ -33,9 +33,9 @@ internal interface PushRulesApi {
/**
* Update the ruleID enable status.
*
- * @param kind the notification kind (sender, room...)
+ * @param kind the notification kind (sender, room...)
* @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")
suspend fun updateEnableRuleStatus(@Path("kind") kind: String,
@@ -46,8 +46,8 @@ internal interface PushRulesApi {
* Update the ruleID action.
* Ref: https://matrix.org/docs/spec/client_server/latest#put-matrix-client-r0-pushrules-scope-kind-ruleid-actions
*
- * @param kind the notification kind (sender, room...)
- * @param ruleId the ruleId
+ * @param kind the notification kind (sender, room...)
+ * @param ruleId the ruleId
* @param actions the actions
*/
@PUT(NetworkConstants.URI_API_PREFIX_PATH_R0 + "pushrules/global/{kind}/{ruleId}/actions")
@@ -58,7 +58,7 @@ internal interface PushRulesApi {
/**
* Delete a rule.
*
- * @param kind the notification kind (sender, room...)
+ * @param kind the notification kind (sender, room...)
* @param ruleId the ruleId
*/
@DELETE(NetworkConstants.URI_API_PREFIX_PATH_R0 + "pushrules/global/{kind}/{ruleId}")
@@ -68,9 +68,9 @@ internal interface PushRulesApi {
/**
* Add the ruleID enable status.
*
- * @param kind the notification kind (sender, room...)
+ * @param kind the notification kind (sender, room...)
* @param ruleId the ruleId.
- * @param rule the rule to add.
+ * @param rule the rule to add.
*/
@PUT(NetworkConstants.URI_API_PREFIX_PATH_R0 + "pushrules/global/{kind}/{ruleId}")
suspend fun addRule(@Path("kind") kind: String,
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/EventRelationsAggregationProcessor.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/EventRelationsAggregationProcessor.kt
index af9c0071fe..3efeef7688 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/EventRelationsAggregationProcessor.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/EventRelationsAggregationProcessor.kt
@@ -16,7 +16,6 @@
package org.matrix.android.sdk.internal.session.room
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.session.crypto.verification.VerificationState
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.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.PowerLevelsContent
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.MessageBeaconLocationDataContent
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.MessagePollResponseContent
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.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.crypto.verification.toState
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.EventEntity
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.ReactionAggregatedSummaryEntityFields
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.session.EventInsertLiveProcessor
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.util.time.Clock
import timber.log.Timber
@@ -79,6 +71,7 @@ internal class EventRelationsAggregationProcessor @Inject constructor(
@SessionId private val sessionId: String,
private val sessionManager: SessionManager,
private val liveLocationAggregationProcessor: LiveLocationAggregationProcessor,
+ private val pollAggregationProcessor: PollAggregationProcessor,
private val clock: Clock,
) : EventInsertLiveProcessor {
@@ -162,9 +155,8 @@ internal class EventRelationsAggregationProcessor @Inject constructor(
// A replace!
handleReplace(realm, event, it, roomId, isLocalEcho, encryptedEventContent.relatesTo.eventId)
} else if (event.getClearType() in EventType.POLL_RESPONSE) {
- event.getClearContent().toModel(catchError = true)?.let { pollResponseContent ->
- Timber.v("###RESPONSE in room $roomId for event ${event.eventId}")
- handleResponse(realm, event, pollResponseContent, roomId, isLocalEcho, encryptedEventContent.relatesTo.eventId)
+ sessionManager.getSessionComponent(sessionId)?.session()?.let { session ->
+ pollAggregationProcessor.handlePollResponseEvent(session, realm, event)
}
}
}
@@ -184,12 +176,16 @@ internal class EventRelationsAggregationProcessor @Inject constructor(
}
in EventType.POLL_RESPONSE -> {
event.getClearContent().toModel(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 -> {
- event.content.toModel(catchError = true)?.let {
- handleEndPoll(realm, event, it, roomId, isLocalEcho)
+ sessionManager.getSessionComponent(sessionId)?.session()?.let { session ->
+ getPowerLevelsHelper(event.roomId)?.let {
+ pollAggregationProcessor.handlePollEndEvent(session, it, realm, event)
+ }
}
}
in EventType.BEACON_LOCATION_DATA -> {
@@ -245,12 +241,16 @@ internal class EventRelationsAggregationProcessor @Inject constructor(
}
in EventType.POLL_RESPONSE -> {
event.content.toModel(catchError = true)?.let {
- handleResponse(realm, event, it, roomId, isLocalEcho)
+ sessionManager.getSessionComponent(sessionId)?.session()?.let { session ->
+ pollAggregationProcessor.handlePollResponseEvent(session, realm, event)
+ }
}
}
in EventType.POLL_END -> {
- event.content.toModel(catchError = true)?.let {
- handleEndPoll(realm, event, it, roomId, isLocalEcho)
+ sessionManager.getSessionComponent(sessionId)?.session()?.let { session ->
+ getPowerLevelsHelper(event.roomId)?.let {
+ pollAggregationProcessor.handlePollEndEvent(session, it, realm, event)
+ }
}
}
in EventType.STATE_ROOM_BEACON_INFO -> {
@@ -318,22 +318,6 @@ internal class EventRelationsAggregationProcessor @Inject constructor(
return
}
- ContentMapper
- .map(eventAnnotationsSummaryEntity.pollResponseSummary?.aggregatedContent)
- ?.toModel()
- ?.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
// is it a remote echo?
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) {
val replaceEvent = TimelineEventEntity
.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.
* @param editedEvent The event that will be changed
* @param replaceEvent The new event
+ * @param editions list of edition of event
*/
private fun handleThreadSummaryEdition(editedEvent: EventEntity?,
replaceEvent: TimelineEventEntity?,
@@ -392,173 +381,10 @@ internal class EventRelationsAggregationProcessor @Inject constructor(
}
}
- private fun handleResponse(realm: Realm,
- event: Event,
- 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()
-
- 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)
+ private fun getPowerLevelsHelper(roomId: String): PowerLevelsHelper? {
+ return stateEventDataSource.getStateEvent(roomId, EventType.STATE_ROOM_POWER_LEVELS, QueryStringValue.NoCondition)
?.content?.toModel()
?.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,
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/RoomAPI.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/RoomAPI.kt
index 72f56ddf68..ba7f4cf5ad 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/RoomAPI.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/RoomAPI.kt
@@ -78,9 +78,9 @@ internal interface RoomAPI {
* Get a list of messages starting from a reference.
*
* @param roomId the room id
- * @param from the token identifying where to start. Required.
- * @param dir The direction to return messages from. Required.
- * @param limit the maximum number of messages to retrieve. Optional.
+ * @param from the token identifying where to start. Required.
+ * @param dir The direction to return messages from. Required.
+ * @param limit the maximum number of messages to retrieve. Optional.
* @param filter A JSON RoomEventFilter to filter returned events with. Optional.
*/
@GET(NetworkConstants.URI_API_PREFIX_PATH_R0 + "rooms/{roomId}/messages")
@@ -94,9 +94,9 @@ internal interface RoomAPI {
/**
* Get all members of a room.
*
- * @param roomId the room id where to get the members
- * @param syncToken the sync token (optional)
- * @param membership to include only one type of membership (optional)
+ * @param roomId the room id where to get the members
+ * @param syncToken the sync token (optional)
+ * @param membership to include only one type of membership (optional)
* @param notMembership to exclude one type of membership (optional)
*/
@GET(NetworkConstants.URI_API_PREFIX_PATH_R0 + "rooms/{roomId}/members")
@@ -109,10 +109,10 @@ internal interface RoomAPI {
/**
* Send an event to a room.
*
- * @param txId the transaction Id
- * @param roomId the room id
+ * @param txId the transaction Id
+ * @param roomId the room id
* @param eventType the event type
- * @param content the event content
+ * @param content the event content
*/
@PUT(NetworkConstants.URI_API_PREFIX_PATH_R0 + "rooms/{roomId}/send/{eventType}/{txId}")
suspend fun send(@Path("txId") txId: String,
@@ -124,10 +124,10 @@ internal interface RoomAPI {
/**
* Get the context surrounding an event.
*
- * @param roomId the room id
+ * @param roomId the room id
* @param eventId the event Id
- * @param limit the maximum number of messages to retrieve
- * @param filter A JSON RoomEventFilter to filter returned events with. Optional.
+ * @param limit the maximum number of messages to retrieve
+ * @param filter A JSON RoomEventFilter to filter returned events with. Optional.
*/
@GET(NetworkConstants.URI_API_PREFIX_PATH_R0 + "rooms/{roomId}/context/{eventId}")
suspend fun getContextOfEvent(@Path("roomId") roomId: String,
@@ -138,7 +138,7 @@ internal interface RoomAPI {
/**
* Retrieve an event from its room id / events id.
*
- * @param roomId the room id
+ * @param roomId the room id
* @param eventId the event Id
*/
@GET(NetworkConstants.URI_API_PREFIX_PATH_R0 + "rooms/{roomId}/event/{eventId}")
@@ -148,7 +148,7 @@ internal interface RoomAPI {
/**
* Send read markers.
*
- * @param roomId the room id
+ * @param roomId the room id
* @param markers the read markers
*/
@POST(NetworkConstants.URI_API_PREFIX_PATH_R0 + "rooms/{roomId}/read_markers")
@@ -169,7 +169,7 @@ internal interface RoomAPI {
* Ref: https://matrix.org/docs/spec/client_server/r0.4.0.html#post-matrix-client-r0-rooms-roomid-invite
*
* @param roomId the room id
- * @param body a object that just contains a user id
+ * @param body a object that just contains a user id
*/
@POST(NetworkConstants.URI_API_PREFIX_PATH_R0 + "rooms/{roomId}/invite")
suspend fun invite(@Path("roomId") roomId: String,
@@ -179,6 +179,7 @@ internal interface RoomAPI {
* Invite a user to a room, using a ThreePid
* 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 body the Json body
*/
@POST(NetworkConstants.URI_API_PREFIX_PATH_R0 + "rooms/{roomId}/invite")
suspend fun invite3pid(@Path("roomId") roomId: String,
@@ -187,9 +188,9 @@ internal interface RoomAPI {
/**
* Send a generic state event.
*
- * @param roomId the room id.
+ * @param roomId the room id.
* @param stateEventType the state event type
- * @param params the request parameters
+ * @param params the request parameters
*/
@PUT(NetworkConstants.URI_API_PREFIX_PATH_R0 + "rooms/{roomId}/state/{state_event_type}")
suspend fun sendStateEvent(@Path("roomId") roomId: String,
@@ -200,10 +201,10 @@ internal interface RoomAPI {
/**
* Send a generic state event.
*
- * @param roomId the room id.
+ * @param roomId the room id.
* @param stateEventType the state event type
- * @param stateKey the state keys
- * @param params the request parameters
+ * @param stateKey the state keys
+ * @param params the request parameters
*/
@PUT(NetworkConstants.URI_API_PREFIX_PATH_R0 + "rooms/{roomId}/state/{state_event_type}/{state_key}")
suspend fun sendStateEvent(@Path("roomId") roomId: String,
@@ -221,8 +222,13 @@ internal interface RoomAPI {
/**
* 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 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}")
suspend fun getRelations(@Path("roomId") roomId: String,
@@ -236,7 +242,13 @@ internal interface RoomAPI {
/**
* 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 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}")
suspend fun getThreadsRelations(@Path("roomId") roomId: String,
@@ -262,7 +274,7 @@ internal interface RoomAPI {
/**
* Leave the given room.
*
- * @param roomId the room id
+ * @param roomId the room id
* @param params the request body
*/
@POST(NetworkConstants.URI_API_PREFIX_PATH_R0 + "rooms/{roomId}/leave")
@@ -272,7 +284,7 @@ internal interface RoomAPI {
/**
* Ban a user from the given room.
*
- * @param roomId the room id
+ * @param roomId the room id
* @param userIdAndReason the banned user object (userId and reason for ban)
*/
@POST(NetworkConstants.URI_API_PREFIX_PATH_R0 + "rooms/{roomId}/ban")
@@ -282,7 +294,7 @@ internal interface RoomAPI {
/**
* unban a user from the given room.
*
- * @param roomId the room id
+ * @param roomId the room id
* @param userIdAndReason the unbanned user object (userId and reason for unban)
*/
@POST(NetworkConstants.URI_API_PREFIX_PATH_R0 + "rooms/{roomId}/unban")
@@ -292,7 +304,7 @@ internal interface RoomAPI {
/**
* Kick a user from the given room.
*
- * @param roomId the room id
+ * @param roomId the room id
* @param userIdAndReason the kicked user object (userId and reason for kicking)
*/
@POST(NetworkConstants.URI_API_PREFIX_PATH_R0 + "rooms/{roomId}/kick")
@@ -304,10 +316,10 @@ internal interface RoomAPI {
* This cannot be undone.
* Users may redact their own events, and any user with a power level greater than or equal to the redact power level of the room may redact events there.
*
- * @param txId the transaction Id
- * @param roomId the room id
- * @param eventId the event to delete
- * @param reason json containing reason key {"reason": "Indecent material"}
+ * @param txId the transaction Id
+ * @param roomId the room id
+ * @param eventId the event to delete
+ * @param reason json containing reason key {"reason": "Indecent material"}
*/
@PUT(NetworkConstants.URI_API_PREFIX_PATH_R0 + "rooms/{roomId}/redact/{eventId}/{txnId}")
suspend fun redactEvent(
@@ -320,9 +332,9 @@ internal interface RoomAPI {
/**
* Reports an event as inappropriate to the server, which may then notify the appropriate people.
*
- * @param roomId the room id
+ * @param roomId the room id
* @param eventId the event to report content
- * @param body body containing score and reason
+ * @param body body containing score and reason
*/
@POST(NetworkConstants.URI_API_PREFIX_PATH_R0 + "rooms/{roomId}/report/{eventId}")
suspend fun reportContent(@Path("roomId") roomId: String,
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/RoomAvatarResolver.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/RoomAvatarResolver.kt
index 29a303475b..c3d55b267a 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/RoomAvatarResolver.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/RoomAvatarResolver.kt
@@ -35,7 +35,7 @@ internal class RoomAvatarResolver @Inject constructor(@UserId private val userId
/**
* 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
* @return the room avatar url, can be a fallback to a room member avatar or null
*/
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/aggregation/livelocation/DeactivateLiveLocationShareWorker.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/aggregation/livelocation/DeactivateLiveLocationShareWorker.kt
new file mode 100644
index 0000000000..2b83c8028b
--- /dev/null
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/aggregation/livelocation/DeactivateLiveLocationShareWorker.kt
@@ -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(
+ 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"
+ }
+ }
+}
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/aggregation/livelocation/LiveLocationAggregationProcessor.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/aggregation/livelocation/LiveLocationAggregationProcessor.kt
index 76b7a4ec8e..42dfc7ba9f 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/aggregation/livelocation/LiveLocationAggregationProcessor.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/aggregation/livelocation/LiveLocationAggregationProcessor.kt
@@ -16,6 +16,7 @@
package org.matrix.android.sdk.internal.session.room.aggregation.livelocation
+import androidx.work.ExistingWorkPolicy
import io.realm.Realm
import org.matrix.android.sdk.api.extensions.orTrue
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.model.livelocation.LiveLocationShareAggregatedSummaryEntity
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 java.util.concurrent.TimeUnit
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) {
if (event.senderId.isNullOrEmpty() || isLocalEcho) {
return
}
- val targetEventId = if (content.isLive.orTrue()) {
+ val isLive = content.isLive.orTrue()
+ val targetEventId = if (isLive) {
event.eventId
} else {
// 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}")
- aggregatedSummary.endOfLiveTimestampMillis = content.getBestTimestampMillis()?.let { it + (content.timeout ?: 0) }
- aggregatedSummary.isActive = content.isLive
+ val endOfLiveTimestampMillis = content.getBestTimestampMillis()?.let { it + (content.timeout ?: 0) }
+ 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()
+ .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(
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/aggregation/poll/DefaultPollAggregationProcessor.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/aggregation/poll/DefaultPollAggregationProcessor.kt
new file mode 100644
index 0000000000..d4b414aaea
--- /dev/null
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/aggregation/poll/DefaultPollAggregationProcessor.kt
@@ -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()
+ 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()
+ ?.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() ?: 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()
+ 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() ?: 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
+ }
+ }
+}
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/aggregation/poll/PollAggregationProcessor.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/aggregation/poll/PollAggregationProcessor.kt
new file mode 100644
index 0000000000..848643b435
--- /dev/null
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/aggregation/poll/PollAggregationProcessor.kt
@@ -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
+}
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/membership/RoomDisplayNameResolver.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/membership/RoomDisplayNameResolver.kt
index 59e0f81ece..9e672dcc5c 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/membership/RoomDisplayNameResolver.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/membership/RoomDisplayNameResolver.kt
@@ -52,8 +52,8 @@ internal class RoomDisplayNameResolver @Inject constructor(
/**
* Compute the room display name.
*
- * @param realm: the current instance of realm
- * @param roomId: the roomId to resolve the name of.
+ * @param realm the current instance of realm
+ * @param roomId the roomId to resolve the name of.
* @return the room display name
*/
fun resolve(realm: Realm, roomId: String): RoomName {
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/queue/QueuedTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/queue/QueuedTask.kt
index 948786677d..983701857f 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/queue/QueuedTask.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/queue/QueuedTask.kt
@@ -21,8 +21,8 @@ import timber.log.Timber
import java.util.concurrent.atomic.AtomicInteger
/**
- * @param queueIdentifier String value to identify a unique Queue
- * @param taskIdentifier String value to identify a unique Task. Should be different from queueIdentifier
+ * @property queueIdentifier String value to identify a unique Queue
+ * @property taskIdentifier String value to identify a unique Task. Should be different from queueIdentifier
*/
internal abstract class QueuedTask(
val queueIdentifier: String,
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/space/SpaceApi.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/space/SpaceApi.kt
index d8daa55e15..33c3c3929f 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/space/SpaceApi.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/space/SpaceApi.kt
@@ -24,11 +24,12 @@ import retrofit2.http.Query
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 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 limit Optional: a client-defined limit to the maximum number of rooms to return per page. Must be a non-negative integer.
+ * @param maxDepth Optional: The maximum depth in the tree (from the root room) to return.
* The deepest depth returned will not include children events. Defaults to no-limit. Must be a non-negative integer.
- * @param from: Optional. Pagination token given to retrieve the next set of rooms.
+ * @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.
*/
@GET(NetworkConstants.URI_API_PREFIX_PATH_V1 + "rooms/{roomId}/hierarchy")
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/handler/CryptoSyncHandler.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/handler/CryptoSyncHandler.kt
index dd95762166..e5a5a0bbad 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/handler/CryptoSyncHandler.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/handler/CryptoSyncHandler.kt
@@ -62,7 +62,7 @@ internal class CryptoSyncHandler @Inject constructor(private val cryptoService:
/**
* Decrypt an encrypted event.
*
- * @param event the event to decrypt
+ * @param event the event to decrypt
* @param timelineId the timeline identifier
* @return true if the event has been decrypted
*/
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/handler/room/RoomSyncHandler.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/handler/room/RoomSyncHandler.kt
index c5d14afac0..53fc9dc6b9 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/handler/room/RoomSyncHandler.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/handler/room/RoomSyncHandler.kt
@@ -520,9 +520,10 @@ internal class RoomSyncHandler @Inject constructor(
private fun decryptIfNeeded(event: Event, roomId: String) {
try {
+ val timelineId = generateTimelineId(roomId)
// 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
- val result = runBlocking { cryptoService.decryptEvent(event.copy(roomId = roomId), "") }
+ val result = runBlocking { cryptoService.decryptEvent(event.copy(roomId = roomId), timelineId) }
event.mxDecryptionResult = OlmDecryptionResult(
payload = result.clearEvent,
senderKey = result.senderCurve25519Key,
@@ -537,6 +538,10 @@ internal class RoomSyncHandler @Inject constructor(
}
}
+ private fun generateTimelineId(roomId: String): String {
+ return "RoomSyncHandler$roomId"
+ }
+
data class EphemeralResult(
val typingUserIds: List = emptyList()
)
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/handler/room/ThreadsAwarenessHandler.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/handler/room/ThreadsAwarenessHandler.kt
index 03e076c217..9beb8333a4 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/handler/room/ThreadsAwarenessHandler.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/handler/room/ThreadsAwarenessHandler.kt
@@ -206,6 +206,8 @@ internal class ThreadsAwarenessHandler @Inject constructor(
/**
* Handle for not thread events that we have marked them as root.
* Find relations and inject them accordingly
+ * @param realm the realm instance
+ * @param roomId the current room Id
* @param eventEntity the current eventEntity received
* @param event the current event received
* @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 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.
+ * @param realm the realm instance
+ * @param roomId the current room Id
* @param event the current event that we examine
* @param eventBody the current body of the 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
*/
private fun handleEventsThatRelatesTo(
@@ -291,9 +296,12 @@ internal class ThreadsAwarenessHandler @Inject constructor(
}
/**
- * Injecting $eventToInject decrypted content as a reply to $event.
- * @param eventToInject the event that will inject
+ * Injecting [eventToInject] decrypted content as a reply to event.
+ * @param roomId the room id
* @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
*/
private fun injectEvent(roomId: String,
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/user/accountdata/AccountDataAPI.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/user/accountdata/AccountDataAPI.kt
index bbeff18c01..178f349ec8 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/user/accountdata/AccountDataAPI.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/user/accountdata/AccountDataAPI.kt
@@ -27,7 +27,7 @@ internal interface AccountDataAPI {
* Set some account_data for the client.
*
* @param userId the user id
- * @param type the type
+ * @param type the type
* @param params the put params
*/
@PUT(NetworkConstants.URI_API_PREFIX_PATH_R0 + "user/{userId}/account_data/{type}")
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/widgets/DefaultWidgetPostAPIMediator.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/widgets/DefaultWidgetPostAPIMediator.kt
index 1da6827916..857105f6ef 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/widgets/DefaultWidgetPostAPIMediator.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/widgets/DefaultWidgetPostAPIMediator.kt
@@ -95,7 +95,7 @@ internal class DefaultWidgetPostAPIMediator @Inject constructor(private val mosh
/**
* Send a boolean response.
*
- * @param response the response
+ * @param response the response
* @param eventData the modular data
*/
override fun sendBoolResponse(response: Boolean, eventData: JsonDict) {
@@ -106,7 +106,7 @@ internal class DefaultWidgetPostAPIMediator @Inject constructor(private val mosh
/**
* Send an integer response.
*
- * @param response the response
+ * @param response the response
* @param eventData the modular data
*/
override fun sendIntegerResponse(response: Int, eventData: JsonDict) {
@@ -116,7 +116,9 @@ internal class DefaultWidgetPostAPIMediator @Inject constructor(private val mosh
/**
* Send an object response.
*
- * @param response the response
+ * @param T the Json type
+ * @param type the type
+ * @param response the response
* @param eventData the modular data
*/
override fun sendObjectResponse(type: Type, response: T?, eventData: JsonDict) {
@@ -145,7 +147,7 @@ internal class DefaultWidgetPostAPIMediator @Inject constructor(private val mosh
/**
* Send an error.
*
- * @param message the error message
+ * @param message the error message
* @param eventData the modular data
*/
override fun sendError(message: String, eventData: JsonDict) {
@@ -162,7 +164,7 @@ internal class DefaultWidgetPostAPIMediator @Inject constructor(private val mosh
/**
* Send the response to the javascript.
*
- * @param jsString the response data
+ * @param jsString the response data
* @param eventData the modular data
*/
private fun sendResponse(jsString: String, eventData: JsonDict) = uiHandler.post {
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/widgets/WidgetsAPI.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/widgets/WidgetsAPI.kt
index b871a317c8..97b40e545e 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/widgets/WidgetsAPI.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/widgets/WidgetsAPI.kt
@@ -27,6 +27,7 @@ internal interface WidgetsAPI {
* Register to the server.
*
* @param body the body content (Ref: https://github.com/matrix-org/matrix-doc/pull/1961)
+ * @param version the widget API version
*/
@POST("register")
suspend fun register(@Body body: OpenIdToken,
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/task/CoroutineSequencer.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/task/CoroutineSequencer.kt
index 80081e3186..dd4c5e7623 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/task/CoroutineSequencer.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/task/CoroutineSequencer.kt
@@ -24,6 +24,7 @@ import kotlinx.coroutines.sync.withPermit
*/
internal interface CoroutineSequencer {
/**
+ * @param T generic type
* @param block the suspendable block to execute
* @return the result of the block
*/
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/util/JsonCanonicalizer.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/util/JsonCanonicalizer.kt
index 94aa238789..c50b7fe675 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/util/JsonCanonicalizer.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/util/JsonCanonicalizer.kt
@@ -53,7 +53,7 @@ internal object JsonCanonicalizer {
/**
* Canonicalize a JSON element.
*
- * @param src the src
+ * @param any the src
* @return the canonicalize element
*/
private fun canonicalizeRecursive(any: Any): String {
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/util/StringUtils.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/util/StringUtils.kt
index d9fd312a6f..c6a417f6eb 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/util/StringUtils.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/util/StringUtils.kt
@@ -54,7 +54,7 @@ internal fun convertFromUTF8(s: String): String {
/**
* Returns whether a string contains an occurrence of another, as a standalone word, regardless of case.
*
- * @param subString the string to search for
+ * @param subString the string to search for
* @return whether a match was found
*/
internal fun String.caseInsensitiveFind(subString: String): Boolean {
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/wellknown/GetWellknownTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/wellknown/GetWellknownTask.kt
index 0d4a5ac28f..31549155d3 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/wellknown/GetWellknownTask.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/wellknown/GetWellknownTask.kt
@@ -75,7 +75,8 @@ internal class DefaultGetWellknownTask @Inject constructor(
* - validate homeserver url and identity server url if provide in .well-known result
* - 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 {
val wellKnownAPI = retrofitFactory.create(client, "https://dummy.org")
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/worker/MatrixWorkerFactory.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/worker/MatrixWorkerFactory.kt
index 95b3662c67..a8ef3e0748 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/worker/MatrixWorkerFactory.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/worker/MatrixWorkerFactory.kt
@@ -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.group.GetGroupDataWorker
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.RedactEventWorker
import org.matrix.android.sdk.internal.session.room.send.SendEventWorker
@@ -64,9 +65,11 @@ internal class MatrixWorkerFactory @Inject constructor(private val sessionManage
SyncWorker(appContext, workerParameters, sessionManager)
UpdateTrustWorker::class.java.name ->
UpdateTrustWorker(appContext, workerParameters, sessionManager)
- UploadContentWorker::class.java.name ->
+ UploadContentWorker::class.java.name ->
UploadContentWorker(appContext, workerParameters, sessionManager)
- else -> {
+ DeactivateLiveLocationShareWorker::class.java.name ->
+ DeactivateLiveLocationShareWorker(appContext, workerParameters, sessionManager)
+ else -> {
Timber.w("No worker defined on MatrixWorkerFactory for $workerClassName will delegate to default.")
// Return null to delegate to the default WorkerFactory.
null
diff --git a/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/session/room/aggregation/poll/PollAggregationProcessorTest.kt b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/session/room/aggregation/poll/PollAggregationProcessorTest.kt
new file mode 100644
index 0000000000..837bbeea26
--- /dev/null
+++ b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/session/room/aggregation/poll/PollAggregationProcessorTest.kt
@@ -0,0 +1,162 @@
+/*
+ * 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.mockk.every
+import io.mockk.mockk
+import io.realm.RealmList
+import io.realm.RealmModel
+import io.realm.RealmQuery
+import org.amshove.kluent.shouldBeFalse
+import org.amshove.kluent.shouldBeTrue
+import org.junit.Before
+import org.junit.Test
+import org.matrix.android.sdk.api.session.Session
+import org.matrix.android.sdk.api.session.getRoom
+import org.matrix.android.sdk.api.session.room.Room
+import org.matrix.android.sdk.api.session.room.getTimelineEvent
+import org.matrix.android.sdk.api.session.room.powerlevels.PowerLevelsHelper
+import org.matrix.android.sdk.internal.database.model.EventAnnotationsSummaryEntity
+import org.matrix.android.sdk.internal.database.model.EventAnnotationsSummaryEntityFields
+import org.matrix.android.sdk.internal.database.model.PollResponseAggregatedSummaryEntity
+import org.matrix.android.sdk.internal.session.room.aggregation.poll.PollEventsTestData.AN_EVENT_ID
+import org.matrix.android.sdk.internal.session.room.aggregation.poll.PollEventsTestData.AN_INVALID_POLL_RESPONSE_EVENT
+import org.matrix.android.sdk.internal.session.room.aggregation.poll.PollEventsTestData.A_BROKEN_POLL_REPLACE_EVENT
+import org.matrix.android.sdk.internal.session.room.aggregation.poll.PollEventsTestData.A_POLL_END_EVENT
+import org.matrix.android.sdk.internal.session.room.aggregation.poll.PollEventsTestData.A_POLL_REFERENCE_EVENT
+import org.matrix.android.sdk.internal.session.room.aggregation.poll.PollEventsTestData.A_POLL_REPLACE_EVENT
+import org.matrix.android.sdk.internal.session.room.aggregation.poll.PollEventsTestData.A_POLL_RESPONSE_EVENT
+import org.matrix.android.sdk.internal.session.room.aggregation.poll.PollEventsTestData.A_POLL_RESPONSE_EVENT_WITH_A_WRONG_REFERENCE
+import org.matrix.android.sdk.internal.session.room.aggregation.poll.PollEventsTestData.A_POLL_START_EVENT
+import org.matrix.android.sdk.internal.session.room.aggregation.poll.PollEventsTestData.A_ROOM_ID
+import org.matrix.android.sdk.internal.session.room.aggregation.poll.PollEventsTestData.A_TIMELINE_EVENT
+import org.matrix.android.sdk.internal.session.room.aggregation.poll.PollEventsTestData.A_USER_ID_1
+import org.matrix.android.sdk.test.fakes.FakeRealm
+
+class PollAggregationProcessorTest {
+
+ private val pollAggregationProcessor: PollAggregationProcessor = DefaultPollAggregationProcessor()
+ private val realm = FakeRealm()
+ private val session = mockk()
+
+ @Before
+ fun setup() {
+ mockEventAnnotationsSummaryEntity()
+ mockRoom(A_ROOM_ID, AN_EVENT_ID)
+ every { session.myUserId } returns A_USER_ID_1
+ }
+
+ @Test
+ fun `given a poll start event, when processing, then is ignored and returns false`() {
+ pollAggregationProcessor.handlePollStartEvent(realm.instance, A_POLL_START_EVENT).shouldBeFalse()
+ }
+
+ @Test
+ fun `given a poll start event with a reference, when processing, then is ignored and returns false`() {
+ pollAggregationProcessor.handlePollStartEvent(realm.instance, A_POLL_REFERENCE_EVENT).shouldBeFalse()
+ }
+
+ @Test
+ fun `given a poll start event with a replace relation but without a target event id, when processing, then is ignored and returns false`() {
+ pollAggregationProcessor.handlePollStartEvent(realm.instance, A_BROKEN_POLL_REPLACE_EVENT).shouldBeFalse()
+ }
+
+ @Test
+ fun `given a poll start event with a replace, when processing, then is processed and returns true`() {
+ pollAggregationProcessor.handlePollStartEvent(realm.instance, A_POLL_REPLACE_EVENT).shouldBeTrue()
+ }
+
+ @Test
+ fun `given a poll response event with a broken reference, when processing, then is ignored and returns false`() {
+ pollAggregationProcessor.handlePollResponseEvent(session, realm.instance, A_POLL_RESPONSE_EVENT_WITH_A_WRONG_REFERENCE).shouldBeFalse()
+ }
+
+ @Test
+ fun `given a poll response event with a reference, when processing, then is processed and returns true`() {
+ every { realm.instance.createObject(PollResponseAggregatedSummaryEntity::class.java) } returns PollResponseAggregatedSummaryEntity()
+ pollAggregationProcessor.handlePollResponseEvent(session, realm.instance, A_POLL_RESPONSE_EVENT).shouldBeTrue()
+ }
+
+ @Test
+ fun `given a poll response event after poll is closed, when processing, then is ignored and returns false`() {
+ every { realm.instance.createObject(PollResponseAggregatedSummaryEntity::class.java) } returns PollResponseAggregatedSummaryEntity().apply {
+ closedTime = (A_POLL_RESPONSE_EVENT.originServerTs ?: 0) - 1
+ }
+ pollAggregationProcessor.handlePollResponseEvent(session, realm.instance, A_POLL_RESPONSE_EVENT).shouldBeFalse()
+ }
+
+ @Test
+ fun `given a poll response event which is already processed, when processing, then is ignored and returns false`() {
+ every { realm.instance.createObject(PollResponseAggregatedSummaryEntity::class.java) } returns PollResponseAggregatedSummaryEntity().apply {
+ sourceEvents = RealmList(A_POLL_RESPONSE_EVENT.eventId)
+ }
+ pollAggregationProcessor.handlePollResponseEvent(session, realm.instance, A_POLL_RESPONSE_EVENT).shouldBeFalse()
+ }
+
+ @Test
+ fun `given a poll response event which is not one of the options, when processing, then is ignored and returns false`() {
+ every { realm.instance.createObject(PollResponseAggregatedSummaryEntity::class.java) } returns PollResponseAggregatedSummaryEntity()
+ pollAggregationProcessor.handlePollResponseEvent(session, realm.instance, AN_INVALID_POLL_RESPONSE_EVENT).shouldBeFalse()
+ }
+
+ @Test
+ fun `given a poll end event, when processing, then is processed and return true`() {
+ every { realm.instance.createObject(PollResponseAggregatedSummaryEntity::class.java) } returns PollResponseAggregatedSummaryEntity()
+ val powerLevelsHelper = mockRedactionPowerLevels(A_USER_ID_1, true)
+ pollAggregationProcessor.handlePollEndEvent(session, powerLevelsHelper, realm.instance, A_POLL_END_EVENT).shouldBeTrue()
+ }
+
+ @Test
+ fun `given a poll end event for my own poll without enough redaction power level, when processing, then is processed and returns true`() {
+ every { realm.instance.createObject(PollResponseAggregatedSummaryEntity::class.java) } returns PollResponseAggregatedSummaryEntity()
+ val powerLevelsHelper = mockRedactionPowerLevels(A_USER_ID_1, false)
+ pollAggregationProcessor.handlePollEndEvent(session, powerLevelsHelper, realm.instance, A_POLL_END_EVENT).shouldBeTrue()
+ }
+
+ @Test
+ fun `given a poll end event without enough redaction power level, when is processed, then is ignored and return false`() {
+ every { realm.instance.createObject(PollResponseAggregatedSummaryEntity::class.java) } returns PollResponseAggregatedSummaryEntity()
+ val powerLevelsHelper = mockRedactionPowerLevels("another-sender-id", false)
+ val event = A_POLL_END_EVENT.copy(senderId = "another-sender-id")
+ pollAggregationProcessor.handlePollEndEvent(session, powerLevelsHelper, realm.instance, event).shouldBeFalse()
+ }
+
+ private inline fun RealmQuery.givenEqualTo(fieldName: String, value: String, result: RealmQuery) {
+ every { equalTo(fieldName, value) } returns result
+ }
+
+ private fun mockEventAnnotationsSummaryEntity() {
+ val queryResult = realm.givenWhereReturns(result = EventAnnotationsSummaryEntity())
+ queryResult.givenEqualTo(EventAnnotationsSummaryEntityFields.ROOM_ID, A_POLL_REPLACE_EVENT.roomId!!, queryResult)
+ queryResult.givenEqualTo(EventAnnotationsSummaryEntityFields.EVENT_ID, A_POLL_REPLACE_EVENT.eventId!!, queryResult)
+ }
+
+ private fun mockRoom(
+ roomId: String,
+ eventId: String
+ ) {
+ val room = mockk()
+ every { session.getRoom(roomId) } returns room
+ every { room.getTimelineEvent(eventId) } returns A_TIMELINE_EVENT
+ }
+
+ private fun mockRedactionPowerLevels(userId: String, isAbleToRedact: Boolean): PowerLevelsHelper {
+ val powerLevelsHelper = mockk()
+ every { powerLevelsHelper.isUserAbleToRedact(userId) } returns isAbleToRedact
+ return powerLevelsHelper
+ }
+}
diff --git a/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/session/room/aggregation/poll/PollEventsTestData.kt b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/session/room/aggregation/poll/PollEventsTestData.kt
new file mode 100644
index 0000000000..129d49633e
--- /dev/null
+++ b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/session/room/aggregation/poll/PollEventsTestData.kt
@@ -0,0 +1,171 @@
+/*
+ * 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 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.RelationType
+import org.matrix.android.sdk.api.session.events.model.toContent
+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.model.message.PollAnswer
+import org.matrix.android.sdk.api.session.room.model.message.PollCreationInfo
+import org.matrix.android.sdk.api.session.room.model.message.PollQuestion
+import org.matrix.android.sdk.api.session.room.model.message.PollResponse
+import org.matrix.android.sdk.api.session.room.model.relation.RelationDefaultContent
+import org.matrix.android.sdk.api.session.room.sender.SenderInfo
+import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent
+
+object PollEventsTestData {
+ internal const val A_USER_ID_1 = "@user_1:matrix.org"
+ internal const val A_ROOM_ID = "!sUeOGZKsBValPTUMax:matrix.org"
+ internal const val AN_EVENT_ID = "\$vApgexcL8Vfh-WxYKsFKCDooo67ttbjm3TiVKXaWijU"
+
+ internal val A_POLL_CONTENT = MessagePollContent(
+ unstablePollCreationInfo = PollCreationInfo(
+ question = PollQuestion(
+ unstableQuestion = "What is your favourite coffee?"
+ ),
+ maxSelections = 1,
+ answers = listOf(
+ PollAnswer(
+ id = "5ef5f7b0-c9a1-49cf-a0b3-374729a43e76",
+ unstableAnswer = "Double Espresso"
+ ),
+ PollAnswer(
+ id = "ec1a4db0-46d8-4d7a-9bb6-d80724715938",
+ unstableAnswer = "Macchiato"
+ ),
+ PollAnswer(
+ id = "3677ca8e-061b-40ab-bffe-b22e4e88fcad",
+ unstableAnswer = "Iced Coffee"
+ )
+ )
+ )
+ )
+
+ internal val A_POLL_RESPONSE_CONTENT = MessagePollResponseContent(
+ unstableResponse = PollResponse(
+ answers = listOf("5ef5f7b0-c9a1-49cf-a0b3-374729a43e76")
+ ),
+ relatesTo = RelationDefaultContent(
+ type = RelationType.REFERENCE,
+ eventId = AN_EVENT_ID
+ )
+ )
+
+ internal val A_POLL_END_CONTENT = MessageEndPollContent(
+ relatesTo = RelationDefaultContent(
+ type = RelationType.REFERENCE,
+ eventId = AN_EVENT_ID
+ )
+ )
+
+ internal val AN_INVALID_POLL_RESPONSE_CONTENT = MessagePollResponseContent(
+ unstableResponse = PollResponse(
+ answers = listOf("fake-option-id")
+ ),
+ relatesTo = RelationDefaultContent(
+ type = RelationType.REFERENCE,
+ eventId = AN_EVENT_ID
+ )
+ )
+
+ internal val A_POLL_START_EVENT = Event(
+ type = EventType.POLL_START.first(),
+ eventId = AN_EVENT_ID,
+ originServerTs = 1652435922563,
+ senderId = A_USER_ID_1,
+ roomId = A_ROOM_ID,
+ content = A_POLL_CONTENT.toContent()
+ )
+
+ internal val A_POLL_RESPONSE_EVENT = Event(
+ type = EventType.POLL_RESPONSE.first(),
+ eventId = AN_EVENT_ID,
+ originServerTs = 1652435922563,
+ senderId = A_USER_ID_1,
+ roomId = A_ROOM_ID,
+ content = A_POLL_RESPONSE_CONTENT.toContent()
+ )
+
+ internal val A_POLL_END_EVENT = Event(
+ type = EventType.POLL_END.first(),
+ eventId = AN_EVENT_ID,
+ originServerTs = 1652435922563,
+ senderId = A_USER_ID_1,
+ roomId = A_ROOM_ID,
+ content = A_POLL_END_CONTENT.toContent()
+ )
+
+ internal val A_TIMELINE_EVENT = TimelineEvent(
+ root = A_POLL_START_EVENT,
+ localId = 1234,
+ eventId = AN_EVENT_ID,
+ displayIndex = 0,
+ senderInfo = SenderInfo(A_USER_ID_1, "A_USER_ID_1", true, null)
+ )
+
+ internal val A_POLL_RESPONSE_EVENT_WITH_A_WRONG_REFERENCE = A_POLL_RESPONSE_EVENT.copy(
+ content = A_POLL_RESPONSE_CONTENT
+ .copy(
+ relatesTo = RelationDefaultContent(
+ type = RelationType.REPLACE,
+ eventId = null
+ )
+ )
+ .toContent()
+ )
+
+ internal val A_POLL_REPLACE_EVENT = A_POLL_START_EVENT.copy(
+ content = A_POLL_CONTENT
+ .copy(
+ relatesTo = RelationDefaultContent(
+ type = RelationType.REPLACE,
+ eventId = AN_EVENT_ID
+ )
+ )
+ .toContent()
+ )
+
+ internal val A_BROKEN_POLL_REPLACE_EVENT = A_POLL_START_EVENT.copy(
+ content = A_POLL_CONTENT
+ .copy(
+ relatesTo = RelationDefaultContent(
+ type = RelationType.REPLACE,
+ eventId = null
+ )
+ )
+ .toContent()
+ )
+
+ internal val A_POLL_REFERENCE_EVENT = A_POLL_START_EVENT.copy(
+ content = A_POLL_CONTENT
+ .copy(
+ relatesTo = RelationDefaultContent(
+ type = RelationType.REFERENCE,
+ eventId = AN_EVENT_ID
+ )
+ )
+ .toContent()
+ )
+
+ internal val AN_INVALID_POLL_RESPONSE_EVENT = A_POLL_RESPONSE_EVENT.copy(
+ content = AN_INVALID_POLL_RESPONSE_CONTENT.toContent()
+ )
+}
diff --git a/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fakes/FakeRealm.kt b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fakes/FakeRealm.kt
new file mode 100644
index 0000000000..c07f8e1873
--- /dev/null
+++ b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fakes/FakeRealm.kt
@@ -0,0 +1,36 @@
+/*
+ * 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.test.fakes
+
+import io.mockk.every
+import io.mockk.mockk
+import io.realm.Realm
+import io.realm.RealmModel
+import io.realm.RealmQuery
+import io.realm.kotlin.where
+
+internal class FakeRealm {
+
+ val instance = mockk(relaxed = true)
+
+ inline fun givenWhereReturns(result: T?): RealmQuery {
+ val queryResult = mockk>()
+ every { queryResult.findFirst() } returns result
+ every { instance.where() } returns queryResult
+ return queryResult
+ }
+}
diff --git a/tools/check/forbidden_strings_in_code.txt b/tools/check/forbidden_strings_in_code.txt
index 7362ff2d10..962a14843d 100755
--- a/tools/check/forbidden_strings_in_code.txt
+++ b/tools/check/forbidden_strings_in_code.txt
@@ -177,3 +177,6 @@ R\.string\.template_
### Use the Clock interface, or use `measureTimeMillis`
System\.currentTimeMillis\(\)===2
+
+### Remove extra space between the name and the description
+\* @\w+ \w+ +
diff --git a/tools/detekt/detekt.yml b/tools/detekt/detekt.yml
index 322f29e5b7..a836edc47a 100644
--- a/tools/detekt/detekt.yml
+++ b/tools/detekt/detekt.yml
@@ -87,8 +87,7 @@ comments:
EndOfSentenceFormat:
active: true
OutdatedDocumentation:
- # TODO Enable it
- active: false
+ active: true
UndocumentedPublicClass:
active: false
UndocumentedPublicFunction:
diff --git a/vector/build.gradle b/vector/build.gradle
index 99ced285cc..766da4c321 100644
--- a/vector/build.gradle
+++ b/vector/build.gradle
@@ -505,9 +505,14 @@ dependencies {
implementation 'commons-codec:commons-codec:1.15'
// MapTiler
- implementation 'org.maplibre.gl:android-sdk:9.5.2'
- implementation 'org.maplibre.gl:android-plugin-annotation-v9:1.0.0'
-
+ fdroidImplementation(libs.maplibre.androidSdk) {
+ exclude group: 'com.google.android.gms', module: 'play-services-location'
+ }
+ fdroidImplementation(libs.maplibre.pluginAnnotation) {
+ exclude group: 'com.google.android.gms', module: 'play-services-location'
+ }
+ gplayImplementation libs.maplibre.androidSdk
+ gplayImplementation libs.maplibre.pluginAnnotation
// TESTS
testImplementation libs.tests.junit
diff --git a/vector/src/debug/java/im/vector/app/features/debug/features/DebugFeaturesStateFactory.kt b/vector/src/debug/java/im/vector/app/features/debug/features/DebugFeaturesStateFactory.kt
index f2904e4b1a..aa4df5e308 100644
--- a/vector/src/debug/java/im/vector/app/features/debug/features/DebugFeaturesStateFactory.kt
+++ b/vector/src/debug/java/im/vector/app/features/debug/features/DebugFeaturesStateFactory.kt
@@ -61,9 +61,9 @@ class DebugFeaturesStateFactory @Inject constructor(
factory = VectorFeatures::isOnboardingCombinedRegisterEnabled
),
createBooleanFeature(
- label = "Live location sharing",
- key = DebugFeatureKeys.liveLocationSharing,
- factory = VectorFeatures::isLiveLocationEnabled
+ label = "FTUE Combined login",
+ key = DebugFeatureKeys.onboardingCombinedLogin,
+ factory = VectorFeatures::isOnboardingCombinedLoginEnabled
),
)
)
diff --git a/vector/src/debug/java/im/vector/app/features/debug/features/DebugVectorFeatures.kt b/vector/src/debug/java/im/vector/app/features/debug/features/DebugVectorFeatures.kt
index 07fab8a58d..f36b1a804a 100644
--- a/vector/src/debug/java/im/vector/app/features/debug/features/DebugVectorFeatures.kt
+++ b/vector/src/debug/java/im/vector/app/features/debug/features/DebugVectorFeatures.kt
@@ -57,8 +57,8 @@ class DebugVectorFeatures(
override fun isOnboardingCombinedRegisterEnabled(): Boolean = read(DebugFeatureKeys.onboardingCombinedRegister)
?: vectorFeatures.isOnboardingCombinedRegisterEnabled()
- override fun isLiveLocationEnabled(): Boolean = read(DebugFeatureKeys.liveLocationSharing)
- ?: vectorFeatures.isLiveLocationEnabled()
+ override fun isOnboardingCombinedLoginEnabled(): Boolean = read(DebugFeatureKeys.onboardingCombinedLogin)
+ ?: vectorFeatures.isOnboardingCombinedLoginEnabled()
override fun isScreenSharingEnabled(): Boolean = read(DebugFeatureKeys.screenSharing)
?: vectorFeatures.isScreenSharingEnabled()
@@ -116,6 +116,7 @@ object DebugFeatureKeys {
val onboardingUseCase = booleanPreferencesKey("onboarding-splash-carousel")
val onboardingPersonalize = booleanPreferencesKey("onboarding-personalize")
val onboardingCombinedRegister = booleanPreferencesKey("onboarding-combined-register")
+ val onboardingCombinedLogin = booleanPreferencesKey("onboarding-combined-login")
val liveLocationSharing = booleanPreferencesKey("live-location-sharing")
val screenSharing = booleanPreferencesKey("screen-sharing")
}
diff --git a/vector/src/fdroid/java/im/vector/app/push/fcm/FcmHelper.kt b/vector/src/fdroid/java/im/vector/app/push/fcm/FcmHelper.kt
index 425fd1081a..6e36d5dd81 100755
--- a/vector/src/fdroid/java/im/vector/app/push/fcm/FcmHelper.kt
+++ b/vector/src/fdroid/java/im/vector/app/push/fcm/FcmHelper.kt
@@ -46,7 +46,7 @@ object FcmHelper {
* Store FCM token to the SharedPrefs
*
* @param context android context
- * @param token the token to store
+ * @param token the token to store
*/
fun storeFcmToken(context: Context, token: String?) {
// No op
diff --git a/vector/src/gplay/java/im/vector/app/push/fcm/FcmHelper.kt b/vector/src/gplay/java/im/vector/app/push/fcm/FcmHelper.kt
index 3d44f10f76..74ab3b38f1 100755
--- a/vector/src/gplay/java/im/vector/app/push/fcm/FcmHelper.kt
+++ b/vector/src/gplay/java/im/vector/app/push/fcm/FcmHelper.kt
@@ -53,7 +53,7 @@ object FcmHelper {
* TODO Store in realm
*
* @param context android context
- * @param token the token to store
+ * @param token the token to store
*/
fun storeFcmToken(context: Context,
token: String?) {
diff --git a/vector/src/main/AndroidManifest.xml b/vector/src/main/AndroidManifest.xml
index 20b7c4908a..8c2e25bc7e 100644
--- a/vector/src/main/AndroidManifest.xml
+++ b/vector/src/main/AndroidManifest.xml
@@ -306,7 +306,9 @@
android:supportsPictureInPicture="true" />
-
+
+
@@ -343,6 +345,7 @@
+
diff --git a/vector/src/main/java/im/vector/app/core/di/FragmentModule.kt b/vector/src/main/java/im/vector/app/core/di/FragmentModule.kt
index c68a35f4e5..e76f0ad672 100644
--- a/vector/src/main/java/im/vector/app/core/di/FragmentModule.kt
+++ b/vector/src/main/java/im/vector/app/core/di/FragmentModule.kt
@@ -101,8 +101,13 @@ import im.vector.app.features.onboarding.ftueauth.FtueAuthAccountCreatedFragment
import im.vector.app.features.onboarding.ftueauth.FtueAuthCaptchaFragment
import im.vector.app.features.onboarding.ftueauth.FtueAuthChooseDisplayNameFragment
import im.vector.app.features.onboarding.ftueauth.FtueAuthChooseProfilePictureFragment
+import im.vector.app.features.onboarding.ftueauth.FtueAuthCombinedLoginFragment
+import im.vector.app.features.onboarding.ftueauth.FtueAuthCombinedRegisterFragment
+import im.vector.app.features.onboarding.ftueauth.FtueAuthCombinedServerSelectionFragment
+import im.vector.app.features.onboarding.ftueauth.FtueAuthEmailEntryFragment
import im.vector.app.features.onboarding.ftueauth.FtueAuthGenericTextInputFormFragment
import im.vector.app.features.onboarding.ftueauth.FtueAuthLegacyStyleCaptchaFragment
+import im.vector.app.features.onboarding.ftueauth.FtueAuthLegacyWaitForEmailFragment
import im.vector.app.features.onboarding.ftueauth.FtueAuthLoginFragment
import im.vector.app.features.onboarding.ftueauth.FtueAuthPersonalizationCompleteFragment
import im.vector.app.features.onboarding.ftueauth.FtueAuthResetPasswordFragment
@@ -474,6 +479,11 @@ interface FragmentModule {
@FragmentKey(FtueAuthWaitForEmailFragment::class)
fun bindFtueAuthWaitForEmailFragment(fragment: FtueAuthWaitForEmailFragment): Fragment
+ @Binds
+ @IntoMap
+ @FragmentKey(FtueAuthLegacyWaitForEmailFragment::class)
+ fun bindFtueAuthLegacyWaitForEmailFragment(fragment: FtueAuthLegacyWaitForEmailFragment): Fragment
+
@Binds
@IntoMap
@FragmentKey(FtueAuthWebFragment::class)
@@ -494,6 +504,11 @@ interface FragmentModule {
@FragmentKey(FtueAuthAccountCreatedFragment::class)
fun bindFtueAuthAccountCreatedFragment(fragment: FtueAuthAccountCreatedFragment): Fragment
+ @Binds
+ @IntoMap
+ @FragmentKey(FtueAuthEmailEntryFragment::class)
+ fun bindFtueAuthEmailEntryFragment(fragment: FtueAuthEmailEntryFragment): Fragment
+
@Binds
@IntoMap
@FragmentKey(FtueAuthChooseDisplayNameFragment::class)
@@ -509,6 +524,21 @@ interface FragmentModule {
@FragmentKey(FtueAuthPersonalizationCompleteFragment::class)
fun bindFtueAuthPersonalizationCompleteFragment(fragment: FtueAuthPersonalizationCompleteFragment): Fragment
+ @Binds
+ @IntoMap
+ @FragmentKey(FtueAuthCombinedLoginFragment::class)
+ fun bindFtueAuthCombinedLoginFragment(fragment: FtueAuthCombinedLoginFragment): Fragment
+
+ @Binds
+ @IntoMap
+ @FragmentKey(FtueAuthCombinedRegisterFragment::class)
+ fun bindFtueAuthCombinedRegisterFragment(fragment: FtueAuthCombinedRegisterFragment): Fragment
+
+ @Binds
+ @IntoMap
+ @FragmentKey(FtueAuthCombinedServerSelectionFragment::class)
+ fun bindFtueAuthCombinedServerSelectionFragment(fragment: FtueAuthCombinedServerSelectionFragment): Fragment
+
@Binds
@IntoMap
@FragmentKey(UserListFragment::class)
diff --git a/vector/src/main/java/im/vector/app/core/dialogs/UnrecognizedCertificateDialog.kt b/vector/src/main/java/im/vector/app/core/dialogs/UnrecognizedCertificateDialog.kt
index c43b2e4f09..8eaced1c48 100644
--- a/vector/src/main/java/im/vector/app/core/dialogs/UnrecognizedCertificateDialog.kt
+++ b/vector/src/main/java/im/vector/app/core/dialogs/UnrecognizedCertificateDialog.kt
@@ -39,8 +39,9 @@ class UnrecognizedCertificateDialog @Inject constructor(
* Display a certificate dialog box, asking the user about an unknown certificate
* To use when user is currently logged in.
*
+ * @param activity the Android activity
* @param unrecognizedFingerprint the fingerprint for the unknown certificate
- * @param callback callback to fire when the user makes a decision
+ * @param callback callback to fire when the user makes a decision
*/
fun show(activity: Activity,
unrecognizedFingerprint: Fingerprint,
@@ -80,9 +81,13 @@ class UnrecognizedCertificateDialog @Inject constructor(
/**
* Display a certificate dialog box, asking the user about an unknown certificate.
*
+ * @param activity the Activity
* @param unrecognizedFingerprint the fingerprint for the unknown certificate
- * @param existing the current session already exist, so it mean that something has changed server side
- * @param callback callback to fire when the user makes a decision
+ * @param existing the current session already exist, so it mean that something has changed server side
+ * @param callback callback to fire when the user makes a decision
+ * @param userId the matrix userId
+ * @param homeServerUrl the homeserver url
+ * @param homeServerConnectionConfigHasFingerprints true if the homeServerConnectionConfig has fingerprint
*/
private fun internalShow(activity: Activity,
unrecognizedFingerprint: Fingerprint,
diff --git a/vector/src/main/java/im/vector/app/core/extensions/Fragment.kt b/vector/src/main/java/im/vector/app/core/extensions/Fragment.kt
index dfbd2eba97..61c4fe2174 100644
--- a/vector/src/main/java/im/vector/app/core/extensions/Fragment.kt
+++ b/vector/src/main/java/im/vector/app/core/extensions/Fragment.kt
@@ -36,9 +36,10 @@ fun Fragment.registerStartForActivityResult(onResult: (ActivityResult) -> Unit):
fun Fragment.addFragment(
frameId: Int,
fragment: Fragment,
+ tag: String? = null,
allowStateLoss: Boolean = false
) {
- parentFragmentManager.commitTransaction(allowStateLoss) { add(frameId, fragment) }
+ parentFragmentManager.commitTransaction(allowStateLoss) { add(frameId, fragment, tag) }
}
fun Fragment.addFragment(
diff --git a/vector/src/main/java/im/vector/app/core/extensions/Job.kt b/vector/src/main/java/im/vector/app/core/extensions/Job.kt
new file mode 100644
index 0000000000..d9a4332ef2
--- /dev/null
+++ b/vector/src/main/java/im/vector/app/core/extensions/Job.kt
@@ -0,0 +1,33 @@
+/*
+ * Copyright (c) 2022 New Vector Ltd
+ *
+ * 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 im.vector.app.core.extensions
+
+import kotlinx.coroutines.Job
+import kotlin.properties.ReadWriteProperty
+import kotlin.reflect.KProperty
+
+/**
+ * Property delegate for automatically cancelling the current job when setting a new value.
+ */
+fun cancelCurrentOnSet(): ReadWriteProperty = object : ReadWriteProperty {
+ private var currentJob: Job? = null
+ override fun getValue(thisRef: Any?, property: KProperty<*>): Job? = currentJob
+ override fun setValue(thisRef: Any?, property: KProperty<*>, value: Job?) {
+ currentJob?.cancel()
+ currentJob = value
+ }
+}
diff --git a/vector/src/main/java/im/vector/app/core/extensions/TextInputLayout.kt b/vector/src/main/java/im/vector/app/core/extensions/TextInputLayout.kt
index d9b92e78b7..205a0f40c4 100644
--- a/vector/src/main/java/im/vector/app/core/extensions/TextInputLayout.kt
+++ b/vector/src/main/java/im/vector/app/core/extensions/TextInputLayout.kt
@@ -16,7 +16,11 @@
package im.vector.app.core.extensions
+import android.text.Editable
+import android.view.View
+import android.view.inputmethod.EditorInfo
import com.google.android.material.textfield.TextInputLayout
+import im.vector.app.core.platform.SimpleTextWatcher
import kotlinx.coroutines.flow.map
import reactivecircus.flowbinding.android.widget.textChanges
@@ -30,3 +34,26 @@ fun TextInputLayout.hasSurroundingSpaces() = editText().text.toString().let { it
fun TextInputLayout.hasContentFlow(mapper: (CharSequence) -> CharSequence = { it }) = editText().textChanges().map { mapper(it).isNotEmpty() }
fun TextInputLayout.content() = editText().text.toString()
+
+fun TextInputLayout.hasContent() = !editText().text.isNullOrEmpty()
+
+fun TextInputLayout.associateContentStateWith(button: View) {
+ editText().addTextChangedListener(object : SimpleTextWatcher() {
+ override fun afterTextChanged(s: Editable) {
+ val newContent = s.toString()
+ button.isEnabled = newContent.isNotEmpty()
+ }
+ })
+}
+
+fun TextInputLayout.setOnImeDoneListener(action: () -> Unit) {
+ editText().setOnEditorActionListener { _, actionId, _ ->
+ when (actionId) {
+ EditorInfo.IME_ACTION_DONE -> {
+ action()
+ true
+ }
+ else -> false
+ }
+ }
+}
diff --git a/vector/src/main/java/im/vector/app/core/intent/ExternalIntentData.kt b/vector/src/main/java/im/vector/app/core/intent/ExternalIntentData.kt
index 142a7a6782..1d7247d758 100644
--- a/vector/src/main/java/im/vector/app/core/intent/ExternalIntentData.kt
+++ b/vector/src/main/java/im/vector/app/core/intent/ExternalIntentData.kt
@@ -29,9 +29,11 @@ sealed class ExternalIntentData {
/**
* Constructor for a text message.
*
- * @param text the text
- * @param htmlText the HTML text
- * @param format the formatted text format
+ * @property text the text
+ * @property htmlText the HTML text
+ * @property format the formatted text format
+ * @property clipDataItem the ClipData
+ * @property mimeType the mimetype
*/
data class IntentDataText(
val text: CharSequence? = null,
@@ -52,8 +54,8 @@ sealed class ExternalIntentData {
/**
* Constructor from a media Uri/.
*
- * @param uri the media uri
- * @param filename the media file name
+ * @property uri the media uri
+ * @property filename the media file name
*/
data class IntentDataUri(
val uri: Uri,
diff --git a/vector/src/main/java/im/vector/app/core/intent/VectorMimeType.kt b/vector/src/main/java/im/vector/app/core/intent/VectorMimeType.kt
index e68b5e1b07..38e304e1ce 100644
--- a/vector/src/main/java/im/vector/app/core/intent/VectorMimeType.kt
+++ b/vector/src/main/java/im/vector/app/core/intent/VectorMimeType.kt
@@ -27,6 +27,7 @@ import java.util.Locale
* Returns the mimetype from a uri.
*
* @param context the context
+ * @param uri the uri
* @return the mimetype
*/
fun getMimeTypeFromUri(context: Context, uri: Uri): String? {
diff --git a/vector/src/main/java/im/vector/app/core/preference/PushRulePreference.kt b/vector/src/main/java/im/vector/app/core/preference/PushRulePreference.kt
index dad7f26560..bea29195c9 100644
--- a/vector/src/main/java/im/vector/app/core/preference/PushRulePreference.kt
+++ b/vector/src/main/java/im/vector/app/core/preference/PushRulePreference.kt
@@ -45,7 +45,7 @@ class PushRulePreference : VectorPreference {
/**
* Update the notification index.
*
- * @param pushRule
+ * @param notificationIndex the new notification index
*/
fun setIndex(notificationIndex: NotificationIndex?) {
index = notificationIndex
diff --git a/vector/src/main/java/im/vector/app/core/resources/Resource.kt b/vector/src/main/java/im/vector/app/core/resources/Resource.kt
index f14c9b834d..861dfdb781 100644
--- a/vector/src/main/java/im/vector/app/core/resources/Resource.kt
+++ b/vector/src/main/java/im/vector/app/core/resources/Resource.kt
@@ -56,8 +56,8 @@ data class Resource(
/**
* Get a resource stream and metadata about it given its URI returned from onActivityResult.
*
- * @param context the context.
- * @param uri the URI
+ * @param context the context.
+ * @param uri the URI
* @param providedMimetype the mimetype
* @return a [Resource] encapsulating the opened resource stream and associated metadata
* or `null` if opening the resource stream failed.
diff --git a/vector/src/main/java/im/vector/app/core/ui/views/KeysBackupBanner.kt b/vector/src/main/java/im/vector/app/core/ui/views/KeysBackupBanner.kt
index f2ea79984e..80603aa3bf 100755
--- a/vector/src/main/java/im/vector/app/core/ui/views/KeysBackupBanner.kt
+++ b/vector/src/main/java/im/vector/app/core/ui/views/KeysBackupBanner.kt
@@ -54,6 +54,7 @@ class KeysBackupBanner @JvmOverloads constructor(
* This methods is responsible for rendering the view according to the newState.
*
* @param newState the newState representing the view
+ * @param force true to force the rendering of the view
*/
fun render(newState: State, force: Boolean = false) {
if (newState == state && !force) {
diff --git a/vector/src/main/java/im/vector/app/core/utils/Dialogs.kt b/vector/src/main/java/im/vector/app/core/utils/Dialogs.kt
index 10ab0fc027..9f3e6a91cf 100644
--- a/vector/src/main/java/im/vector/app/core/utils/Dialogs.kt
+++ b/vector/src/main/java/im/vector/app/core/utils/Dialogs.kt
@@ -30,7 +30,7 @@ import me.gujun.android.span.span
/**
* Open a web view above the current activity.
*
- * @param url the url to open
+ * @param url the url to open
*/
fun Context.displayInWebView(url: String) {
val wv = WebView(this)
diff --git a/vector/src/main/java/im/vector/app/core/utils/Emoji.kt b/vector/src/main/java/im/vector/app/core/utils/Emoji.kt
index d6a63dca10..3e82ecd5f2 100644
--- a/vector/src/main/java/im/vector/app/core/utils/Emoji.kt
+++ b/vector/src/main/java/im/vector/app/core/utils/Emoji.kt
@@ -16,7 +16,7 @@
package im.vector.app.core.utils
-import com.vanniktech.emoji.EmojiUtils
+import com.vanniktech.emoji.isOnlyEmojis
/**
* Test if a string contains emojis.
@@ -28,7 +28,7 @@ import com.vanniktech.emoji.EmojiUtils
*/
fun containsOnlyEmojis(str: String?): Boolean {
// Now rely on vanniktech library
- return EmojiUtils.isOnlyEmojis(str)
+ return str.isOnlyEmojis()
}
/**
diff --git a/vector/src/main/java/im/vector/app/core/utils/EvenBetterLinkMovementMethod.kt b/vector/src/main/java/im/vector/app/core/utils/EvenBetterLinkMovementMethod.kt
index b9c1386933..a53c8161b1 100644
--- a/vector/src/main/java/im/vector/app/core/utils/EvenBetterLinkMovementMethod.kt
+++ b/vector/src/main/java/im/vector/app/core/utils/EvenBetterLinkMovementMethod.kt
@@ -26,9 +26,9 @@ class EvenBetterLinkMovementMethod(private val onLinkClickListener: OnLinkClickL
interface OnLinkClickListener {
/**
- * @param textView The TextView on which a click was registered.
- * @param span The ClickableSpan which is clicked on.
- * @param url The clicked URL.
+ * @param textView The TextView on which a click was registered.
+ * @param span The ClickableSpan which is clicked on.
+ * @param url The clicked URL.
* @param actualText The original text which is spanned. Can be used to compare actualText and target url to prevent misleading urls.
* @return true if this click was handled, false to let Android handle the URL.
*/
diff --git a/vector/src/main/java/im/vector/app/core/utils/ExternalApplicationsUtil.kt b/vector/src/main/java/im/vector/app/core/utils/ExternalApplicationsUtil.kt
index 8bfbcaeb92..9616e35840 100644
--- a/vector/src/main/java/im/vector/app/core/utils/ExternalApplicationsUtil.kt
+++ b/vector/src/main/java/im/vector/app/core/utils/ExternalApplicationsUtil.kt
@@ -170,9 +170,9 @@ fun openUri(activity: Activity, uri: String) {
/**
* Send media to a third party application.
*
- * @param activity the activity
+ * @param activity the activity
* @param savedMediaPath the media path
- * @param mimeType the media mime type.
+ * @param mimeType the media mime type.
*/
fun openMedia(activity: Activity, savedMediaPath: String, mimeType: String) {
val file = File(savedMediaPath)
@@ -415,8 +415,8 @@ fun selectTxtFileToWrite(
*
* ~~ This is copied from the old matrix sdk ~~
*
- * @param sourceFile the file source path
- * @param dstDirPath the dst path
+ * @param sourceFile the file source path
+ * @param dstDirPath the dst path
* @param outputFilename optional the output filename
* @param currentTimeMillis the current time in milliseconds
* @return the created file
diff --git a/vector/src/main/java/im/vector/app/core/utils/PermissionsTools.kt b/vector/src/main/java/im/vector/app/core/utils/PermissionsTools.kt
index b4f8de2485..a41abba7ab 100644
--- a/vector/src/main/java/im/vector/app/core/utils/PermissionsTools.kt
+++ b/vector/src/main/java/im/vector/app/core/utils/PermissionsTools.kt
@@ -101,9 +101,9 @@ private fun onPermissionResult(result: Map, lambda: (allGranted
* explain why vector needs the corresponding permission.
*
* @param permissionsToBeGranted the permissions to be granted
- * @param activity the calling Activity that is requesting the permissions (or fragment parent)
+ * @param activity the calling Activity that is requesting the permissions (or fragment parent)
* @param activityResultLauncher from the calling fragment/Activity that is requesting the permissions
- * @param rationaleMessage message to be displayed BEFORE requesting for the permission
+ * @param rationaleMessage message to be displayed BEFORE requesting for the permission
* @return true if the permissions are granted (synchronous flow), false otherwise (asynchronous flow)
*/
fun checkPermissions(permissionsToBeGranted: List,
@@ -145,7 +145,7 @@ fun checkPermissions(permissionsToBeGranted: List,
* To be call after the permission request.
*
* @param permissionsToBeGranted the permissions to be granted
- * @param activity the calling Activity that is requesting the permissions (or fragment parent)
+ * @param activity the calling Activity that is requesting the permissions (or fragment parent)
*
* @return true if one of the permission has been denied and the user check the do not ask again checkbox
*/
diff --git a/vector/src/main/java/im/vector/app/core/utils/RingtoneUtils.kt b/vector/src/main/java/im/vector/app/core/utils/RingtoneUtils.kt
index a0fd3addac..bbed2f6000 100644
--- a/vector/src/main/java/im/vector/app/core/utils/RingtoneUtils.kt
+++ b/vector/src/main/java/im/vector/app/core/utils/RingtoneUtils.kt
@@ -90,6 +90,7 @@ fun getCallRingtoneName(context: Context): String? {
/**
* Sets the selected ringtone for riot calls.
*
+ * @param context Android context
* @param ringtoneUri
* @see Ringtone
*/
diff --git a/vector/src/main/java/im/vector/app/core/utils/SpannableUtils.kt b/vector/src/main/java/im/vector/app/core/utils/SpannableUtils.kt
index 69702fc793..aa1917e326 100644
--- a/vector/src/main/java/im/vector/app/core/utils/SpannableUtils.kt
+++ b/vector/src/main/java/im/vector/app/core/utils/SpannableUtils.kt
@@ -22,6 +22,7 @@ import android.text.style.ForegroundColorSpan
import android.text.style.StyleSpan
import androidx.annotation.ColorInt
import me.gujun.android.span.Span
+import me.gujun.android.span.span
fun Spannable.styleMatchingText(match: String, typeFace: Int): Spannable {
if (match.isEmpty()) return this
@@ -56,3 +57,17 @@ fun Span.bullet(text: CharSequence = "",
build()
})
}
+
+fun String.colorTerminatingFullStop(@ColorInt color: Int): CharSequence {
+ val fullStop = "."
+ return if (endsWith(fullStop)) {
+ span {
+ +this@colorTerminatingFullStop.removeSuffix(fullStop)
+ span(fullStop) {
+ textColor = color
+ }
+ }
+ } else {
+ this
+ }
+}
diff --git a/vector/src/main/java/im/vector/app/core/utils/SystemUtils.kt b/vector/src/main/java/im/vector/app/core/utils/SystemUtils.kt
index 18a467d8d0..1d9ac6c3ef 100644
--- a/vector/src/main/java/im/vector/app/core/utils/SystemUtils.kt
+++ b/vector/src/main/java/im/vector/app/core/utils/SystemUtils.kt
@@ -82,7 +82,9 @@ fun requestDisablingBatteryOptimization(activity: Activity, activityResultLaunch
* Copy a text to the clipboard, and display a Toast when done.
*
* @param context the context
- * @param text the text to copy
+ * @param text the text to copy
+ * @param showToast true to also show a Toast to the user
+ * @param toastMessage content of the toast message as a String resource
*/
fun copyToClipboard(context: Context, text: CharSequence, showToast: Boolean = true, @StringRes toastMessage: Int = R.string.copied_to_clipboard) {
val clipboard = context.getSystemService()!!
diff --git a/vector/src/main/java/im/vector/app/core/utils/TemporaryStore.kt b/vector/src/main/java/im/vector/app/core/utils/TemporaryStore.kt
index fb386e0876..bd1e396126 100644
--- a/vector/src/main/java/im/vector/app/core/utils/TemporaryStore.kt
+++ b/vector/src/main/java/im/vector/app/core/utils/TemporaryStore.kt
@@ -23,7 +23,8 @@ const val THREE_MINUTES = 3 * 60_000L
/**
* Store an object T for a specific period of time.
- * @param delay delay to keep the data, in millis
+ * @param T type of the data to store
+ * @property delay delay to keep the data, in millis
*/
open class TemporaryStore(private val delay: Long = THREE_MINUTES) {
diff --git a/vector/src/main/java/im/vector/app/features/VectorFeatures.kt b/vector/src/main/java/im/vector/app/features/VectorFeatures.kt
index 42693a53f9..6a7a0865de 100644
--- a/vector/src/main/java/im/vector/app/features/VectorFeatures.kt
+++ b/vector/src/main/java/im/vector/app/features/VectorFeatures.kt
@@ -26,7 +26,7 @@ interface VectorFeatures {
fun isOnboardingUseCaseEnabled(): Boolean
fun isOnboardingPersonalizeEnabled(): Boolean
fun isOnboardingCombinedRegisterEnabled(): Boolean
- fun isLiveLocationEnabled(): Boolean
+ fun isOnboardingCombinedLoginEnabled(): Boolean
fun isScreenSharingEnabled(): Boolean
enum class OnboardingVariant {
@@ -43,6 +43,6 @@ class DefaultVectorFeatures : VectorFeatures {
override fun isOnboardingUseCaseEnabled() = true
override fun isOnboardingPersonalizeEnabled() = false
override fun isOnboardingCombinedRegisterEnabled() = false
- override fun isLiveLocationEnabled(): Boolean = false
+ override fun isOnboardingCombinedLoginEnabled() = false
override fun isScreenSharingEnabled(): Boolean = true
}
diff --git a/vector/src/main/java/im/vector/app/features/call/audio/CallAudioManager.kt b/vector/src/main/java/im/vector/app/features/call/audio/CallAudioManager.kt
index d4793640d3..6577d0374d 100644
--- a/vector/src/main/java/im/vector/app/features/call/audio/CallAudioManager.kt
+++ b/vector/src/main/java/im/vector/app/features/call/audio/CallAudioManager.kt
@@ -122,6 +122,7 @@ class CallAudioManager(private val context: Context, val configChange: (() -> Un
* Updates the audio route for the given mode.
*
* @param mode the audio mode to be used when computing the audio route.
+ * @param force true to force setting the audio route
* @return `true` if the audio route was updated successfully;
* `false`, otherwise.
*/
diff --git a/vector/src/main/java/im/vector/app/features/command/CommandParser.kt b/vector/src/main/java/im/vector/app/features/command/CommandParser.kt
index 49e35687f4..17b8087601 100644
--- a/vector/src/main/java/im/vector/app/features/command/CommandParser.kt
+++ b/vector/src/main/java/im/vector/app/features/command/CommandParser.kt
@@ -30,7 +30,8 @@ class CommandParser @Inject constructor() {
/**
* Convert the text message into a Slash command.
*
- * @param textMessage the text message
+ * @param textMessage the text message
+ * @param isInThreadTimeline true if the user is currently typing in a thread
* @return a parsed slash command (ok or error)
*/
fun parseSlashCommand(textMessage: CharSequence, isInThreadTimeline: Boolean): ParsedCommand {
@@ -412,8 +413,8 @@ class CommandParser @Inject constructor() {
/**
* Checks whether or not the current command is not supported by threads.
- * @param slashCommand the slash command that will be checked
* @param isInThreadTimeline if its true we are in a thread timeline
+ * @param slashCommand the slash command that will be checked
* @return The command that is not supported
*/
private fun getNotSupportedByThreads(isInThreadTimeline: Boolean, slashCommand: String): Command? {
diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/TimelineFragment.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/TimelineFragment.kt
index 635b00c05d..f0cfff4cf8 100644
--- a/vector/src/main/java/im/vector/app/features/home/room/detail/TimelineFragment.kt
+++ b/vector/src/main/java/im/vector/app/features/home/room/detail/TimelineFragment.kt
@@ -221,6 +221,7 @@ import org.matrix.android.sdk.api.session.events.model.toModel
import org.matrix.android.sdk.api.session.room.model.Membership
import org.matrix.android.sdk.api.session.room.model.RoomSummary
import org.matrix.android.sdk.api.session.room.model.message.MessageAudioContent
+import org.matrix.android.sdk.api.session.room.model.message.MessageBeaconInfoContent
import org.matrix.android.sdk.api.session.room.model.message.MessageContent
import org.matrix.android.sdk.api.session.room.model.message.MessageFormat
import org.matrix.android.sdk.api.session.room.model.message.MessageImageInfoContent
@@ -647,6 +648,13 @@ class TimelineFragment @Inject constructor(
)
}
+ private fun navigateToLocationLiveMap() {
+ navigator.openLocationLiveMap(
+ context = requireContext(),
+ roomId = timelineArgs.roomId
+ )
+ }
+
private fun handleChangeLocationIndicator(event: RoomDetailViewEvents.ChangeLocationIndicator) {
views.locationLiveStatusIndicator.isVisible = event.isVisible
}
@@ -706,31 +714,31 @@ class TimelineFragment @Inject constructor(
}
private fun createEmojiPopup(): EmojiPopup {
- return EmojiPopup
- .Builder
- .fromRootView(views.rootConstraintLayout)
- .setKeyboardAnimationStyle(R.style.emoji_fade_animation_style)
- .setOnEmojiPopupShownListener {
+ return EmojiPopup(
+ rootView = views.rootConstraintLayout,
+ keyboardAnimationStyle = R.style.emoji_fade_animation_style,
+ onEmojiPopupShownListener = {
views.composerLayout.views.composerEmojiButton.apply {
contentDescription = getString(R.string.a11y_close_emoji_picker)
setImageResource(R.drawable.ic_keyboard)
}
- }
- .setOnEmojiPopupDismissListenerLifecycleAware {
+ },
+ onEmojiPopupDismissListener = lifecycleAwareDismissAction {
views.composerLayout.views.composerEmojiButton.apply {
contentDescription = getString(R.string.a11y_open_emoji_picker)
setImageResource(R.drawable.ic_insert_emoji)
}
- }
- .build(views.composerLayout.views.composerEditText)
+ },
+ editText = views.composerLayout.views.composerEditText
+ )
}
/**
* Ensure dismiss actions only trigger when the fragment is in the started state.
* EmojiPopup by default dismisses onViewDetachedFromWindow, this can cause race conditions with onDestroyView.
*/
- private fun EmojiPopup.Builder.setOnEmojiPopupDismissListenerLifecycleAware(action: () -> Unit): EmojiPopup.Builder {
- return setOnEmojiPopupDismissListener {
+ private fun lifecycleAwareDismissAction(action: () -> Unit): () -> Unit {
+ return {
if (lifecycle.currentState.isAtLeast(Lifecycle.State.STARTED)) {
action()
}
@@ -2015,6 +2023,9 @@ class TimelineFragment @Inject constructor(
is MessageLocationContent -> {
handleShowLocationPreview(messageContent, informationData.senderId)
}
+ is MessageBeaconInfoContent -> {
+ navigateToLocationLiveMap()
+ }
else -> {
val handled = onThreadSummaryClicked(informationData.eventId, isRootThreadEvent)
if (!handled) {
diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/LiveLocationShareMessageItemFactory.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/LiveLocationShareMessageItemFactory.kt
index 479a742369..3c7b6c32e1 100644
--- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/LiveLocationShareMessageItemFactory.kt
+++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/LiveLocationShareMessageItemFactory.kt
@@ -33,7 +33,6 @@ import im.vector.app.features.home.room.detail.timeline.item.MessageLiveLocation
import im.vector.app.features.location.INITIAL_MAP_ZOOM_IN_TIMELINE
import im.vector.app.features.location.UrlMapProvider
import im.vector.app.features.location.toLocationData
-import org.matrix.android.sdk.api.extensions.orFalse
import org.matrix.android.sdk.api.session.Session
import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent
import org.threeten.bp.LocalDateTime
@@ -75,7 +74,8 @@ class LiveLocationShareMessageItemFactory @Inject constructor(
val height = dimensionConverter.dpToPx(MessageItemFactory.MESSAGE_LOCATION_ITEM_HEIGHT_IN_DP)
return MessageLiveLocationInactiveItem_()
- .attributes(attributes)
+ // disable the click on this state item
+ .attributes(attributes.copy(itemClickListener = null))
.mapWidth(width)
.mapHeight(height)
.highlighted(highlight)
@@ -90,7 +90,8 @@ class LiveLocationShareMessageItemFactory @Inject constructor(
val height = dimensionConverter.dpToPx(MessageItemFactory.MESSAGE_LOCATION_ITEM_HEIGHT_IN_DP)
return MessageLiveLocationStartItem_()
- .attributes(attributes)
+ // disable the click on this state item
+ .attributes(attributes.copy(itemClickListener = null))
.mapWidth(width)
.mapHeight(height)
.highlighted(highlight)
@@ -127,7 +128,7 @@ class LiveLocationShareMessageItemFactory @Inject constructor(
private fun getViewState(liveLocationShareSummaryData: LiveLocationShareSummaryData?): LiveLocationShareViewState {
return when {
liveLocationShareSummaryData?.isActive == null -> LiveLocationShareViewState.Unkwown
- liveLocationShareSummaryData.isActive.not() || isLiveTimedOut(liveLocationShareSummaryData) -> LiveLocationShareViewState.Inactive
+ liveLocationShareSummaryData.isActive.not() -> LiveLocationShareViewState.Inactive
liveLocationShareSummaryData.isActive && liveLocationShareSummaryData.lastGeoUri.isNullOrEmpty() -> LiveLocationShareViewState.Loading
else ->
LiveLocationShareViewState.Running(
@@ -137,16 +138,6 @@ class LiveLocationShareMessageItemFactory @Inject constructor(
}.also { viewState -> Timber.d("computed viewState: $viewState") }
}
- private fun isLiveTimedOut(liveLocationShareSummaryData: LiveLocationShareSummaryData): Boolean {
- return getEndOfLiveDateTime(liveLocationShareSummaryData)
- ?.let { endOfLive ->
- // this will only cover users with different timezones but not users with manually time set
- val now = LocalDateTime.now()
- now.isAfter(endOfLive)
- }
- .orFalse()
- }
-
private fun getEndOfLiveDateTime(liveLocationShareSummaryData: LiveLocationShareSummaryData): LocalDateTime? {
return liveLocationShareSummaryData.endOfLiveTimestampMillis?.let { DateProvider.toLocalDateTime(timestamp = it) }
}
diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/MergedHeaderItemFactory.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/MergedHeaderItemFactory.kt
index aca2aab174..224c1cdbea 100644
--- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/MergedHeaderItemFactory.kt
+++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/MergedHeaderItemFactory.kt
@@ -55,8 +55,15 @@ class MergedHeaderItemFactory @Inject constructor(private val activeSessionHolde
private val mergeItemCollapseStates = HashMap()
/**
+ * @param event the main timeline event
* @param nextEvent is an older event than event
* @param items all known items, sorted from newer event to oldest event
+ * @param partialState partial state data
+ * @param addDaySeparator true to add a day separator
+ * @param currentPosition the current position
+ * @param eventIdToHighlight if not null the event which has to be highlighted
+ * @param callback callback for user event
+ * @param requestModelBuild lambda to let the built Item request a model build when the collapse state is changed
*/
fun create(event: TimelineEvent,
nextEvent: TimelineEvent?,
diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/TimelineEventVisibilityHelper.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/TimelineEventVisibilityHelper.kt
index f317eb4f9a..8ca999309a 100644
--- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/TimelineEventVisibilityHelper.kt
+++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/helper/TimelineEventVisibilityHelper.kt
@@ -36,6 +36,8 @@ class TimelineEventVisibilityHelper @Inject constructor(private val userPreferen
* @param index the index to start computing (inclusive)
* @param minSize the minimum number of same type events to have sequentially, otherwise will return an empty list
* @param eventIdToHighlight used to compute visibility
+ * @param rootThreadEventId the root thread event id if in a thread timeline
+ * @param isFromThreadTimeline true if the timeline is a thread
*
* @return a list of timeline events which have sequentially the same type following the next direction.
*/
@@ -86,6 +88,8 @@ class TimelineEventVisibilityHelper @Inject constructor(private val userPreferen
* @param index the index to start computing (inclusive)
* @param minSize the minimum number of same type events to have sequentially, otherwise will return an empty list
* @param eventIdToHighlight used to compute visibility
+ * @param rootThreadEventId the root thread eventId
+ * @param isFromThreadTimeline true if the timeline is a thread
*
* @return a list of timeline events which have sequentially the same type following the prev direction.
*/
@@ -107,6 +111,7 @@ class TimelineEventVisibilityHelper @Inject constructor(private val userPreferen
/**
* @param timelineEvent the event to check for visibility
* @param highlightedEventId can be checked to force visibility to true
+ * @param isFromThreadTimeline true if the timeline is a thread
* @param rootThreadEventId if this param is null it means we are in the original timeline
* @return true if the event should be shown in the timeline.
*/
diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/AbsMessageLocationItem.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/AbsMessageLocationItem.kt
index f7146c24e9..e7823845fa 100644
--- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/AbsMessageLocationItem.kt
+++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/AbsMessageLocationItem.kt
@@ -75,6 +75,7 @@ abstract class AbsMessageLocationItem : AbsMe
GlideApp.with(holder.staticMapImageView)
.load(location)
.apply(RequestOptions.centerCropTransform())
+ .placeholder(holder.staticMapImageView.drawable)
.listener(object : RequestListener {
override fun onLoadFailed(e: GlideException?,
model: Any?,
diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MergedRoomCreationItem.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MergedRoomCreationItem.kt
index f3ca525136..f41c17d9e7 100644
--- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MergedRoomCreationItem.kt
+++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MergedRoomCreationItem.kt
@@ -135,7 +135,7 @@ abstract class MergedRoomCreationItem : BasedMergedItem) {
- viewModelScope.launch {
- _viewEvents.post(InviteUsersToRoomViewEvents.Loading)
- selections.asFlow()
- .map { user ->
- when (user) {
- is PendingSelection.UserPendingSelection -> room.membershipService().invite(user.user.userId, null)
- is PendingSelection.ThreePidPendingSelection -> room.membershipService().invite3pid(user.threePid)
- }
+ _viewEvents.post(InviteUsersToRoomViewEvents.Loading)
+ selections.asFlow()
+ .map { user ->
+ when (user) {
+ is PendingSelection.UserPendingSelection -> room.membershipService().invite(user.user.userId, null)
+ is PendingSelection.ThreePidPendingSelection -> room.membershipService().invite3pid(user.threePid)
}
- .catch { cause ->
- _viewEvents.post(InviteUsersToRoomViewEvents.Failure(cause))
+ }.onCompletion { error ->
+ if (error != null) return@onCompletion
+
+ val successMessage = when (selections.size) {
+ 1 -> stringProvider.getString(
+ R.string.invitation_sent_to_one_user,
+ selections.first().getBestName()
+ )
+ 2 -> stringProvider.getString(
+ R.string.invitations_sent_to_two_users,
+ selections.first().getBestName(),
+ selections.last().getBestName()
+ )
+ else -> stringProvider.getQuantityString(
+ R.plurals.invitations_sent_to_one_and_more_users,
+ selections.size - 1,
+ selections.first().getBestName(),
+ selections.size - 1
+ )
}
- .collect {
- val successMessage = when (selections.size) {
- 1 -> stringProvider.getString(
- R.string.invitation_sent_to_one_user,
- selections.first().getBestName()
- )
- 2 -> stringProvider.getString(
- R.string.invitations_sent_to_two_users,
- selections.first().getBestName(),
- selections.last().getBestName()
- )
- else -> stringProvider.getQuantityString(
- R.plurals.invitations_sent_to_one_and_more_users,
- selections.size - 1,
- selections.first().getBestName(),
- selections.size - 1
- )
- }
- _viewEvents.post(InviteUsersToRoomViewEvents.Success(successMessage))
- }
- }
+ _viewEvents.post(InviteUsersToRoomViewEvents.Success(successMessage))
+ }
+ .catch { cause ->
+ _viewEvents.post(InviteUsersToRoomViewEvents.Failure(cause))
+ }.launchIn(viewModelScope)
}
fun getUserIdsOfRoomMembers(): Set {
diff --git a/vector/src/main/java/im/vector/app/features/lifecycle/VectorActivityLifecycleCallbacks.kt b/vector/src/main/java/im/vector/app/features/lifecycle/VectorActivityLifecycleCallbacks.kt
index 386e60359d..e453a347f5 100644
--- a/vector/src/main/java/im/vector/app/features/lifecycle/VectorActivityLifecycleCallbacks.kt
+++ b/vector/src/main/java/im/vector/app/features/lifecycle/VectorActivityLifecycleCallbacks.kt
@@ -136,7 +136,7 @@ class VectorActivityLifecycleCallbacks constructor(private val popupAlertManager
* Detect potential malicious activity.
* Check if the activity running in app task is declared in app manifest.
*
- * @param activity the activity of the task
+ * @param activity the activity of the task
* @return true if the activity is potentially malicious
*/
private fun isPotentialMaliciousActivity(activity: ComponentName): Boolean = activitiesInfo.none { it.name == activity.className }
diff --git a/vector/src/main/java/im/vector/app/features/location/LocationSharingFragment.kt b/vector/src/main/java/im/vector/app/features/location/LocationSharingFragment.kt
index e472c568b6..cc5586e7f5 100644
--- a/vector/src/main/java/im/vector/app/features/location/LocationSharingFragment.kt
+++ b/vector/src/main/java/im/vector/app/features/location/LocationSharingFragment.kt
@@ -37,11 +37,11 @@ import im.vector.app.core.utils.PERMISSIONS_FOR_FOREGROUND_LOCATION_SHARING
import im.vector.app.core.utils.checkPermissions
import im.vector.app.core.utils.registerForPermissionsResult
import im.vector.app.databinding.FragmentLocationSharingBinding
-import im.vector.app.features.VectorFeatures
import im.vector.app.features.home.AvatarRenderer
import im.vector.app.features.home.room.detail.timeline.helper.MatrixItemColorProvider
import im.vector.app.features.location.live.duration.ChooseLiveDurationBottomSheet
import im.vector.app.features.location.option.LocationSharingOption
+import im.vector.app.features.settings.VectorPreferences
import org.matrix.android.sdk.api.util.MatrixItem
import java.lang.ref.WeakReference
import javax.inject.Inject
@@ -53,7 +53,7 @@ class LocationSharingFragment @Inject constructor(
private val urlMapProvider: UrlMapProvider,
private val avatarRenderer: AvatarRenderer,
private val matrixItemColorProvider: MatrixItemColorProvider,
- private val vectorFeatures: VectorFeatures,
+ private val vectorPreferences: VectorPreferences,
) : VectorBaseFragment(),
LocationTargetChangeListener,
VectorBaseBottomSheetDialogFragment.ResultListener {
@@ -255,7 +255,7 @@ class LocationSharingFragment @Inject constructor(
// first, update the options view
val options: Set = when (state.areTargetAndUserLocationEqual) {
true -> {
- if (vectorFeatures.isLiveLocationEnabled()) {
+ if (vectorPreferences.labsEnableLiveLocation()) {
setOf(LocationSharingOption.USER_CURRENT, LocationSharingOption.USER_LIVE)
} else {
setOf(LocationSharingOption.USER_CURRENT)
diff --git a/vector/src/main/java/im/vector/app/features/location/live/map/LocationLiveMapViewActivity.kt b/vector/src/main/java/im/vector/app/features/location/live/map/LocationLiveMapViewActivity.kt
new file mode 100644
index 0000000000..c0f07dba57
--- /dev/null
+++ b/vector/src/main/java/im/vector/app/features/location/live/map/LocationLiveMapViewActivity.kt
@@ -0,0 +1,68 @@
+/*
+ * Copyright (c) 2022 New Vector Ltd
+ *
+ * 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 im.vector.app.features.location.live.map
+
+import android.content.Context
+import android.content.Intent
+import android.os.Parcelable
+import dagger.hilt.android.AndroidEntryPoint
+import im.vector.app.R
+import im.vector.app.core.extensions.addFragment
+import im.vector.app.core.platform.VectorBaseActivity
+import im.vector.app.databinding.ActivityLocationSharingBinding
+import kotlinx.parcelize.Parcelize
+
+@Parcelize
+data class LocationLiveMapViewArgs(
+ val roomId: String
+) : Parcelable
+
+@AndroidEntryPoint
+class LocationLiveMapViewActivity : VectorBaseActivity() {
+
+ override fun getBinding() = ActivityLocationSharingBinding.inflate(layoutInflater)
+
+ override fun initUiAndData() {
+ val mapViewArgs: LocationLiveMapViewArgs? = intent?.extras?.getParcelable(EXTRA_LOCATION_LIVE_MAP_VIEW_ARGS)
+ if (mapViewArgs == null) {
+ finish()
+ return
+ }
+ setupToolbar(views.toolbar)
+ .setTitle(getString(R.string.location_activity_title_preview))
+ .allowBack()
+
+ if (isFirstCreation()) {
+ addFragment(
+ views.fragmentContainer,
+ LocationLiveMapViewFragment::class.java,
+ mapViewArgs
+ )
+ }
+ }
+
+ companion object {
+
+ private const val EXTRA_LOCATION_LIVE_MAP_VIEW_ARGS = "EXTRA_LOCATION_LIVE_MAP_VIEW_ARGS"
+
+ fun getIntent(context: Context, locationLiveMapViewArgs: LocationLiveMapViewArgs): Intent {
+ return Intent(context, LocationLiveMapViewActivity::class.java).apply {
+ putExtra(EXTRA_LOCATION_LIVE_MAP_VIEW_ARGS, locationLiveMapViewArgs)
+ }
+ }
+ }
+}
diff --git a/vector/src/main/java/im/vector/app/features/location/live/map/LocationLiveMapViewFragment.kt b/vector/src/main/java/im/vector/app/features/location/live/map/LocationLiveMapViewFragment.kt
new file mode 100644
index 0000000000..32b87727d8
--- /dev/null
+++ b/vector/src/main/java/im/vector/app/features/location/live/map/LocationLiveMapViewFragment.kt
@@ -0,0 +1,76 @@
+/*
+ * Copyright (c) 2022 New Vector Ltd
+ *
+ * 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 im.vector.app.features.location.live.map
+
+import android.os.Bundle
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import androidx.lifecycle.lifecycleScope
+import com.airbnb.mvrx.args
+import com.mapbox.mapboxsdk.maps.MapboxMapOptions
+import com.mapbox.mapboxsdk.maps.SupportMapFragment
+import dagger.hilt.android.AndroidEntryPoint
+import im.vector.app.R
+import im.vector.app.core.extensions.addChildFragment
+import im.vector.app.core.platform.VectorBaseFragment
+import im.vector.app.databinding.FragmentSimpleContainerBinding
+import im.vector.app.features.location.UrlMapProvider
+import javax.inject.Inject
+
+/**
+ * Screen showing a map with all the current users sharing their live location in room.
+ */
+@AndroidEntryPoint
+class LocationLiveMapViewFragment : VectorBaseFragment() {
+
+ @Inject
+ lateinit var urlMapProvider: UrlMapProvider
+
+ private val args: LocationLiveMapViewArgs by args()
+
+ override fun getBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentSimpleContainerBinding {
+ return FragmentSimpleContainerBinding.inflate(layoutInflater, container, false)
+ }
+
+ override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+ super.onViewCreated(view, savedInstanceState)
+ setupMap()
+ }
+
+ private fun setupMap() {
+ val mapFragment = getOrCreateSupportMapFragment()
+
+ mapFragment.getMapAsync { mapBoxMap ->
+ lifecycleScope.launchWhenCreated {
+ mapBoxMap.setStyle(urlMapProvider.getMapUrl())
+ }
+ }
+ }
+
+ private fun getOrCreateSupportMapFragment() =
+ childFragmentManager.findFragmentByTag(MAP_FRAGMENT_TAG) as? SupportMapFragment
+ ?: run {
+ val options = MapboxMapOptions.createFromAttributes(requireContext(), null)
+ SupportMapFragment.newInstance(options)
+ .also { addChildFragment(R.id.fragmentContainer, it, tag = MAP_FRAGMENT_TAG) }
+ }
+
+ companion object {
+ private const val MAP_FRAGMENT_TAG = "im.vector.app.features.location.live.map"
+ }
+}
diff --git a/vector/src/main/java/im/vector/app/features/login/SocialLoginButtonsView.kt b/vector/src/main/java/im/vector/app/features/login/SocialLoginButtonsView.kt
index 68fc2d1c59..49fa815a56 100644
--- a/vector/src/main/java/im/vector/app/features/login/SocialLoginButtonsView.kt
+++ b/vector/src/main/java/im/vector/app/features/login/SocialLoginButtonsView.kt
@@ -159,3 +159,9 @@ class SocialLoginButtonsView @JvmOverloads constructor(context: Context, attrs:
return TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dp.toFloat(), resources.displayMetrics).toInt()
}
}
+
+fun SocialLoginButtonsView.render(ssoProviders: List?, mode: SocialLoginButtonsView.Mode, listener: (String?) -> Unit) {
+ this.mode = mode
+ this.ssoIdentityProviders = ssoProviders?.sorted()
+ this.listener = SocialLoginButtonsView.InteractionListener { listener(it) }
+}
diff --git a/vector/src/main/java/im/vector/app/features/navigation/DefaultNavigator.kt b/vector/src/main/java/im/vector/app/features/navigation/DefaultNavigator.kt
index 0f921ab80a..a051266688 100644
--- a/vector/src/main/java/im/vector/app/features/navigation/DefaultNavigator.kt
+++ b/vector/src/main/java/im/vector/app/features/navigation/DefaultNavigator.kt
@@ -68,6 +68,8 @@ import im.vector.app.features.location.LocationData
import im.vector.app.features.location.LocationSharingActivity
import im.vector.app.features.location.LocationSharingArgs
import im.vector.app.features.location.LocationSharingMode
+import im.vector.app.features.location.live.map.LocationLiveMapViewActivity
+import im.vector.app.features.location.live.map.LocationLiveMapViewArgs
import im.vector.app.features.login.LoginActivity
import im.vector.app.features.login.LoginConfig
import im.vector.app.features.matrixto.MatrixToBottomSheet
@@ -592,6 +594,14 @@ class DefaultNavigator @Inject constructor(
context.startActivity(intent)
}
+ override fun openLocationLiveMap(context: Context, roomId: String) {
+ val intent = LocationLiveMapViewActivity.getIntent(
+ context = context,
+ locationLiveMapViewArgs = LocationLiveMapViewArgs(roomId = roomId)
+ )
+ context.startActivity(intent)
+ }
+
private fun startActivity(context: Context, intent: Intent, buildTask: Boolean) {
if (buildTask) {
val stackBuilder = TaskStackBuilder.create(context)
diff --git a/vector/src/main/java/im/vector/app/features/navigation/Navigator.kt b/vector/src/main/java/im/vector/app/features/navigation/Navigator.kt
index 075b41daf3..d4ef2b8099 100644
--- a/vector/src/main/java/im/vector/app/features/navigation/Navigator.kt
+++ b/vector/src/main/java/im/vector/app/features/navigation/Navigator.kt
@@ -172,6 +172,8 @@ interface Navigator {
initialLocationData: LocationData?,
locationOwnerId: String?)
+ fun openLocationLiveMap(context: Context, roomId: String)
+
fun openThread(context: Context, threadTimelineArgs: ThreadTimelineArgs, eventIdToNavigate: String? = null)
fun openThreadList(context: Context, threadTimelineArgs: ThreadTimelineArgs)
diff --git a/vector/src/main/java/im/vector/app/features/notifications/NotificationUtils.kt b/vector/src/main/java/im/vector/app/features/notifications/NotificationUtils.kt
index d03fcadcfa..abfca1a64c 100755
--- a/vector/src/main/java/im/vector/app/features/notifications/NotificationUtils.kt
+++ b/vector/src/main/java/im/vector/app/features/notifications/NotificationUtils.kt
@@ -233,6 +233,7 @@ class NotificationUtils @Inject constructor(
* Build a polling thread listener notification.
*
* @param subTitleResId subtitle string resource Id of the notification
+ * @param withProgress true to show indeterminate progress on the notification
* @return the polling thread listener notification
*/
@SuppressLint("NewApi")
@@ -298,10 +299,8 @@ class NotificationUtils @Inject constructor(
* Build an incoming call notification.
* This notification starts the VectorHomeActivity which is in charge of centralizing the incoming call flow.
*
- * @param isVideo true if this is a video call, false for voice call
- * @param roomName the room name in which the call is pending.
- * @param matrixId the matrix id
- * @param callId the call id.
+ * @param call information about the call
+ * @param title title of the notification
* @param fromBg true if the app is in background when posting the notification
* @return the call notification.
*/
@@ -430,11 +429,8 @@ class NotificationUtils @Inject constructor(
/**
* Build a pending call notification.
*
- * @param isVideo true if this is a video call, false for voice call
- * @param roomName the room name in which the call is pending.
- * @param roomId the room Id
- * @param matrixId the matrix id
- * @param callId the call id.
+ * @param call information about the call
+ * @param title title of the notification
* @return the call notification.
*/
@SuppressLint("NewApi")
diff --git a/vector/src/main/java/im/vector/app/features/onboarding/DirectLoginUseCase.kt b/vector/src/main/java/im/vector/app/features/onboarding/DirectLoginUseCase.kt
index 3014b199b4..925c838d80 100644
--- a/vector/src/main/java/im/vector/app/features/onboarding/DirectLoginUseCase.kt
+++ b/vector/src/main/java/im/vector/app/features/onboarding/DirectLoginUseCase.kt
@@ -19,7 +19,7 @@ package im.vector.app.features.onboarding
import im.vector.app.R
import im.vector.app.core.extensions.andThen
import im.vector.app.core.resources.StringProvider
-import im.vector.app.features.onboarding.OnboardingAction.LoginOrRegister
+import im.vector.app.features.onboarding.OnboardingAction.AuthenticateAction.LoginDirect
import org.matrix.android.sdk.api.MatrixPatterns.getServerName
import org.matrix.android.sdk.api.auth.AuthenticationService
import org.matrix.android.sdk.api.auth.data.HomeServerConnectionConfig
@@ -33,8 +33,8 @@ class DirectLoginUseCase @Inject constructor(
private val uriFactory: UriFactory
) {
- suspend fun execute(action: LoginOrRegister, homeServerConnectionConfig: HomeServerConnectionConfig?): Result {
- return fetchWellKnown(action.username, homeServerConnectionConfig)
+ suspend fun execute(action: LoginDirect, homeServerConnectionConfig: HomeServerConnectionConfig?): Result {
+ return fetchWellKnown(action.matrixId, homeServerConnectionConfig)
.andThen { wellKnown -> createSessionFor(wellKnown, action, homeServerConnectionConfig) }
}
@@ -42,13 +42,13 @@ class DirectLoginUseCase @Inject constructor(
authenticationService.getWellKnownData(matrixId, config)
}
- private suspend fun createSessionFor(data: WellknownResult, action: LoginOrRegister, config: HomeServerConnectionConfig?) = when (data) {
- is WellknownResult.Prompt -> loginDirect(action, data, config)
+ private suspend fun createSessionFor(data: WellknownResult, action: LoginDirect, config: HomeServerConnectionConfig?) = when (data) {
+ is WellknownResult.Prompt -> loginDirect(action, data, config)
is WellknownResult.FailPrompt -> handleFailPrompt(data, action, config)
- else -> onWellKnownError()
+ else -> onWellKnownError()
}
- private suspend fun handleFailPrompt(data: WellknownResult.FailPrompt, action: LoginOrRegister, config: HomeServerConnectionConfig?): Result {
+ private suspend fun handleFailPrompt(data: WellknownResult.FailPrompt, action: LoginDirect, config: HomeServerConnectionConfig?): Result {
// Relax on IS discovery if homeserver is valid
val isMissingInformationToLogin = data.homeServerUrl == null || data.wellKnown == null
return when {
@@ -57,12 +57,12 @@ class DirectLoginUseCase @Inject constructor(
}
}
- private suspend fun loginDirect(action: LoginOrRegister, wellKnownPrompt: WellknownResult.Prompt, config: HomeServerConnectionConfig?): Result {
+ private suspend fun loginDirect(action: LoginDirect, wellKnownPrompt: WellknownResult.Prompt, config: HomeServerConnectionConfig?): Result {
val alteredHomeServerConnectionConfig = config?.updateWith(wellKnownPrompt) ?: fallbackConfig(action, wellKnownPrompt)
return runCatching {
authenticationService.directAuthentication(
alteredHomeServerConnectionConfig,
- action.username,
+ action.matrixId,
action.password,
action.initialDeviceName
)
@@ -74,8 +74,8 @@ class DirectLoginUseCase @Inject constructor(
identityServerUri = wellKnownPrompt.identityServerUrl?.let { uriFactory.parse(it) }
)
- private fun fallbackConfig(action: LoginOrRegister, wellKnownPrompt: WellknownResult.Prompt) = HomeServerConnectionConfig(
- homeServerUri = uriFactory.parse("https://${action.username.getServerName()}"),
+ private fun fallbackConfig(action: LoginDirect, wellKnownPrompt: WellknownResult.Prompt) = HomeServerConnectionConfig(
+ homeServerUri = uriFactory.parse("https://${action.matrixId.getServerName()}"),
homeServerUriBase = uriFactory.parse(wellKnownPrompt.homeServerUrl),
identityServerUri = wellKnownPrompt.identityServerUrl?.let { uriFactory.parse(it) }
)
diff --git a/vector/src/main/java/im/vector/app/features/onboarding/OnboardingAction.kt b/vector/src/main/java/im/vector/app/features/onboarding/OnboardingAction.kt
index 9f7dce56ea..bef624ddc4 100644
--- a/vector/src/main/java/im/vector/app/features/onboarding/OnboardingAction.kt
+++ b/vector/src/main/java/im/vector/app/features/onboarding/OnboardingAction.kt
@@ -46,9 +46,12 @@ sealed interface OnboardingAction : VectorViewModelAction {
data class ResetPassword(val email: String, val newPassword: String) : OnboardingAction
object ResetPasswordMailConfirmed : OnboardingAction
- // Login or Register, depending on the signMode
- data class LoginOrRegister(val username: String, val password: String, val initialDeviceName: String) : OnboardingAction
- data class Register(val username: String, val password: String, val initialDeviceName: String) : OnboardingAction
+ sealed interface AuthenticateAction : OnboardingAction {
+ data class Register(val username: String, val password: String, val initialDeviceName: String) : AuthenticateAction
+ data class Login(val username: String, val password: String, val initialDeviceName: String) : AuthenticateAction
+ data class LoginDirect(val matrixId: String, val password: String, val initialDeviceName: String) : AuthenticateAction
+ }
+
object StopEmailValidationCheck : OnboardingAction
data class PostRegisterAction(val registerAction: RegisterAction) : OnboardingAction
diff --git a/vector/src/main/java/im/vector/app/features/onboarding/OnboardingViewEvents.kt b/vector/src/main/java/im/vector/app/features/onboarding/OnboardingViewEvents.kt
index 6ffece4ab6..5dbcd162f3 100644
--- a/vector/src/main/java/im/vector/app/features/onboarding/OnboardingViewEvents.kt
+++ b/vector/src/main/java/im/vector/app/features/onboarding/OnboardingViewEvents.kt
@@ -37,6 +37,7 @@ sealed class OnboardingViewEvents : VectorViewEvents {
object OpenUseCaseSelection : OnboardingViewEvents()
object OpenServerSelection : OnboardingViewEvents()
object OpenCombinedRegister : OnboardingViewEvents()
+ object OpenCombinedLogin : OnboardingViewEvents()
object EditServerSelection : OnboardingViewEvents()
data class OnServerSelectionDone(val serverType: ServerType) : OnboardingViewEvents()
object OnLoginFlowRetrieved : OnboardingViewEvents()
diff --git a/vector/src/main/java/im/vector/app/features/onboarding/OnboardingViewModel.kt b/vector/src/main/java/im/vector/app/features/onboarding/OnboardingViewModel.kt
index 03526b47a5..0bd61758bc 100644
--- a/vector/src/main/java/im/vector/app/features/onboarding/OnboardingViewModel.kt
+++ b/vector/src/main/java/im/vector/app/features/onboarding/OnboardingViewModel.kt
@@ -25,6 +25,7 @@ import im.vector.app.R
import im.vector.app.core.di.ActiveSessionHolder
import im.vector.app.core.di.MavericksAssistedViewModelFactory
import im.vector.app.core.di.hiltMavericksViewModelFactory
+import im.vector.app.core.extensions.cancelCurrentOnSet
import im.vector.app.core.extensions.configureAndStart
import im.vector.app.core.extensions.vectorStore
import im.vector.app.core.platform.VectorViewModel
@@ -41,7 +42,9 @@ import im.vector.app.features.login.LoginMode
import im.vector.app.features.login.ReAuthHelper
import im.vector.app.features.login.ServerType
import im.vector.app.features.login.SignMode
+import im.vector.app.features.onboarding.OnboardingAction.AuthenticateAction
import im.vector.app.features.onboarding.StartAuthenticationFlowUseCase.StartAuthenticationResult
+import im.vector.app.features.onboarding.ftueauth.MatrixOrgRegistrationStagesComparator
import kotlinx.coroutines.Job
import kotlinx.coroutines.flow.firstOrNull
import kotlinx.coroutines.launch
@@ -50,7 +53,6 @@ import org.matrix.android.sdk.api.auth.HomeServerHistoryService
import org.matrix.android.sdk.api.auth.data.HomeServerConnectionConfig
import org.matrix.android.sdk.api.auth.login.LoginWizard
import org.matrix.android.sdk.api.auth.registration.FlowResult
-import org.matrix.android.sdk.api.auth.registration.RegistrationResult
import org.matrix.android.sdk.api.auth.registration.RegistrationWizard
import org.matrix.android.sdk.api.auth.registration.Stage
import org.matrix.android.sdk.api.session.Session
@@ -125,12 +127,8 @@ class OnboardingViewModel @AssistedInject constructor(
private var loginConfig: LoginConfig? = null
- private var currentJob: Job? = null
- set(value) {
- // Cancel any previous Job
- field?.cancel()
- field = value
- }
+ private var emailVerificationPollingJob: Job? by cancelCurrentOnSet()
+ private var currentJob: Job? by cancelCurrentOnSet()
override fun handle(action: OnboardingAction) {
when (action) {
@@ -142,8 +140,7 @@ class OnboardingViewModel @AssistedInject constructor(
is OnboardingAction.UpdateSignMode -> handleUpdateSignMode(action)
is OnboardingAction.InitWith -> handleInitWith(action)
is OnboardingAction.HomeServerChange -> withAction(action) { handleHomeserverChange(action) }
- is OnboardingAction.LoginOrRegister -> handleLoginOrRegister(action).also { lastAction = action }
- is OnboardingAction.Register -> handleRegisterWith(action).also { lastAction = action }
+ is AuthenticateAction -> withAction(action) { handleAuthenticateAction(action) }
is OnboardingAction.LoginWithToken -> handleLoginWithToken(action)
is OnboardingAction.WebLoginSuccess -> handleWebLoginSuccess(action)
is OnboardingAction.ResetPassword -> handleResetPassword(action)
@@ -168,6 +165,14 @@ class OnboardingViewModel @AssistedInject constructor(
block(action)
}
+ private fun handleAuthenticateAction(action: AuthenticateAction) {
+ when (action) {
+ is AuthenticateAction.Register -> handleRegisterWith(action)
+ is AuthenticateAction.Login -> handleLogin(action)
+ is AuthenticateAction.LoginDirect -> handleDirectLogin(action, homeServerConnectionConfig = null)
+ }
+ }
+
private fun handleSplashAction(resetConfig: Boolean, onboardingFlow: OnboardingFlow) {
if (resetConfig) {
loginConfig = null
@@ -191,16 +196,21 @@ class OnboardingViewModel @AssistedInject constructor(
}
private fun continueToPageAfterSplash(onboardingFlow: OnboardingFlow) {
- val nextOnboardingStep = when (onboardingFlow) {
- OnboardingFlow.SignUp -> if (vectorFeatures.isOnboardingUseCaseEnabled()) {
- OnboardingViewEvents.OpenUseCaseSelection
- } else {
- OnboardingViewEvents.OpenServerSelection
+ when (onboardingFlow) {
+ OnboardingFlow.SignUp -> {
+ _viewEvents.post(
+ if (vectorFeatures.isOnboardingUseCaseEnabled()) {
+ OnboardingViewEvents.OpenUseCaseSelection
+ } else {
+ OnboardingViewEvents.OpenServerSelection
+ }
+ )
}
- OnboardingFlow.SignIn,
- OnboardingFlow.SignInSignUp -> OnboardingViewEvents.OpenServerSelection
+ OnboardingFlow.SignIn -> if (vectorFeatures.isOnboardingCombinedLoginEnabled()) {
+ handle(OnboardingAction.HomeServerChange.SelectHomeServer(defaultHomeserverUrl))
+ } else _viewEvents.post(OnboardingViewEvents.OpenServerSelection)
+ OnboardingFlow.SignInSignUp -> _viewEvents.post(OnboardingViewEvents.OpenServerSelection)
}
- _viewEvents.post(nextOnboardingStep)
}
private fun handleUserAcceptCertificate(action: OnboardingAction.UserAcceptCertificate) {
@@ -212,7 +222,7 @@ class OnboardingViewModel @AssistedInject constructor(
?.let { it.copy(allowedFingerprints = it.allowedFingerprints + action.fingerprint) }
?.let { startAuthenticationFlow(finalLastAction, it) }
}
- is OnboardingAction.LoginOrRegister ->
+ is AuthenticateAction.LoginDirect ->
handleDirectLogin(
finalLastAction,
HomeServerConnectionConfig.Builder()
@@ -257,13 +267,19 @@ class OnboardingViewModel @AssistedInject constructor(
}
private fun handleRegisterAction(action: RegisterAction, onNextRegistrationStepAction: (FlowResult) -> Unit) {
- currentJob = viewModelScope.launch {
+ val job = viewModelScope.launch {
if (action.hasLoadingState()) {
setState { copy(isLoading = true) }
}
internalRegisterAction(action, onNextRegistrationStepAction)
setState { copy(isLoading = false) }
}
+
+ // Allow email verification polling to coexist with other jobs
+ when (action) {
+ is RegisterAction.CheckIfEmailHasBeenValidated -> emailVerificationPollingJob = job
+ else -> currentJob = job
+ }
}
private suspend fun internalRegisterAction(action: RegisterAction, onNextRegistrationStepAction: (FlowResult) -> Unit) {
@@ -275,8 +291,10 @@ class OnboardingViewModel @AssistedInject constructor(
// do nothing
}
else -> when (it) {
- is RegistrationResult.Success -> onSessionCreated(it.session, isAccountCreated = true)
- is RegistrationResult.FlowResponse -> onFlowResponse(it.flowResult, onNextRegistrationStepAction)
+ is RegistrationResult.Complete -> onSessionCreated(it.session, isAccountCreated = true)
+ is RegistrationResult.NextStep -> onFlowResponse(it.flowResult, onNextRegistrationStepAction)
+ is RegistrationResult.SendEmailSuccess -> _viewEvents.post(OnboardingViewEvents.OnSendEmailSuccess(it.email))
+ is RegistrationResult.Error -> _viewEvents.post(OnboardingViewEvents.Failure(it.cause))
}
}
},
@@ -289,10 +307,20 @@ class OnboardingViewModel @AssistedInject constructor(
}
private fun emitFlowResultViewEvent(flowResult: FlowResult) {
- _viewEvents.post(OnboardingViewEvents.RegistrationFlowResult(flowResult, isRegistrationStarted))
+ withState { state ->
+ val orderedResult = when {
+ state.hasSelectedMatrixOrg() && vectorFeatures.isOnboardingCombinedRegisterEnabled() -> flowResult.copy(
+ missingStages = flowResult.missingStages.sortedWith(MatrixOrgRegistrationStagesComparator())
+ )
+ else -> flowResult
+ }
+ _viewEvents.post(OnboardingViewEvents.RegistrationFlowResult(orderedResult, isRegistrationStarted))
+ }
}
- private fun handleRegisterWith(action: OnboardingAction.Register) {
+ private fun OnboardingViewState.hasSelectedMatrixOrg() = selectedHomeserver.userFacingUrl == matrixOrgUrl
+
+ private fun handleRegisterWith(action: AuthenticateAction.Register) {
reAuthHelper.data = action.password
handleRegisterAction(
RegisterAction.CreateAccount(
@@ -307,6 +335,7 @@ class OnboardingViewModel @AssistedInject constructor(
private fun handleResetAction(action: OnboardingAction.ResetAction) {
// Cancel any request
currentJob = null
+ emailVerificationPollingJob = null
when (action) {
OnboardingAction.ResetHomeServerType -> {
@@ -466,16 +495,7 @@ class OnboardingViewModel @AssistedInject constructor(
}
}
- private fun handleLoginOrRegister(action: OnboardingAction.LoginOrRegister) = withState { state ->
- when (state.signMode) {
- SignMode.Unknown -> error("Developer error, invalid sign mode")
- SignMode.SignIn -> handleLogin(action)
- SignMode.SignUp -> handleRegisterWith(OnboardingAction.Register(action.username, action.password, action.initialDeviceName))
- SignMode.SignInWithMatrixId -> handleDirectLogin(action, null)
- }
- }
-
- private fun handleDirectLogin(action: OnboardingAction.LoginOrRegister, homeServerConnectionConfig: HomeServerConnectionConfig?) {
+ private fun handleDirectLogin(action: AuthenticateAction.LoginDirect, homeServerConnectionConfig: HomeServerConnectionConfig?) {
setState { copy(isLoading = true) }
currentJob = viewModelScope.launch {
directLoginUseCase.execute(action, homeServerConnectionConfig).fold(
@@ -488,7 +508,7 @@ class OnboardingViewModel @AssistedInject constructor(
}
}
- private fun handleLogin(action: OnboardingAction.LoginOrRegister) {
+ private fun handleLogin(action: AuthenticateAction.Login) {
val safeLoginWizard = loginWizard
if (safeLoginWizard == null) {
@@ -632,7 +652,11 @@ class OnboardingViewModel @AssistedInject constructor(
when (trigger) {
is OnboardingAction.HomeServerChange.EditHomeServer -> {
when (awaitState().onboardingFlow) {
- OnboardingFlow.SignUp -> internalRegisterAction(RegisterAction.StartRegistration) { _ ->
+ OnboardingFlow.SignUp -> internalRegisterAction(RegisterAction.StartRegistration) {
+ updateServerSelection(config, serverTypeOverride, authResult)
+ _viewEvents.post(OnboardingViewEvents.OnHomeserverEdited)
+ }
+ OnboardingFlow.SignIn -> {
updateServerSelection(config, serverTypeOverride, authResult)
_viewEvents.post(OnboardingViewEvents.OnHomeserverEdited)
}
@@ -645,7 +669,10 @@ class OnboardingViewModel @AssistedInject constructor(
when (awaitState().onboardingFlow) {
OnboardingFlow.SignIn -> {
updateSignMode(SignMode.SignIn)
- _viewEvents.post(OnboardingViewEvents.OnSignModeSelected(SignMode.SignIn))
+ when (vectorFeatures.isOnboardingCombinedLoginEnabled()) {
+ true -> _viewEvents.post(OnboardingViewEvents.OpenCombinedLogin)
+ false -> _viewEvents.post(OnboardingViewEvents.OnSignModeSelected(SignMode.SignIn))
+ }
}
OnboardingFlow.SignUp -> {
updateSignMode(SignMode.SignUp)
@@ -790,7 +817,7 @@ class OnboardingViewModel @AssistedInject constructor(
}
private fun cancelWaitForEmailValidation() {
- currentJob = null
+ emailVerificationPollingJob = null
}
}
diff --git a/vector/src/main/java/im/vector/app/features/onboarding/RegistrationActionHandler.kt b/vector/src/main/java/im/vector/app/features/onboarding/RegistrationActionHandler.kt
index b4998d2ba0..7bffe50754 100644
--- a/vector/src/main/java/im/vector/app/features/onboarding/RegistrationActionHandler.kt
+++ b/vector/src/main/java/im/vector/app/features/onboarding/RegistrationActionHandler.kt
@@ -16,26 +16,80 @@
package im.vector.app.features.onboarding
+import org.matrix.android.sdk.api.auth.registration.FlowResult
import org.matrix.android.sdk.api.auth.registration.RegisterThreePid
-import org.matrix.android.sdk.api.auth.registration.RegistrationResult
+import org.matrix.android.sdk.api.auth.registration.RegistrationResult.FlowResponse
+import org.matrix.android.sdk.api.auth.registration.RegistrationResult.Success
import org.matrix.android.sdk.api.auth.registration.RegistrationWizard
+import org.matrix.android.sdk.api.failure.is401
+import org.matrix.android.sdk.api.session.Session
import javax.inject.Inject
+import org.matrix.android.sdk.api.auth.registration.RegistrationResult as MatrixRegistrationResult
class RegistrationActionHandler @Inject constructor() {
suspend fun handleRegisterAction(registrationWizard: RegistrationWizard, action: RegisterAction): RegistrationResult {
return when (action) {
- RegisterAction.StartRegistration -> registrationWizard.getRegistrationFlow()
- is RegisterAction.CaptchaDone -> registrationWizard.performReCaptcha(action.captchaResponse)
- is RegisterAction.AcceptTerms -> registrationWizard.acceptTerms()
- is RegisterAction.RegisterDummy -> registrationWizard.dummy()
- is RegisterAction.AddThreePid -> registrationWizard.addThreePid(action.threePid)
- is RegisterAction.SendAgainThreePid -> registrationWizard.sendAgainThreePid()
- is RegisterAction.ValidateThreePid -> registrationWizard.handleValidateThreePid(action.code)
- is RegisterAction.CheckIfEmailHasBeenValidated -> registrationWizard.checkIfEmailHasBeenValidated(action.delayMillis)
- is RegisterAction.CreateAccount -> registrationWizard.createAccount(action.username, action.password, action.initialDeviceName)
+ RegisterAction.StartRegistration -> resultOf { registrationWizard.getRegistrationFlow() }
+ is RegisterAction.CaptchaDone -> resultOf { registrationWizard.performReCaptcha(action.captchaResponse) }
+ is RegisterAction.AcceptTerms -> resultOf { registrationWizard.acceptTerms() }
+ is RegisterAction.RegisterDummy -> resultOf { registrationWizard.dummy() }
+ is RegisterAction.AddThreePid -> handleAddThreePid(registrationWizard, action)
+ is RegisterAction.SendAgainThreePid -> resultOf { registrationWizard.sendAgainThreePid() }
+ is RegisterAction.ValidateThreePid -> resultOf { registrationWizard.handleValidateThreePid(action.code) }
+ is RegisterAction.CheckIfEmailHasBeenValidated -> handleCheckIfEmailIsValidated(registrationWizard, action.delayMillis)
+ is RegisterAction.CreateAccount -> resultOf {
+ registrationWizard.createAccount(
+ action.username,
+ action.password,
+ action.initialDeviceName
+ )
+ }
}
}
+
+ private suspend fun handleAddThreePid(wizard: RegistrationWizard, action: RegisterAction.AddThreePid): RegistrationResult {
+ return runCatching { wizard.addThreePid(action.threePid) }.fold(
+ onSuccess = { it.toRegistrationResult() },
+ onFailure = {
+ when {
+ action.threePid is RegisterThreePid.Email && it.is401() -> RegistrationResult.SendEmailSuccess(action.threePid.email)
+ else -> RegistrationResult.Error(it)
+ }
+ }
+ )
+ }
+
+ private tailrec suspend fun handleCheckIfEmailIsValidated(registrationWizard: RegistrationWizard, delayMillis: Long): RegistrationResult {
+ return runCatching { registrationWizard.checkIfEmailHasBeenValidated(delayMillis) }.fold(
+ onSuccess = { it.toRegistrationResult() },
+ onFailure = {
+ when {
+ it.is401() -> null // recursively continue to check with a delay
+ else -> RegistrationResult.Error(it)
+ }
+ }
+ ) ?: handleCheckIfEmailIsValidated(registrationWizard, 10_000)
+ }
+}
+
+private inline fun resultOf(block: () -> MatrixRegistrationResult): RegistrationResult {
+ return runCatching { block() }.fold(
+ onSuccess = { it.toRegistrationResult() },
+ onFailure = { RegistrationResult.Error(it) }
+ )
+}
+
+private fun MatrixRegistrationResult.toRegistrationResult() = when (this) {
+ is FlowResponse -> RegistrationResult.NextStep(flowResult)
+ is Success -> RegistrationResult.Complete(session)
+}
+
+sealed interface RegistrationResult {
+ data class Error(val cause: Throwable) : RegistrationResult
+ data class Complete(val session: Session) : RegistrationResult
+ data class NextStep(val flowResult: FlowResult) : RegistrationResult
+ data class SendEmailSuccess(val email: String) : RegistrationResult
}
sealed interface RegisterAction {
@@ -56,7 +110,6 @@ sealed interface RegisterAction {
}
fun RegisterAction.ignoresResult() = when (this) {
- is RegisterAction.AddThreePid -> true
is RegisterAction.SendAgainThreePid -> true
else -> false
}
diff --git a/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthChooseDisplayNameFragment.kt b/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthChooseDisplayNameFragment.kt
index 1ce0c544e5..f4cf1e9bea 100644
--- a/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthChooseDisplayNameFragment.kt
+++ b/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthChooseDisplayNameFragment.kt
@@ -22,7 +22,7 @@ import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.view.inputmethod.EditorInfo
-import com.google.android.material.textfield.TextInputLayout
+import im.vector.app.core.extensions.hasContent
import im.vector.app.core.platform.SimpleTextWatcher
import im.vector.app.databinding.FragmentFtueDisplayNameBinding
import im.vector.app.features.onboarding.OnboardingAction
@@ -69,7 +69,7 @@ class FtueAuthChooseDisplayNameFragment @Inject constructor() : AbstractFtueAuth
override fun updateWithState(state: OnboardingViewState) {
views.displayNameInput.editText?.setText(state.personalizationState.displayName)
- views.displayNameSubmit.isEnabled = views.displayNameInput.hasContentEmpty()
+ views.displayNameSubmit.isEnabled = views.displayNameInput.hasContent()
}
override fun resetViewModel() {
@@ -81,5 +81,3 @@ class FtueAuthChooseDisplayNameFragment @Inject constructor() : AbstractFtueAuth
return true
}
}
-
-private fun TextInputLayout.hasContentEmpty() = !editText?.text.isNullOrEmpty()
diff --git a/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthCombinedLoginFragment.kt b/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthCombinedLoginFragment.kt
new file mode 100644
index 0000000000..7324c4fbb1
--- /dev/null
+++ b/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthCombinedLoginFragment.kt
@@ -0,0 +1,161 @@
+/*
+ * Copyright 2019 New Vector Ltd
+ *
+ * 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 im.vector.app.features.onboarding.ftueauth
+
+import android.os.Build
+import android.os.Bundle
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import androidx.autofill.HintConstants
+import androidx.core.view.isVisible
+import androidx.lifecycle.lifecycleScope
+import im.vector.app.R
+import im.vector.app.core.extensions.content
+import im.vector.app.core.extensions.editText
+import im.vector.app.core.extensions.hideKeyboard
+import im.vector.app.core.extensions.hidePassword
+import im.vector.app.core.extensions.realignPercentagesToParent
+import im.vector.app.core.extensions.setOnImeDoneListener
+import im.vector.app.core.extensions.toReducedUrl
+import im.vector.app.databinding.FragmentFtueCombinedLoginBinding
+import im.vector.app.features.login.LoginMode
+import im.vector.app.features.login.SSORedirectRouterActivity
+import im.vector.app.features.login.SocialLoginButtonsView
+import im.vector.app.features.login.render
+import im.vector.app.features.onboarding.OnboardingAction
+import im.vector.app.features.onboarding.OnboardingViewEvents
+import im.vector.app.features.onboarding.OnboardingViewState
+import kotlinx.coroutines.flow.launchIn
+import org.matrix.android.sdk.api.auth.data.SsoIdentityProvider
+import javax.inject.Inject
+
+class FtueAuthCombinedLoginFragment @Inject constructor(
+ private val loginFieldsValidation: LoginFieldsValidation,
+ private val loginErrorParser: LoginErrorParser
+) : AbstractSSOFtueAuthFragment() {
+
+ override fun getBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentFtueCombinedLoginBinding {
+ return FragmentFtueCombinedLoginBinding.inflate(inflater, container, false)
+ }
+
+ override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+ super.onViewCreated(view, savedInstanceState)
+ setupSubmitButton()
+ views.loginRoot.realignPercentagesToParent()
+ views.editServerButton.debouncedClicks { viewModel.handle(OnboardingAction.PostViewEvent(OnboardingViewEvents.EditServerSelection)) }
+ views.loginPasswordInput.setOnImeDoneListener { submit() }
+ }
+
+ private fun setupSubmitButton() {
+ views.loginSubmit.setOnClickListener { submit() }
+ observeContentChangesAndResetErrors(views.loginInput, views.loginPasswordInput, views.loginSubmit)
+ .launchIn(viewLifecycleOwner.lifecycleScope)
+ }
+
+ private fun submit() {
+ cleanupUi()
+ loginFieldsValidation.validate(views.loginInput.content(), views.loginPasswordInput.content())
+ .onUsernameOrIdError { views.loginInput.error = it }
+ .onPasswordError { views.loginPasswordInput.error = it }
+ .onValid { usernameOrId, password ->
+ val initialDeviceName = getString(R.string.login_default_session_public_name)
+ viewModel.handle(OnboardingAction.AuthenticateAction.Login(usernameOrId, password, initialDeviceName))
+ }
+ }
+
+ private fun cleanupUi() {
+ views.loginSubmit.hideKeyboard()
+ views.loginInput.error = null
+ views.loginPasswordInput.error = null
+ }
+
+ override fun resetViewModel() {
+ viewModel.handle(OnboardingAction.ResetAuthenticationAttempt)
+ }
+
+ override fun onError(throwable: Throwable) {
+ // Trick to display the error without text.
+ views.loginInput.error = " "
+ loginErrorParser.parse(throwable, views.loginPasswordInput.content())
+ .onUnknown { super.onError(it) }
+ .onUsernameOrIdError { views.loginInput.error = it }
+ .onPasswordError { views.loginPasswordInput.error = it }
+ }
+
+ override fun updateWithState(state: OnboardingViewState) {
+ setupUi(state)
+ setupAutoFill()
+
+ views.selectedServerName.text = state.selectedHomeserver.userFacingUrl.toReducedUrl()
+ views.selectedServerDescription.text = state.selectedHomeserver.description
+
+ if (state.isLoading) {
+ // Ensure password is hidden
+ views.loginPasswordInput.editText().hidePassword()
+ }
+ }
+
+ private fun setupUi(state: OnboardingViewState) {
+ when (state.selectedHomeserver.preferredLoginMode) {
+ is LoginMode.SsoAndPassword -> {
+ showUsernamePassword()
+ renderSsoProviders(state.deviceId, state.selectedHomeserver.preferredLoginMode.ssoIdentityProviders)
+ }
+ is LoginMode.Sso -> {
+ hideUsernamePassword()
+ renderSsoProviders(state.deviceId, state.selectedHomeserver.preferredLoginMode.ssoIdentityProviders)
+ }
+ else -> {
+ showUsernamePassword()
+ hideSsoProviders()
+ }
+ }
+ }
+
+ private fun renderSsoProviders(deviceId: String?, ssoProviders: List?) {
+ views.ssoGroup.isVisible = ssoProviders?.isNotEmpty() == true
+ views.ssoButtonsHeader.isVisible = views.ssoGroup.isVisible && views.loginEntryGroup.isVisible
+ views.ssoButtons.render(ssoProviders, SocialLoginButtonsView.Mode.MODE_CONTINUE) { id ->
+ viewModel.getSsoUrl(
+ redirectUrl = SSORedirectRouterActivity.VECTOR_REDIRECT_URL,
+ deviceId = deviceId,
+ providerId = id
+ )?.let { openInCustomTab(it) }
+ }
+ }
+
+ private fun hideSsoProviders() {
+ views.ssoGroup.isVisible = false
+ views.ssoButtons.ssoIdentityProviders = null
+ }
+
+ private fun hideUsernamePassword() {
+ views.loginEntryGroup.isVisible = false
+ }
+
+ private fun showUsernamePassword() {
+ views.loginEntryGroup.isVisible = true
+ }
+
+ private fun setupAutoFill() {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
+ views.loginInput.setAutofillHints(HintConstants.AUTOFILL_HINT_NEW_USERNAME)
+ views.loginPasswordInput.setAutofillHints(HintConstants.AUTOFILL_HINT_NEW_PASSWORD)
+ }
+ }
+}
diff --git a/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthCombinedRegisterFragment.kt b/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthCombinedRegisterFragment.kt
index 0755f18c8c..62aa0854c3 100644
--- a/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthCombinedRegisterFragment.kt
+++ b/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthCombinedRegisterFragment.kt
@@ -21,7 +21,6 @@ import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
-import android.view.inputmethod.EditorInfo
import androidx.autofill.HintConstants
import androidx.core.text.isDigitsOnly
import androidx.core.view.isVisible
@@ -31,22 +30,22 @@ import com.google.android.material.dialog.MaterialAlertDialogBuilder
import im.vector.app.R
import im.vector.app.core.extensions.content
import im.vector.app.core.extensions.editText
-import im.vector.app.core.extensions.hasContentFlow
import im.vector.app.core.extensions.hasSurroundingSpaces
import im.vector.app.core.extensions.hideKeyboard
import im.vector.app.core.extensions.hidePassword
import im.vector.app.core.extensions.realignPercentagesToParent
+import im.vector.app.core.extensions.setOnImeDoneListener
import im.vector.app.core.extensions.toReducedUrl
import im.vector.app.databinding.FragmentFtueCombinedRegisterBinding
import im.vector.app.features.login.LoginMode
import im.vector.app.features.login.SSORedirectRouterActivity
import im.vector.app.features.login.SocialLoginButtonsView
+import im.vector.app.features.login.render
import im.vector.app.features.onboarding.OnboardingAction
+import im.vector.app.features.onboarding.OnboardingAction.AuthenticateAction
import im.vector.app.features.onboarding.OnboardingViewEvents
import im.vector.app.features.onboarding.OnboardingViewState
-import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.launchIn
-import kotlinx.coroutines.flow.onEach
import org.matrix.android.sdk.api.auth.data.SsoIdentityProvider
import org.matrix.android.sdk.api.failure.isInvalidPassword
import org.matrix.android.sdk.api.failure.isInvalidUsername
@@ -66,36 +65,16 @@ class FtueAuthCombinedRegisterFragment @Inject constructor() : AbstractSSOFtueAu
super.onViewCreated(view, savedInstanceState)
setupSubmitButton()
views.createAccountRoot.realignPercentagesToParent()
- views.editServerButton.debouncedClicks {
- viewModel.handle(OnboardingAction.PostViewEvent(OnboardingViewEvents.EditServerSelection))
- }
-
- views.createAccountPasswordInput.editText().setOnEditorActionListener { _, actionId, _ ->
- if (actionId == EditorInfo.IME_ACTION_DONE) {
- submit()
- return@setOnEditorActionListener true
- }
- return@setOnEditorActionListener false
- }
+ views.editServerButton.debouncedClicks { viewModel.handle(OnboardingAction.PostViewEvent(OnboardingViewEvents.EditServerSelection)) }
+ views.createAccountPasswordInput.setOnImeDoneListener { submit() }
}
private fun setupSubmitButton() {
views.createAccountSubmit.setOnClickListener { submit() }
- observeInputFields()
- .onEach {
- views.createAccountPasswordInput.error = null
- views.createAccountInput.error = null
- views.createAccountSubmit.isEnabled = it
- }
+ observeContentChangesAndResetErrors(views.createAccountInput, views.createAccountPasswordInput, views.createAccountSubmit)
.launchIn(viewLifecycleOwner.lifecycleScope)
}
- private fun observeInputFields() = combine(
- views.createAccountInput.hasContentFlow { it.trim() },
- views.createAccountPasswordInput.hasContentFlow(),
- transform = { isLoginNotEmpty, isPasswordNotEmpty -> isLoginNotEmpty && isPasswordNotEmpty }
- )
-
private fun submit() {
withState(viewModel) { state ->
cleanupUi()
@@ -119,7 +98,7 @@ class FtueAuthCombinedRegisterFragment @Inject constructor() : AbstractSSOFtueAu
}
if (error == 0) {
- viewModel.handle(OnboardingAction.Register(login, password, getString(R.string.login_default_session_public_name)))
+ viewModel.handle(AuthenticateAction.Register(login, password, getString(R.string.login_default_session_public_name)))
}
}
}
@@ -185,9 +164,7 @@ class FtueAuthCombinedRegisterFragment @Inject constructor() : AbstractSSOFtueAu
private fun renderSsoProviders(deviceId: String?, ssoProviders: List?) {
views.ssoGroup.isVisible = ssoProviders?.isNotEmpty() == true
- views.ssoButtons.mode = SocialLoginButtonsView.Mode.MODE_CONTINUE
- views.ssoButtons.ssoIdentityProviders = ssoProviders?.sorted()
- views.ssoButtons.listener = SocialLoginButtonsView.InteractionListener { id ->
+ views.ssoButtons.render(ssoProviders, SocialLoginButtonsView.Mode.MODE_CONTINUE) { id ->
viewModel.getSsoUrl(
redirectUrl = SSORedirectRouterActivity.VECTOR_REDIRECT_URL,
deviceId = deviceId,
diff --git a/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthCombinedServerSelectionFragment.kt b/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthCombinedServerSelectionFragment.kt
index 2e6057288a..b7a5dc7298 100644
--- a/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthCombinedServerSelectionFragment.kt
+++ b/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthCombinedServerSelectionFragment.kt
@@ -68,7 +68,7 @@ class FtueAuthCombinedServerSelectionFragment @Inject constructor() : AbstractFt
views.chooseServerSubmit.debouncedClicks { updateServerUrl() }
views.chooseServerInput.editText().textChanges()
.onEach { views.chooseServerInput.error = null }
- .launchIn(lifecycleScope)
+ .launchIn(viewLifecycleOwner.lifecycleScope)
}
private fun updateServerUrl() {
diff --git a/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthEmailEntryFragment.kt b/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthEmailEntryFragment.kt
new file mode 100644
index 0000000000..ea376709f5
--- /dev/null
+++ b/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthEmailEntryFragment.kt
@@ -0,0 +1,74 @@
+/*
+ * Copyright (c) 2022 New Vector Ltd
+ *
+ * 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 im.vector.app.features.onboarding.ftueauth
+
+import android.os.Bundle
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import androidx.lifecycle.lifecycleScope
+import im.vector.app.core.extensions.associateContentStateWith
+import im.vector.app.core.extensions.content
+import im.vector.app.core.extensions.editText
+import im.vector.app.core.extensions.isEmail
+import im.vector.app.core.extensions.setOnImeDoneListener
+import im.vector.app.databinding.FragmentFtueEmailInputBinding
+import im.vector.app.features.onboarding.OnboardingAction
+import im.vector.app.features.onboarding.RegisterAction
+import kotlinx.coroutines.flow.launchIn
+import kotlinx.coroutines.flow.onEach
+import org.matrix.android.sdk.api.auth.registration.RegisterThreePid
+import reactivecircus.flowbinding.android.widget.textChanges
+import javax.inject.Inject
+
+class FtueAuthEmailEntryFragment @Inject constructor() : AbstractFtueAuthFragment() {
+
+ override fun getBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentFtueEmailInputBinding {
+ return FragmentFtueEmailInputBinding.inflate(inflater, container, false)
+ }
+
+ override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+ super.onViewCreated(view, savedInstanceState)
+ setupViews()
+ }
+
+ private fun setupViews() {
+ views.emailEntryInput.associateContentStateWith(button = views.emailEntrySubmit)
+ views.emailEntryInput.setOnImeDoneListener { updateEmail() }
+ views.emailEntrySubmit.debouncedClicks { updateEmail() }
+
+ views.emailEntryInput.editText().textChanges()
+ .onEach {
+ views.emailEntryInput.error = null
+ views.emailEntrySubmit.isEnabled = it.isEmail()
+ }
+ .launchIn(viewLifecycleOwner.lifecycleScope)
+ }
+
+ private fun updateEmail() {
+ val email = views.emailEntryInput.content()
+ viewModel.handle(OnboardingAction.PostRegisterAction(RegisterAction.AddThreePid(RegisterThreePid.Email(email))))
+ }
+
+ override fun onError(throwable: Throwable) {
+ views.emailEntryInput.error = errorFormatter.toHumanReadable(throwable)
+ }
+
+ override fun resetViewModel() {
+ viewModel.handle(OnboardingAction.ResetAuthenticationAttempt)
+ }
+}
diff --git a/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthGenericTextInputFormFragment.kt b/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthGenericTextInputFormFragment.kt
index ce3dee7a19..fce1308d3c 100644
--- a/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthGenericTextInputFormFragment.kt
+++ b/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthGenericTextInputFormFragment.kt
@@ -223,12 +223,7 @@ class FtueAuthGenericTextInputFormFragment @Inject constructor() : AbstractFtueA
override fun onError(throwable: Throwable) {
when (params.mode) {
TextInputFormFragmentMode.SetEmail -> {
- if (throwable.is401()) {
- // This is normal use case, we go to the mail waiting screen
- viewModel.handle(OnboardingAction.PostViewEvent(OnboardingViewEvents.OnSendEmailSuccess(viewModel.currentThreePid ?: "")))
- } else {
- views.loginGenericTextInputFormTil.error = errorFormatter.toHumanReadable(throwable)
- }
+ views.loginGenericTextInputFormTil.error = errorFormatter.toHumanReadable(throwable)
}
TextInputFormFragmentMode.SetMsisdn -> {
if (throwable.is401()) {
diff --git a/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthLegacyWaitForEmailFragment.kt b/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthLegacyWaitForEmailFragment.kt
new file mode 100644
index 0000000000..c815f354f0
--- /dev/null
+++ b/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthLegacyWaitForEmailFragment.kt
@@ -0,0 +1,63 @@
+/*
+ * Copyright (c) 2022 New Vector Ltd
+ *
+ * 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 im.vector.app.features.onboarding.ftueauth
+
+import android.os.Bundle
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import com.airbnb.mvrx.args
+import im.vector.app.R
+import im.vector.app.databinding.FragmentLoginWaitForEmailBinding
+import im.vector.app.features.onboarding.OnboardingAction
+import im.vector.app.features.onboarding.RegisterAction
+import javax.inject.Inject
+
+/**
+ * In this screen, the user is asked to check their emails.
+ */
+class FtueAuthLegacyWaitForEmailFragment @Inject constructor() : AbstractFtueAuthFragment() {
+
+ private val params: FtueAuthWaitForEmailFragmentArgument by args()
+
+ override fun getBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentLoginWaitForEmailBinding {
+ return FragmentLoginWaitForEmailBinding.inflate(inflater, container, false)
+ }
+
+ override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+ super.onViewCreated(view, savedInstanceState)
+ setupUi()
+ }
+
+ override fun onResume() {
+ super.onResume()
+ viewModel.handle(OnboardingAction.PostRegisterAction(RegisterAction.CheckIfEmailHasBeenValidated(0)))
+ }
+
+ override fun onPause() {
+ super.onPause()
+ viewModel.handle(OnboardingAction.StopEmailValidationCheck)
+ }
+
+ private fun setupUi() {
+ views.loginWaitForEmailNotice.text = getString(R.string.login_wait_for_email_notice, params.email)
+ }
+
+ override fun resetViewModel() {
+ viewModel.handle(OnboardingAction.ResetAuthenticationAttempt)
+ }
+}
diff --git a/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthLoginFragment.kt b/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthLoginFragment.kt
index 2308280400..98d9a24999 100644
--- a/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthLoginFragment.kt
+++ b/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthLoginFragment.kt
@@ -26,6 +26,7 @@ import androidx.autofill.HintConstants
import androidx.core.text.isDigitsOnly
import androidx.core.view.isVisible
import androidx.lifecycle.lifecycleScope
+import com.airbnb.mvrx.withState
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import im.vector.app.R
import im.vector.app.core.extensions.hideKeyboard
@@ -119,40 +120,43 @@ class FtueAuthLoginFragment @Inject constructor() : AbstractSSOFtueAuthFragment<
}
private fun submit() {
- cleanupUi()
+ withState(viewModel) { state ->
+ cleanupUi()
- val login = views.loginField.text.toString()
- val password = views.passwordField.text.toString()
+ val login = views.loginField.text.toString()
+ val password = views.passwordField.text.toString()
- // This can be called by the IME action, so deal with empty cases
- var error = 0
- if (login.isEmpty()) {
- views.loginFieldTil.error = getString(
- if (isSignupMode) {
- R.string.error_empty_field_choose_user_name
- } else {
- R.string.error_empty_field_enter_user_name
- }
- )
- error++
- }
- if (isSignupMode && isNumericOnlyUserIdForbidden && login.isDigitsOnly()) {
- views.loginFieldTil.error = getString(R.string.error_forbidden_digits_only_username)
- error++
- }
- if (password.isEmpty()) {
- views.passwordFieldTil.error = getString(
- if (isSignupMode) {
- R.string.error_empty_field_choose_password
- } else {
- R.string.error_empty_field_your_password
- }
- )
- error++
- }
+ // This can be called by the IME action, so deal with empty cases
+ var error = 0
+ if (login.isEmpty()) {
+ views.loginFieldTil.error = getString(
+ if (isSignupMode) {
+ R.string.error_empty_field_choose_user_name
+ } else {
+ R.string.error_empty_field_enter_user_name
+ }
+ )
+ error++
+ }
+ if (isSignupMode && isNumericOnlyUserIdForbidden && login.isDigitsOnly()) {
+ views.loginFieldTil.error = getString(R.string.error_forbidden_digits_only_username)
+ error++
+ }
+ if (password.isEmpty()) {
+ views.passwordFieldTil.error = getString(
+ if (isSignupMode) {
+ R.string.error_empty_field_choose_password
+ } else {
+ R.string.error_empty_field_your_password
+ }
+ )
+ error++
+ }
- if (error == 0) {
- viewModel.handle(OnboardingAction.LoginOrRegister(login, password, getString(R.string.login_default_session_public_name)))
+ if (error == 0) {
+ val initialDeviceName = getString(R.string.login_default_session_public_name)
+ viewModel.handle(state.signMode.toAuthenticateAction(login, password, initialDeviceName))
+ }
}
}
diff --git a/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthSplashCarouselFragment.kt b/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthSplashCarouselFragment.kt
index 49e8875cb5..30416bde9e 100644
--- a/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthSplashCarouselFragment.kt
+++ b/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthSplashCarouselFragment.kt
@@ -22,6 +22,8 @@ import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.core.view.isVisible
+import androidx.lifecycle.DefaultLifecycleObserver
+import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.lifecycleScope
import androidx.viewpager2.widget.ViewPager2
import com.airbnb.mvrx.withState
@@ -90,7 +92,7 @@ class FtueAuthSplashCarouselFragment @Inject constructor(
private fun ViewPager2.registerAutomaticUntilInteractionTransitions() {
var scheduledTransition: Job? = null
- registerOnPageChangeCallback(object : ViewPager2.OnPageChangeCallback() {
+ val pageChangingCallback = object : ViewPager2.OnPageChangeCallback() {
private var hasUserManuallyInteractedWithCarousel: Boolean = false
override fun onPageScrolled(position: Int, positionOffset: Float, positionOffsetPixels: Int) {
@@ -104,12 +106,21 @@ class FtueAuthSplashCarouselFragment @Inject constructor(
scheduledTransition = scheduleCarouselTransition()
}
}
+ }
+ viewLifecycleOwner.lifecycle.addObserver(object : DefaultLifecycleObserver {
+ override fun onCreate(owner: LifecycleOwner) {
+ registerOnPageChangeCallback(pageChangingCallback)
+ }
+
+ override fun onDestroy(owner: LifecycleOwner) {
+ unregisterOnPageChangeCallback(pageChangingCallback)
+ }
})
}
private fun ViewPager2.scheduleCarouselTransition(): Job {
val itemCount = adapter?.itemCount ?: throw IllegalStateException("An adapter must be set")
- return lifecycleScope.launch {
+ return viewLifecycleOwner.lifecycleScope.launch {
delay(CAROUSEL_ROTATION_DELAY_MS)
setCurrentItem(currentItem.incrementByOneAndWrap(max = itemCount - 1), duration = CAROUSEL_TRANSITION_TIME_MS)
}
diff --git a/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthVariant.kt b/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthVariant.kt
index 6f1b85df4f..5ad6b7e78d 100644
--- a/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthVariant.kt
+++ b/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthVariant.kt
@@ -54,7 +54,6 @@ import im.vector.app.features.onboarding.OnboardingViewState
import im.vector.app.features.onboarding.ftueauth.terms.FtueAuthLegacyStyleTermsFragment
import im.vector.app.features.onboarding.ftueauth.terms.FtueAuthTermsFragment
import im.vector.app.features.onboarding.ftueauth.terms.FtueAuthTermsLegacyStyleFragmentArgument
-import org.matrix.android.sdk.api.auth.registration.FlowResult
import org.matrix.android.sdk.api.auth.registration.Stage
import org.matrix.android.sdk.api.auth.toLocalizedLoginTerms
import org.matrix.android.sdk.api.extensions.tryOrNull
@@ -192,12 +191,7 @@ class FtueAuthVariant(
supportFragmentManager.popBackStack(FRAGMENT_LOGIN_TAG, POP_BACK_STACK_EXCLUSIVE)
}
is OnboardingViewEvents.OnSendEmailSuccess -> {
- // Pop the enter email Fragment
- supportFragmentManager.popBackStack(FRAGMENT_REGISTRATION_STAGE_TAG, FragmentManager.POP_BACK_STACK_INCLUSIVE)
- addRegistrationStageFragmentToBackstack(
- FtueAuthWaitForEmailFragment::class.java,
- FtueAuthWaitForEmailFragmentArgument(viewEvents.email),
- )
+ openWaitForEmailVerification(viewEvents.email)
}
is OnboardingViewEvents.OnSendMsisdnSuccess -> {
// Pop the enter Msisdn Fragment
@@ -233,24 +227,24 @@ class FtueAuthVariant(
option = commonOption
)
}
- OnboardingViewEvents.OnHomeserverEdited -> activity.popBackstack()
+ OnboardingViewEvents.OnHomeserverEdited -> activity.popBackstack()
+ OnboardingViewEvents.OpenCombinedLogin -> onStartCombinedLogin()
}
}
+ private fun onStartCombinedLogin() {
+ addRegistrationStageFragmentToBackstack(FtueAuthCombinedLoginFragment::class.java)
+ }
+
private fun onRegistrationFlow(viewEvents: OnboardingViewEvents.RegistrationFlowResult) {
when {
registrationShouldFallback(viewEvents) -> displayFallbackWebDialog()
- viewEvents.isRegistrationStarted -> handleRegistrationNavigation(viewEvents.flowResult.orderedStages())
+ viewEvents.isRegistrationStarted -> handleRegistrationNavigation(viewEvents.flowResult.missingStages)
vectorFeatures.isOnboardingCombinedRegisterEnabled() -> openStartCombinedRegister()
else -> openAuthLoginFragmentWithTag(FRAGMENT_REGISTRATION_STAGE_TAG)
}
}
- private fun FlowResult.orderedStages() = when {
- vectorFeatures.isOnboardingCombinedRegisterEnabled() -> missingStages.sortedWith(FtueMissingRegistrationStagesComparator())
- else -> missingStages
- }
-
private fun openStartCombinedRegister() {
addRegistrationStageFragmentToBackstack(FtueAuthCombinedRegisterFragment::class.java)
}
@@ -393,10 +387,7 @@ class FtueAuthVariant(
when (stage) {
is Stage.ReCaptcha -> onCaptcha(stage)
- is Stage.Email -> addRegistrationStageFragmentToBackstack(
- FtueAuthGenericTextInputFormFragment::class.java,
- FtueAuthGenericTextInputFormFragmentArgument(TextInputFormFragmentMode.SetEmail, stage.mandatory),
- )
+ is Stage.Email -> onEmail(stage)
is Stage.Msisdn -> addRegistrationStageFragmentToBackstack(
FtueAuthGenericTextInputFormFragment::class.java,
FtueAuthGenericTextInputFormFragmentArgument(TextInputFormFragmentMode.SetMsisdn, stage.mandatory),
@@ -406,6 +397,32 @@ class FtueAuthVariant(
}
}
+ private fun onEmail(stage: Stage) {
+ when {
+ vectorFeatures.isOnboardingCombinedRegisterEnabled() -> addRegistrationStageFragmentToBackstack(
+ FtueAuthEmailEntryFragment::class.java
+ )
+ else -> addRegistrationStageFragmentToBackstack(
+ FtueAuthGenericTextInputFormFragment::class.java,
+ FtueAuthGenericTextInputFormFragmentArgument(TextInputFormFragmentMode.SetEmail, stage.mandatory),
+ )
+ }
+ }
+
+ private fun openWaitForEmailVerification(email: String) {
+ supportFragmentManager.popBackStack(FRAGMENT_REGISTRATION_STAGE_TAG, FragmentManager.POP_BACK_STACK_INCLUSIVE)
+ when {
+ vectorFeatures.isOnboardingCombinedRegisterEnabled() -> addRegistrationStageFragmentToBackstack(
+ FtueAuthWaitForEmailFragment::class.java,
+ FtueAuthWaitForEmailFragmentArgument(email),
+ )
+ else -> addRegistrationStageFragmentToBackstack(
+ FtueAuthLegacyWaitForEmailFragment::class.java,
+ FtueAuthWaitForEmailFragmentArgument(email),
+ )
+ }
+ }
+
private fun onTerms(stage: Stage.Terms) {
when {
vectorFeatures.isOnboardingCombinedRegisterEnabled() -> addRegistrationStageFragmentToBackstack(
diff --git a/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthWaitForEmailFragment.kt b/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthWaitForEmailFragment.kt
index d78e0fe74d..c81a9c2feb 100644
--- a/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthWaitForEmailFragment.kt
+++ b/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueAuthWaitForEmailFragment.kt
@@ -21,13 +21,16 @@ import android.os.Parcelable
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
+import androidx.core.view.isVisible
import com.airbnb.mvrx.args
import im.vector.app.R
-import im.vector.app.databinding.FragmentLoginWaitForEmailBinding
+import im.vector.app.core.utils.colorTerminatingFullStop
+import im.vector.app.databinding.FragmentFtueWaitForEmailVerificationBinding
import im.vector.app.features.onboarding.OnboardingAction
import im.vector.app.features.onboarding.RegisterAction
+import im.vector.app.features.themes.ThemeProvider
+import im.vector.app.features.themes.ThemeUtils
import kotlinx.parcelize.Parcelize
-import org.matrix.android.sdk.api.failure.is401
import javax.inject.Inject
@Parcelize
@@ -38,45 +41,57 @@ data class FtueAuthWaitForEmailFragmentArgument(
/**
* In this screen, the user is asked to check their emails.
*/
-class FtueAuthWaitForEmailFragment @Inject constructor() : AbstractFtueAuthFragment() {
+class FtueAuthWaitForEmailFragment @Inject constructor(
+ private val themeProvider: ThemeProvider
+) : AbstractFtueAuthFragment() {
private val params: FtueAuthWaitForEmailFragmentArgument by args()
+ private var inferHasLeftAndReturnedToScreen = false
- override fun getBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentLoginWaitForEmailBinding {
- return FragmentLoginWaitForEmailBinding.inflate(inflater, container, false)
+ override fun getBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentFtueWaitForEmailVerificationBinding {
+ return FragmentFtueWaitForEmailVerificationBinding.inflate(inflater, container, false)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
-
setupUi()
}
+ private fun setupUi() {
+ views.emailVerificationGradientContainer.setBackgroundResource(
+ when (themeProvider.isLightTheme()) {
+ true -> R.drawable.bg_waiting_for_email_verification
+ false -> R.drawable.bg_color_background
+ }
+ )
+ views.emailVerificationTitle.text = getString(R.string.ftue_auth_email_verification_title)
+ .colorTerminatingFullStop(ThemeUtils.getColor(requireContext(), R.attr.colorSecondary))
+ views.emailVerificationSubtitle.text = getString(R.string.ftue_auth_email_verification_subtitle, params.email)
+ views.emailVerificationResendEmail.debouncedClicks {
+ viewModel.handle(OnboardingAction.PostRegisterAction(RegisterAction.SendAgainThreePid))
+ }
+ }
+
override fun onResume() {
super.onResume()
-
+ showLoadingIfReturningToScreen()
viewModel.handle(OnboardingAction.PostRegisterAction(RegisterAction.CheckIfEmailHasBeenValidated(0)))
}
+ private fun showLoadingIfReturningToScreen() {
+ when (inferHasLeftAndReturnedToScreen) {
+ true -> views.emailVerificationWaiting.isVisible = true
+ false -> {
+ inferHasLeftAndReturnedToScreen = true
+ }
+ }
+ }
+
override fun onPause() {
super.onPause()
-
viewModel.handle(OnboardingAction.StopEmailValidationCheck)
}
- private fun setupUi() {
- views.loginWaitForEmailNotice.text = getString(R.string.login_wait_for_email_notice, params.email)
- }
-
- override fun onError(throwable: Throwable) {
- if (throwable.is401()) {
- // Try again, with a delay
- viewModel.handle(OnboardingAction.PostRegisterAction(RegisterAction.CheckIfEmailHasBeenValidated(10_000)))
- } else {
- super.onError(throwable)
- }
- }
-
override fun resetViewModel() {
viewModel.handle(OnboardingAction.ResetAuthenticationAttempt)
}
diff --git a/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueExtensions.kt b/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueExtensions.kt
new file mode 100644
index 0000000000..8d63fbf547
--- /dev/null
+++ b/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueExtensions.kt
@@ -0,0 +1,51 @@
+/*
+ * Copyright (c) 2022 New Vector Ltd
+ *
+ * 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 im.vector.app.features.onboarding.ftueauth
+
+import android.widget.Button
+import com.google.android.material.textfield.TextInputLayout
+import im.vector.app.core.extensions.hasContentFlow
+import im.vector.app.features.login.SignMode
+import im.vector.app.features.onboarding.OnboardingAction
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.onEach
+
+fun SignMode.toAuthenticateAction(login: String, password: String, initialDeviceName: String): OnboardingAction.AuthenticateAction {
+ return when (this) {
+ SignMode.Unknown -> error("developer error")
+ SignMode.SignUp -> OnboardingAction.AuthenticateAction.Register(username = login, password, initialDeviceName)
+ SignMode.SignIn -> OnboardingAction.AuthenticateAction.Login(username = login, password, initialDeviceName)
+ SignMode.SignInWithMatrixId -> OnboardingAction.AuthenticateAction.LoginDirect(matrixId = login, password, initialDeviceName)
+ }
+}
+
+/**
+ * A flow to monitor content changes from both username/id and password fields,
+ * clearing errors and enabling/disabling the submission button on non empty content changes.
+ */
+fun observeContentChangesAndResetErrors(username: TextInputLayout, password: TextInputLayout, submit: Button): Flow<*> {
+ return combine(
+ username.hasContentFlow { it.trim() },
+ password.hasContentFlow(),
+ transform = { usernameHasContent, passwordHasContent -> usernameHasContent && passwordHasContent }
+ ).onEach {
+ username.error = null
+ password.error = null
+ submit.isEnabled = it
+ }
+}
diff --git a/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/LoginErrorParser.kt b/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/LoginErrorParser.kt
new file mode 100644
index 0000000000..a92fdea04a
--- /dev/null
+++ b/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/LoginErrorParser.kt
@@ -0,0 +1,69 @@
+/*
+ * Copyright (c) 2022 New Vector Ltd
+ *
+ * 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 im.vector.app.features.onboarding.ftueauth
+
+import im.vector.app.R
+import im.vector.app.core.error.ErrorFormatter
+import im.vector.app.core.resources.StringProvider
+import im.vector.app.features.onboarding.ftueauth.LoginErrorParser.LoginErrorResult
+import org.matrix.android.sdk.api.failure.isInvalidPassword
+import org.matrix.android.sdk.api.failure.isInvalidUsername
+import org.matrix.android.sdk.api.failure.isLoginEmailUnknown
+import javax.inject.Inject
+
+class LoginErrorParser @Inject constructor(
+ private val errorFormatter: ErrorFormatter,
+ private val stringProvider: StringProvider,
+) {
+ fun parse(throwable: Throwable, password: String): LoginErrorResult {
+ return when {
+ throwable.isInvalidUsername() -> {
+ LoginErrorResult(throwable, usernameOrIdError = errorFormatter.toHumanReadable(throwable))
+ }
+ throwable.isLoginEmailUnknown() -> {
+ LoginErrorResult(throwable, usernameOrIdError = stringProvider.getString(R.string.login_login_with_email_error))
+ }
+ throwable.isInvalidPassword() && password.hasSurroundingSpaces() -> {
+ LoginErrorResult(throwable, passwordError = stringProvider.getString(R.string.auth_invalid_login_param_space_in_password))
+ }
+ else -> {
+ LoginErrorResult(throwable)
+ }
+ }
+ }
+
+ private fun String.hasSurroundingSpaces() = trim() != this
+
+ data class LoginErrorResult(val cause: Throwable, val usernameOrIdError: String? = null, val passwordError: String? = null)
+}
+
+fun LoginErrorResult.onUnknown(action: (Throwable) -> Unit): LoginErrorResult {
+ when {
+ usernameOrIdError == null && passwordError == null -> action(cause)
+ }
+ return this
+}
+
+fun LoginErrorResult.onUsernameOrIdError(action: (String) -> Unit): LoginErrorResult {
+ usernameOrIdError?.let(action)
+ return this
+}
+
+fun LoginErrorResult.onPasswordError(action: (String) -> Unit): LoginErrorResult {
+ passwordError?.let(action)
+ return this
+}
diff --git a/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/LoginFieldsValidation.kt b/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/LoginFieldsValidation.kt
new file mode 100644
index 0000000000..659a8cd2c1
--- /dev/null
+++ b/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/LoginFieldsValidation.kt
@@ -0,0 +1,63 @@
+/*
+ * Copyright (c) 2022 New Vector Ltd
+ *
+ * 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 im.vector.app.features.onboarding.ftueauth
+
+import im.vector.app.R
+import im.vector.app.core.resources.StringProvider
+import javax.inject.Inject
+
+class LoginFieldsValidation @Inject constructor(
+ private val stringProvider: StringProvider
+) {
+
+ fun validate(usernameOrId: String, password: String): LoginValidationResult {
+ return LoginValidationResult(usernameOrId, password, validateUsernameOrId(usernameOrId), validatePassword(password))
+ }
+
+ private fun validateUsernameOrId(usernameOrId: String): String? {
+ val accountError = when {
+ usernameOrId.isEmpty() -> stringProvider.getString(R.string.error_empty_field_enter_user_name)
+ else -> null
+ }
+ return accountError
+ }
+
+ private fun validatePassword(password: String): String? {
+ val passwordError = when {
+ password.isEmpty() -> stringProvider.getString(R.string.error_empty_field_your_password)
+ else -> null
+ }
+ return passwordError
+ }
+}
+
+fun LoginValidationResult.onValid(action: (String, String) -> Unit): LoginValidationResult {
+ when {
+ usernameOrIdError == null && passwordError == null -> action(usernameOrId, password)
+ }
+ return this
+}
+
+fun LoginValidationResult.onUsernameOrIdError(action: (String) -> Unit): LoginValidationResult {
+ usernameOrIdError?.let(action)
+ return this
+}
+
+fun LoginValidationResult.onPasswordError(action: (String) -> Unit): LoginValidationResult {
+ passwordError?.let(action)
+ return this
+}
diff --git a/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/LoginValidationResult.kt b/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/LoginValidationResult.kt
new file mode 100644
index 0000000000..caf127332a
--- /dev/null
+++ b/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/LoginValidationResult.kt
@@ -0,0 +1,24 @@
+/*
+ * Copyright (c) 2022 New Vector Ltd
+ *
+ * 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 im.vector.app.features.onboarding.ftueauth
+
+data class LoginValidationResult(
+ val usernameOrId: String,
+ val password: String,
+ val usernameOrIdError: String?,
+ val passwordError: String?
+)
diff --git a/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueMissingRegistrationStagesComparator.kt b/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/MatrixOrgRegistrationStagesComparator.kt
similarity index 84%
rename from vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueMissingRegistrationStagesComparator.kt
rename to vector/src/main/java/im/vector/app/features/onboarding/ftueauth/MatrixOrgRegistrationStagesComparator.kt
index 6a6326625e..527c20987a 100644
--- a/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/FtueMissingRegistrationStagesComparator.kt
+++ b/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/MatrixOrgRegistrationStagesComparator.kt
@@ -18,10 +18,10 @@ package im.vector.app.features.onboarding.ftueauth
import org.matrix.android.sdk.api.auth.registration.Stage
-class FtueMissingRegistrationStagesComparator : Comparator {
+class MatrixOrgRegistrationStagesComparator : Comparator {
- override fun compare(a: Stage?, b: Stage?): Int {
- return (a?.toPriority() ?: 0) - (b?.toPriority() ?: 0)
+ override fun compare(a: Stage, b: Stage): Int {
+ return a.toPriority().compareTo(b.toPriority())
}
private fun Stage.toPriority() = when (this) {
diff --git a/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/SplashCarouselStateFactory.kt b/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/SplashCarouselStateFactory.kt
index 23f7014374..f8b885ddee 100644
--- a/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/SplashCarouselStateFactory.kt
+++ b/vector/src/main/java/im/vector/app/features/onboarding/ftueauth/SplashCarouselStateFactory.kt
@@ -23,11 +23,11 @@ import im.vector.app.R
import im.vector.app.core.resources.LocaleProvider
import im.vector.app.core.resources.StringProvider
import im.vector.app.core.resources.isEnglishSpeaking
+import im.vector.app.core.utils.colorTerminatingFullStop
import im.vector.app.features.themes.ThemeProvider
import im.vector.app.features.themes.ThemeUtils
import im.vector.lib.core.utils.epoxy.charsequence.EpoxyCharSequence
import im.vector.lib.core.utils.epoxy.charsequence.toEpoxyCharSequence
-import me.gujun.android.span.span
import javax.inject.Inject
class SplashCarouselStateFactory @Inject constructor(
@@ -39,7 +39,7 @@ class SplashCarouselStateFactory @Inject constructor(
fun create(): SplashCarouselState {
val lightTheme = themeProvider.isLightTheme()
- fun background(@DrawableRes lightDrawable: Int) = if (lightTheme) lightDrawable else R.drawable.bg_carousel_page_dark
+ fun background(@DrawableRes lightDrawable: Int) = if (lightTheme) lightDrawable else R.drawable.bg_color_background
fun hero(@DrawableRes lightDrawable: Int, @DrawableRes darkDrawable: Int) = if (lightTheme) lightDrawable else darkDrawable
return SplashCarouselState(
listOf(
@@ -79,18 +79,8 @@ class SplashCarouselStateFactory @Inject constructor(
}
private fun Int.colorTerminatingFullStop(@AttrRes color: Int): EpoxyCharSequence {
- val string = stringProvider.getString(this)
- val fullStop = "."
- val charSequence = if (string.endsWith(fullStop)) {
- span {
- +string.removeSuffix(fullStop)
- span(fullStop) {
- textColor = ThemeUtils.getColor(context, color)
- }
- }
- } else {
- string
- }
- return charSequence.toEpoxyCharSequence()
+ return stringProvider.getString(this)
+ .colorTerminatingFullStop(ThemeUtils.getColor(context, color))
+ .toEpoxyCharSequence()
}
}
diff --git a/vector/src/main/java/im/vector/app/features/rageshake/BugReporter.kt b/vector/src/main/java/im/vector/app/features/rageshake/BugReporter.kt
index b7ce7ffdb4..e2c1aaa2a4 100755
--- a/vector/src/main/java/im/vector/app/features/rageshake/BugReporter.kt
+++ b/vector/src/main/java/im/vector/app/features/rageshake/BugReporter.kt
@@ -156,13 +156,16 @@ class BugReporter @Inject constructor(
/**
* Send a bug report.
*
- * @param reportType The report type (bug, suggestion, feedback)
- * @param withDevicesLogs true to include the device log
- * @param withCrashLogs true to include the crash logs
+ * @param reportType The report type (bug, suggestion, feedback)
+ * @param withDevicesLogs true to include the device log
+ * @param withCrashLogs true to include the crash logs
* @param withKeyRequestHistory true to include the crash logs
- * @param withScreenshot true to include the screenshot
+ * @param withScreenshot true to include the screenshot
* @param theBugDescription the bug description
- * @param listener the listener
+ * @param serverVersion version of the server
+ * @param canContact true if the user opt in to be contacted directly
+ * @param customFields fields which will be sent with the report
+ * @param listener the listener
*/
@SuppressLint("StaticFieldLeak")
fun sendBugReport(reportType: ReportType,
@@ -287,7 +290,8 @@ class BugReporter @Inject constructor(
.addFormDataPart("app_language", VectorLocale.applicationLocale.toString())
.addFormDataPart("default_app_language", systemLocaleProvider.getSystemLocale().toString())
.addFormDataPart("theme", ThemeUtils.getApplicationTheme(context))
- .addFormDataPart("server_version", serverVersion).apply {
+ .addFormDataPart("server_version", serverVersion)
+ .apply {
customFields?.forEach { (name, value) ->
addFormDataPart(name, value)
}
@@ -678,7 +682,7 @@ class BugReporter @Inject constructor(
/**
* Retrieves the logs.
*
- * @param streamWriter the stream writer
+ * @param streamWriter the stream writer
* @param isErrorLogCat true to save the error logs
*/
private fun getLogCatError(streamWriter: OutputStreamWriter, isErrorLogCat: Boolean) {
diff --git a/vector/src/main/java/im/vector/app/features/rageshake/BugReporterMultipartBody.java b/vector/src/main/java/im/vector/app/features/rageshake/BugReporterMultipartBody.java
index a530b6e667..72cc63e5c7 100755
--- a/vector/src/main/java/im/vector/app/features/rageshake/BugReporterMultipartBody.java
+++ b/vector/src/main/java/im/vector/app/features/rageshake/BugReporterMultipartBody.java
@@ -39,7 +39,7 @@ public class BugReporterMultipartBody extends RequestBody {
/**
* Upload listener
*
- * @param totalWritten total written bytes
+ * @param totalWritten total written bytes
* @param contentLength content length
*/
void onWrite(long totalWritten, long contentLength);
@@ -296,4 +296,4 @@ public class BugReporterMultipartBody extends RequestBody {
return new BugReporterMultipartBody(boundary, parts);
}
}
-}
\ No newline at end of file
+}
diff --git a/vector/src/main/java/im/vector/app/features/rageshake/VectorUncaughtExceptionHandler.kt b/vector/src/main/java/im/vector/app/features/rageshake/VectorUncaughtExceptionHandler.kt
index bc78b84088..5496ff4a94 100644
--- a/vector/src/main/java/im/vector/app/features/rageshake/VectorUncaughtExceptionHandler.kt
+++ b/vector/src/main/java/im/vector/app/features/rageshake/VectorUncaughtExceptionHandler.kt
@@ -57,7 +57,7 @@ class VectorUncaughtExceptionHandler @Inject constructor(
/**
* An uncaught exception has been triggered.
*
- * @param thread the thread
+ * @param thread the thread
* @param throwable the throwable
* @return the exception description
*/
diff --git a/vector/src/main/java/im/vector/app/features/settings/FontScale.kt b/vector/src/main/java/im/vector/app/features/settings/FontScale.kt
index c4ea730afd..a1acef7d35 100644
--- a/vector/src/main/java/im/vector/app/features/settings/FontScale.kt
+++ b/vector/src/main/java/im/vector/app/features/settings/FontScale.kt
@@ -78,6 +78,7 @@ object FontScale {
/**
* Store the font scale value.
*
+ * @param context the Android context
* @param fontScaleValue the font scale value to store
*/
private fun saveFontScaleValue(context: Context, fontScaleValue: FontScaleValue) {
diff --git a/vector/src/main/java/im/vector/app/features/settings/VectorLocale.kt b/vector/src/main/java/im/vector/app/features/settings/VectorLocale.kt
index 3fb3d3f7c8..326f20845f 100644
--- a/vector/src/main/java/im/vector/app/features/settings/VectorLocale.kt
+++ b/vector/src/main/java/im/vector/app/features/settings/VectorLocale.kt
@@ -120,8 +120,8 @@ object VectorLocale {
/**
* Get String from a locale.
*
- * @param context the context
- * @param locale the locale
+ * @param context the context
+ * @param locale the locale
* @param resourceId the string resource id
* @return the localized string
*/
diff --git a/vector/src/main/java/im/vector/app/features/settings/VectorPreferences.kt b/vector/src/main/java/im/vector/app/features/settings/VectorPreferences.kt
index c841c6a0af..72f6080417 100755
--- a/vector/src/main/java/im/vector/app/features/settings/VectorPreferences.kt
+++ b/vector/src/main/java/im/vector/app/features/settings/VectorPreferences.kt
@@ -203,6 +203,7 @@ class VectorPreferences @Inject constructor(
private const val TAKE_PHOTO_VIDEO_MODE = "TAKE_PHOTO_VIDEO_MODE"
private const val SETTINGS_LABS_RENDER_LOCATIONS_IN_TIMELINE = "SETTINGS_LABS_RENDER_LOCATIONS_IN_TIMELINE"
+ private const val SETTINGS_LABS_ENABLE_LIVE_LOCATION = "SETTINGS_LABS_ENABLE_LIVE_LOCATION"
// This key will be used to identify clients with the old thread support enabled io.element.thread
const val SETTINGS_LABS_ENABLE_THREAD_MESSAGES_OLD_CLIENTS = "SETTINGS_LABS_ENABLE_THREAD_MESSAGES"
@@ -492,7 +493,7 @@ class VectorPreferences @Inject constructor(
/**
* Update the notification ringtone.
*
- * @param uri the new notification ringtone, or null for no RingTone
+ * @param uri the new notification ringtone, or null for no RingTone
*/
fun setNotificationRingTone(uri: Uri?) {
defaultPrefs.edit {
@@ -635,7 +636,7 @@ class VectorPreferences @Inject constructor(
/**
* Tells if the application is started on boot.
*
- * @param value true to start the application on boot
+ * @param value true to start the application on boot
*/
fun setAutoStartOnBoot(value: Boolean) {
defaultPrefs.edit {
@@ -655,7 +656,7 @@ class VectorPreferences @Inject constructor(
/**
* Updates the selected saving period.
*
- * @param index the selected period index
+ * @param index the selected period index
*/
fun setSelectedMediasSavingPeriod(index: Int) {
defaultPrefs.edit {
@@ -1041,6 +1042,10 @@ class VectorPreferences @Inject constructor(
return defaultPrefs.getBoolean(SETTINGS_LABS_RENDER_LOCATIONS_IN_TIMELINE, true)
}
+ fun labsEnableLiveLocation(): Boolean {
+ return defaultPrefs.getBoolean(SETTINGS_LABS_ENABLE_LIVE_LOCATION, false)
+ }
+
/**
* Indicates whether or not thread messages are enabled.
*/
diff --git a/vector/src/main/java/im/vector/app/features/themes/ThemeUtils.kt b/vector/src/main/java/im/vector/app/features/themes/ThemeUtils.kt
index 3b1e8240fa..3d1a224d0c 100644
--- a/vector/src/main/java/im/vector/app/features/themes/ThemeUtils.kt
+++ b/vector/src/main/java/im/vector/app/features/themes/ThemeUtils.kt
@@ -108,6 +108,7 @@ object ThemeUtils {
/**
* Update the application theme.
*
+ * @param context the Android context
* @param aTheme the new theme
*/
fun setApplicationTheme(context: Context, aTheme: String) {
@@ -126,9 +127,11 @@ object ThemeUtils {
}
/**
- * Set the activity theme according to the selected one.
+ * Set the activity theme according to the selected one. Default is Light, so if this is the current
+ * theme, the theme is not changed.
*
* @param activity the activity
+ * @param otherThemes themes to apply for dark and black theme
*/
fun setActivityTheme(activity: Activity, otherThemes: ActivityOtherThemes) {
when (getApplicationTheme(activity)) {
@@ -143,7 +146,7 @@ object ThemeUtils {
/**
* Translates color attributes to colors.
*
- * @param c Context
+ * @param c Context
* @param colorAttribute Color Attribute
* @return Requested Color
*/
@@ -175,8 +178,8 @@ object ThemeUtils {
/**
* Tint the drawable with a theme attribute.
*
- * @param context the context
- * @param drawable the drawable to tint
+ * @param context the context
+ * @param drawable the drawable to tint
* @param attribute the theme color
* @return the tinted drawable
*/
@@ -188,7 +191,7 @@ object ThemeUtils {
* Tint the drawable with a color integer.
*
* @param drawable the drawable to tint
- * @param color the color
+ * @param color the color
* @return the tinted drawable
*/
fun tintDrawableWithColor(drawable: Drawable, @ColorInt color: Int): Drawable {
diff --git a/vector/src/main/java/im/vector/app/features/webview/WebViewEventListener.kt b/vector/src/main/java/im/vector/app/features/webview/WebViewEventListener.kt
index bd77283029..2f00ad07b9 100644
--- a/vector/src/main/java/im/vector/app/features/webview/WebViewEventListener.kt
+++ b/vector/src/main/java/im/vector/app/features/webview/WebViewEventListener.kt
@@ -48,8 +48,8 @@ interface WebViewEventListener {
/**
* Triggered when an error occurred while loading a page.
*
- * @param url The url that failed.
- * @param errorCode The error code.
+ * @param url The url that failed.
+ * @param errorCode The error code.
* @param description The error description.
*/
fun onPageError(url: String, errorCode: Int, description: String) {
@@ -59,8 +59,8 @@ interface WebViewEventListener {
/**
* Triggered when an error occurred while loading a page.
*
- * @param url The url that failed.
- * @param errorCode The error code.
+ * @param url The url that failed.
+ * @param errorCode The error code.
* @param description The error description.
*/
fun onHttpError(url: String, errorCode: Int, description: String) {
diff --git a/vector/src/main/java/im/vector/app/features/widgets/WidgetPostAPIHandler.kt b/vector/src/main/java/im/vector/app/features/widgets/WidgetPostAPIHandler.kt
index 3c88ea65a3..fc73e71b51 100644
--- a/vector/src/main/java/im/vector/app/features/widgets/WidgetPostAPIHandler.kt
+++ b/vector/src/main/java/im/vector/app/features/widgets/WidgetPostAPIHandler.kt
@@ -110,6 +110,7 @@ class WidgetPostAPIHandler @AssistedInject constructor(@Assisted private val roo
/**
* Retrieve the latest botOptions event.
*
+ * @param widgetPostAPIMediator the post api mediator
* @param eventData the modular data
*/
private fun getBotOptions(widgetPostAPIMediator: WidgetPostAPIMediator, eventData: JsonDict) {
@@ -171,6 +172,7 @@ class WidgetPostAPIHandler @AssistedInject constructor(@Assisted private val roo
/**
* Provides the membership state.
*
+ * @param widgetPostAPIMediator the post api mediator
* @param eventData the modular data
*/
private fun getMembershipState(widgetPostAPIMediator: WidgetPostAPIMediator, eventData: JsonDict) {
@@ -190,6 +192,7 @@ class WidgetPostAPIHandler @AssistedInject constructor(@Assisted private val roo
/**
* Request the latest joined room event.
*
+ * @param widgetPostAPIMediator the post api mediator
* @param eventData the modular data
*/
private fun getJoinRules(widgetPostAPIMediator: WidgetPostAPIMediator, eventData: JsonDict) {
@@ -208,6 +211,7 @@ class WidgetPostAPIHandler @AssistedInject constructor(@Assisted private val roo
/**
* Provide the widgets list.
*
+ * @param widgetPostAPIMediator the post api mediator
* @param eventData the modular data
*/
private fun getWidgets(widgetPostAPIMediator: WidgetPostAPIMediator, eventData: JsonDict) {
@@ -228,6 +232,7 @@ class WidgetPostAPIHandler @AssistedInject constructor(@Assisted private val roo
/**
* Set a new widget.
*
+ * @param widgetPostAPIMediator the post api mediator
* @param eventData the modular data
*/
private fun setWidget(widgetPostAPIMediator: WidgetPostAPIMediator, eventData: JsonDict) {
@@ -303,6 +308,7 @@ class WidgetPostAPIHandler @AssistedInject constructor(@Assisted private val roo
/**
* Update the 'plumbing state".
*
+ * @param widgetPostAPIMediator the post api mediator
* @param eventData the modular data
*/
private fun setPlumbingState(widgetPostAPIMediator: WidgetPostAPIMediator, eventData: JsonDict) {
@@ -328,6 +334,7 @@ class WidgetPostAPIHandler @AssistedInject constructor(@Assisted private val roo
/**
* Update the bot options.
*
+ * @param widgetPostAPIMediator the post api mediator
* @param eventData the modular data
*/
@Suppress("UNCHECKED_CAST")
@@ -353,6 +360,7 @@ class WidgetPostAPIHandler @AssistedInject constructor(@Assisted private val roo
/**
* Update the bot power levels.
*
+ * @param widgetPostAPIMediator the post api mediator
* @param eventData the modular data
*/
private fun setBotPower(widgetPostAPIMediator: WidgetPostAPIMediator, eventData: JsonDict) {
@@ -375,6 +383,7 @@ class WidgetPostAPIHandler @AssistedInject constructor(@Assisted private val roo
/**
* Invite an user to this room.
*
+ * @param widgetPostAPIMediator the post api mediator
* @param eventData the modular data
*/
private fun inviteUser(widgetPostAPIMediator: WidgetPostAPIMediator, eventData: JsonDict) {
@@ -397,6 +406,7 @@ class WidgetPostAPIHandler @AssistedInject constructor(@Assisted private val roo
/**
* Provides the number of members in the rooms.
*
+ * @param widgetPostAPIMediator the post api mediator
* @param eventData the modular data
*/
private fun getMembershipCount(widgetPostAPIMediator: WidgetPostAPIMediator, eventData: JsonDict) {
diff --git a/vector/src/main/res/drawable/ic_email.xml b/vector/src/main/res/drawable/ic_email.xml
new file mode 100644
index 0000000000..48de7aec41
--- /dev/null
+++ b/vector/src/main/res/drawable/ic_email.xml
@@ -0,0 +1,10 @@
+
+
+
diff --git a/vector/src/main/res/layout/fragment_ftue_combined_login.xml b/vector/src/main/res/layout/fragment_ftue_combined_login.xml
new file mode 100644
index 0000000000..1b65056e9f
--- /dev/null
+++ b/vector/src/main/res/layout/fragment_ftue_combined_login.xml
@@ -0,0 +1,244 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/vector/src/main/res/layout/fragment_ftue_email_input.xml b/vector/src/main/res/layout/fragment_ftue_email_input.xml
new file mode 100644
index 0000000000..0cfcfea7cc
--- /dev/null
+++ b/vector/src/main/res/layout/fragment_ftue_email_input.xml
@@ -0,0 +1,131 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/vector/src/main/res/layout/fragment_ftue_wait_for_email_verification.xml b/vector/src/main/res/layout/fragment_ftue_wait_for_email_verification.xml
new file mode 100644
index 0000000000..15aaf4d1b2
--- /dev/null
+++ b/vector/src/main/res/layout/fragment_ftue_wait_for_email_verification.xml
@@ -0,0 +1,142 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/vector/src/main/res/layout/fragment_simple_container.xml b/vector/src/main/res/layout/fragment_simple_container.xml
new file mode 100644
index 0000000000..fb9e6d38c5
--- /dev/null
+++ b/vector/src/main/res/layout/fragment_simple_container.xml
@@ -0,0 +1,5 @@
+
+
diff --git a/vector/src/main/res/layout/view_read_receipts.xml b/vector/src/main/res/layout/view_read_receipts.xml
index 907f1ec0e3..11fdeb74d5 100644
--- a/vector/src/main/res/layout/view_read_receipts.xml
+++ b/vector/src/main/res/layout/view_read_receipts.xml
@@ -7,7 +7,7 @@
Join millions for free on the largest public serverEdit
+ Welcome back!
+
Choose your serverWhat is the address of your server? Server is like a home for all your data.Server URL
@@ -31,4 +33,13 @@
Privacy policyPlease read through T&C. You must accept in order to continue.
+ Enter your email address
+ This will help verify your account and enables password recovery.
+ Email Address
+
+ Check your email to verify.
+
+ To confirm your email address, tap the button in the email we just sent to %s
+ Did not receive an email?
+ Resend email
diff --git a/vector/src/main/res/values/strings.xml b/vector/src/main/res/values/strings.xml
index d13f0e51bb..63d4730dc5 100644
--- a/vector/src/main/res/values/strings.xml
+++ b/vector/src/main/res/values/strings.xml
@@ -3035,6 +3035,8 @@
%1$s left${app_name} Live LocationLocation sharing is in progress
+ Enable Live Location Sharing
+ Temporary implementation: locations persist in room historyShow Message bubbles
diff --git a/vector/src/main/res/xml/vector_settings_labs.xml b/vector/src/main/res/xml/vector_settings_labs.xml
index 8b25a8c287..19ee7e366f 100644
--- a/vector/src/main/res/xml/vector_settings_labs.xml
+++ b/vector/src/main/res/xml/vector_settings_labs.xml
@@ -69,4 +69,10 @@
android:key="SETTINGS_LABS_RENDER_LOCATIONS_IN_TIMELINE"
android:title="@string/labs_render_locations_in_timeline" />
+
+
diff --git a/vector/src/test/java/im/vector/app/features/onboarding/DirectLoginUseCaseTest.kt b/vector/src/test/java/im/vector/app/features/onboarding/DirectLoginUseCaseTest.kt
index 1fed03b601..d3600940ab 100644
--- a/vector/src/test/java/im/vector/app/features/onboarding/DirectLoginUseCaseTest.kt
+++ b/vector/src/test/java/im/vector/app/features/onboarding/DirectLoginUseCaseTest.kt
@@ -32,13 +32,13 @@ import org.matrix.android.sdk.api.auth.data.HomeServerConnectionConfig
import org.matrix.android.sdk.api.auth.data.WellKnown
import org.matrix.android.sdk.api.auth.wellknown.WellknownResult
-private val A_LOGIN_OR_REGISTER_ACTION = OnboardingAction.LoginOrRegister("@a-user:id.org", "a-password", "a-device-name")
+private val A_DIRECT_LOGIN_ACTION = OnboardingAction.AuthenticateAction.LoginDirect("@a-user:id.org", "a-password", "a-device-name")
private val A_WELLKNOWN_SUCCESS_RESULT = WellknownResult.Prompt("https://homeserverurl.com", identityServerUrl = null, WellKnown())
private val A_WELLKNOWN_FAILED_WITH_CONTENT_RESULT = WellknownResult.FailPrompt("https://homeserverurl.com", WellKnown())
private val A_WELLKNOWN_FAILED_WITHOUT_CONTENT_RESULT = WellknownResult.FailPrompt(null, null)
private val NO_HOMESERVER_CONFIG: HomeServerConnectionConfig? = null
private val A_FALLBACK_CONFIG: HomeServerConnectionConfig = HomeServerConnectionConfig(
- homeServerUri = FakeUri("https://${A_LOGIN_OR_REGISTER_ACTION.username.getServerName()}").instance,
+ homeServerUri = FakeUri("https://${A_DIRECT_LOGIN_ACTION.matrixId.getServerName()}").instance,
homeServerUriBase = FakeUri(A_WELLKNOWN_SUCCESS_RESULT.homeServerUrl).instance,
identityServerUri = null
)
@@ -54,11 +54,11 @@ class DirectLoginUseCaseTest {
@Test
fun `when logging in directly, then returns success with direct session result`() = runTest {
- fakeAuthenticationService.givenWellKnown(A_LOGIN_OR_REGISTER_ACTION.username, config = NO_HOMESERVER_CONFIG, result = A_WELLKNOWN_SUCCESS_RESULT)
- val (username, password, initialDeviceName) = A_LOGIN_OR_REGISTER_ACTION
+ fakeAuthenticationService.givenWellKnown(A_DIRECT_LOGIN_ACTION.matrixId, config = NO_HOMESERVER_CONFIG, result = A_WELLKNOWN_SUCCESS_RESULT)
+ val (username, password, initialDeviceName) = A_DIRECT_LOGIN_ACTION
fakeAuthenticationService.givenDirectAuthentication(A_FALLBACK_CONFIG, username, password, initialDeviceName, result = fakeSession)
- val result = useCase.execute(A_LOGIN_OR_REGISTER_ACTION, homeServerConnectionConfig = NO_HOMESERVER_CONFIG)
+ val result = useCase.execute(A_DIRECT_LOGIN_ACTION, homeServerConnectionConfig = NO_HOMESERVER_CONFIG)
result shouldBeEqualTo Result.success(fakeSession)
}
@@ -66,14 +66,14 @@ class DirectLoginUseCaseTest {
@Test
fun `given wellknown fails with content, when logging in directly, then returns success with direct session result`() = runTest {
fakeAuthenticationService.givenWellKnown(
- A_LOGIN_OR_REGISTER_ACTION.username,
+ A_DIRECT_LOGIN_ACTION.matrixId,
config = NO_HOMESERVER_CONFIG,
result = A_WELLKNOWN_FAILED_WITH_CONTENT_RESULT
)
- val (username, password, initialDeviceName) = A_LOGIN_OR_REGISTER_ACTION
+ val (username, password, initialDeviceName) = A_DIRECT_LOGIN_ACTION
fakeAuthenticationService.givenDirectAuthentication(A_FALLBACK_CONFIG, username, password, initialDeviceName, result = fakeSession)
- val result = useCase.execute(A_LOGIN_OR_REGISTER_ACTION, homeServerConnectionConfig = NO_HOMESERVER_CONFIG)
+ val result = useCase.execute(A_DIRECT_LOGIN_ACTION, homeServerConnectionConfig = NO_HOMESERVER_CONFIG)
result shouldBeEqualTo Result.success(fakeSession)
}
@@ -81,14 +81,14 @@ class DirectLoginUseCaseTest {
@Test
fun `given wellknown fails without content, when logging in directly, then returns well known error`() = runTest {
fakeAuthenticationService.givenWellKnown(
- A_LOGIN_OR_REGISTER_ACTION.username,
+ A_DIRECT_LOGIN_ACTION.matrixId,
config = NO_HOMESERVER_CONFIG,
result = A_WELLKNOWN_FAILED_WITHOUT_CONTENT_RESULT
)
- val (username, password, initialDeviceName) = A_LOGIN_OR_REGISTER_ACTION
+ val (username, password, initialDeviceName) = A_DIRECT_LOGIN_ACTION
fakeAuthenticationService.givenDirectAuthentication(A_FALLBACK_CONFIG, username, password, initialDeviceName, result = fakeSession)
- val result = useCase.execute(A_LOGIN_OR_REGISTER_ACTION, homeServerConnectionConfig = NO_HOMESERVER_CONFIG)
+ val result = useCase.execute(A_DIRECT_LOGIN_ACTION, homeServerConnectionConfig = NO_HOMESERVER_CONFIG)
result should { this.isFailure }
result should { this.exceptionOrNull() is Exception }
@@ -97,20 +97,20 @@ class DirectLoginUseCaseTest {
@Test
fun `given wellknown throws, when logging in directly, then returns failure result with original cause`() = runTest {
- fakeAuthenticationService.givenWellKnownThrows(A_LOGIN_OR_REGISTER_ACTION.username, config = NO_HOMESERVER_CONFIG, cause = AN_ERROR)
+ fakeAuthenticationService.givenWellKnownThrows(A_DIRECT_LOGIN_ACTION.matrixId, config = NO_HOMESERVER_CONFIG, cause = AN_ERROR)
- val result = useCase.execute(A_LOGIN_OR_REGISTER_ACTION, homeServerConnectionConfig = NO_HOMESERVER_CONFIG)
+ val result = useCase.execute(A_DIRECT_LOGIN_ACTION, homeServerConnectionConfig = NO_HOMESERVER_CONFIG)
result shouldBeEqualTo Result.failure(AN_ERROR)
}
@Test
fun `given direct authentication throws, when logging in directly, then returns failure result with original cause`() = runTest {
- fakeAuthenticationService.givenWellKnown(A_LOGIN_OR_REGISTER_ACTION.username, config = NO_HOMESERVER_CONFIG, result = A_WELLKNOWN_SUCCESS_RESULT)
- val (username, password, initialDeviceName) = A_LOGIN_OR_REGISTER_ACTION
+ fakeAuthenticationService.givenWellKnown(A_DIRECT_LOGIN_ACTION.matrixId, config = NO_HOMESERVER_CONFIG, result = A_WELLKNOWN_SUCCESS_RESULT)
+ val (username, password, initialDeviceName) = A_DIRECT_LOGIN_ACTION
fakeAuthenticationService.givenDirectAuthenticationThrows(A_FALLBACK_CONFIG, username, password, initialDeviceName, cause = AN_ERROR)
- val result = useCase.execute(A_LOGIN_OR_REGISTER_ACTION, homeServerConnectionConfig = NO_HOMESERVER_CONFIG)
+ val result = useCase.execute(A_DIRECT_LOGIN_ACTION, homeServerConnectionConfig = NO_HOMESERVER_CONFIG)
result shouldBeEqualTo Result.failure(AN_ERROR)
}
diff --git a/vector/src/test/java/im/vector/app/features/onboarding/OnboardingViewModelTest.kt b/vector/src/test/java/im/vector/app/features/onboarding/OnboardingViewModelTest.kt
index 59f6d4ea12..e4e687536c 100644
--- a/vector/src/test/java/im/vector/app/features/onboarding/OnboardingViewModelTest.kt
+++ b/vector/src/test/java/im/vector/app/features/onboarding/OnboardingViewModelTest.kt
@@ -46,8 +46,6 @@ import org.junit.Rule
import org.junit.Test
import org.matrix.android.sdk.api.auth.data.HomeServerConnectionConfig
import org.matrix.android.sdk.api.auth.registration.FlowResult
-import org.matrix.android.sdk.api.auth.registration.RegisterThreePid
-import org.matrix.android.sdk.api.auth.registration.RegistrationResult
import org.matrix.android.sdk.api.auth.registration.Stage
import org.matrix.android.sdk.api.session.Session
import org.matrix.android.sdk.api.session.homeserver.HomeServerCapabilities
@@ -57,11 +55,11 @@ private const val A_PICTURE_FILENAME = "a-picture.png"
private val AN_ERROR = RuntimeException("an error!")
private val A_LOADABLE_REGISTER_ACTION = RegisterAction.StartRegistration
private val A_NON_LOADABLE_REGISTER_ACTION = RegisterAction.CheckIfEmailHasBeenValidated(delayMillis = -1L)
-private val A_RESULT_IGNORED_REGISTER_ACTION = RegisterAction.AddThreePid(RegisterThreePid.Email("an email"))
+private val A_RESULT_IGNORED_REGISTER_ACTION = RegisterAction.SendAgainThreePid
private val A_HOMESERVER_CAPABILITIES = aHomeServerCapabilities(canChangeDisplayName = true, canChangeAvatar = true)
private val AN_IGNORED_FLOW_RESULT = FlowResult(missingStages = emptyList(), completedStages = emptyList())
-private val ANY_CONTINUING_REGISTRATION_RESULT = RegistrationResult.FlowResponse(AN_IGNORED_FLOW_RESULT)
-private val A_LOGIN_OR_REGISTER_ACTION = OnboardingAction.LoginOrRegister("@a-user:id.org", "a-password", "a-device-name")
+private val ANY_CONTINUING_REGISTRATION_RESULT = RegistrationResult.NextStep(AN_IGNORED_FLOW_RESULT)
+private val A_DIRECT_LOGIN = OnboardingAction.AuthenticateAction.LoginDirect("@a-user:id.org", "a-password", "a-device-name")
private const val A_HOMESERVER_URL = "https://edited-homeserver.org"
private val A_HOMESERVER_CONFIG = HomeServerConnectionConfig(FakeUri().instance)
private val SELECTED_HOMESERVER_STATE = SelectedHomeserverState(preferredLoginMode = LoginMode.Password)
@@ -144,11 +142,11 @@ class OnboardingViewModelTest {
@Test
fun `given has sign in with matrix id sign mode, when handling login or register action, then logs in directly`() = runTest {
viewModelWith(initialState.copy(signMode = SignMode.SignInWithMatrixId))
- fakeDirectLoginUseCase.givenSuccessResult(A_LOGIN_OR_REGISTER_ACTION, config = null, result = fakeSession)
+ fakeDirectLoginUseCase.givenSuccessResult(A_DIRECT_LOGIN, config = null, result = fakeSession)
givenInitialisesSession(fakeSession)
val test = viewModel.test()
- viewModel.handle(A_LOGIN_OR_REGISTER_ACTION)
+ viewModel.handle(A_DIRECT_LOGIN)
test
.assertStatesChanges(
@@ -163,11 +161,11 @@ class OnboardingViewModelTest {
@Test
fun `given has sign in with matrix id sign mode, when handling login or register action fails, then emits error`() = runTest {
viewModelWith(initialState.copy(signMode = SignMode.SignInWithMatrixId))
- fakeDirectLoginUseCase.givenFailureResult(A_LOGIN_OR_REGISTER_ACTION, config = null, cause = AN_ERROR)
+ fakeDirectLoginUseCase.givenFailureResult(A_DIRECT_LOGIN, config = null, cause = AN_ERROR)
givenInitialisesSession(fakeSession)
val test = viewModel.test()
- viewModel.handle(A_LOGIN_OR_REGISTER_ACTION)
+ viewModel.handle(A_DIRECT_LOGIN)
test
.assertStatesChanges(
@@ -230,7 +228,7 @@ class OnboardingViewModelTest {
@Test
fun `given register action ignores result, when handling action, then does nothing on success`() = runTest {
val test = viewModel.test()
- givenRegistrationResultFor(A_RESULT_IGNORED_REGISTER_ACTION, RegistrationResult.FlowResponse(AN_IGNORED_FLOW_RESULT))
+ givenRegistrationResultFor(A_RESULT_IGNORED_REGISTER_ACTION, RegistrationResult.NextStep(AN_IGNORED_FLOW_RESULT))
viewModel.handle(OnboardingAction.PostRegisterAction(A_RESULT_IGNORED_REGISTER_ACTION))
@@ -249,7 +247,7 @@ class OnboardingViewModelTest {
viewModelWith(initialState.copy(onboardingFlow = OnboardingFlow.SignUp))
fakeHomeServerConnectionConfigFactory.givenConfigFor(A_HOMESERVER_URL, A_HOMESERVER_CONFIG)
fakeStartAuthenticationFlowUseCase.givenResult(A_HOMESERVER_CONFIG, StartAuthenticationResult(isHomeserverOutdated = false, SELECTED_HOMESERVER_STATE))
- givenRegistrationResultFor(RegisterAction.StartRegistration, RegistrationResult.FlowResponse(AN_IGNORED_FLOW_RESULT))
+ givenRegistrationResultFor(RegisterAction.StartRegistration, RegistrationResult.NextStep(AN_IGNORED_FLOW_RESULT))
fakeHomeServerHistoryService.expectUrlToBeAdded(A_HOMESERVER_CONFIG.homeServerUri.toString())
val test = viewModel.test()
@@ -291,7 +289,7 @@ class OnboardingViewModelTest {
@Test
fun `given personalisation enabled, when registering account, then updates state and emits account created event`() = runTest {
fakeVectorFeatures.givenPersonalisationEnabled()
- givenRegistrationResultFor(A_LOADABLE_REGISTER_ACTION, RegistrationResult.Success(fakeSession))
+ givenRegistrationResultFor(A_LOADABLE_REGISTER_ACTION, RegistrationResult.Complete(fakeSession))
givenSuccessfullyCreatesAccount(A_HOMESERVER_CAPABILITIES)
val test = viewModel.test()
@@ -495,8 +493,8 @@ class OnboardingViewModelTest {
val flowResult = FlowResult(missingStages = missingStages, completedStages = emptyList())
givenRegistrationResultsFor(
listOf(
- A_LOADABLE_REGISTER_ACTION to RegistrationResult.FlowResponse(flowResult),
- RegisterAction.RegisterDummy to RegistrationResult.Success(fakeSession)
+ A_LOADABLE_REGISTER_ACTION to RegistrationResult.NextStep(flowResult),
+ RegisterAction.RegisterDummy to RegistrationResult.Complete(fakeSession)
)
)
givenSuccessfullyCreatesAccount(A_HOMESERVER_CAPABILITIES)
diff --git a/vector/src/test/java/im/vector/app/features/onboarding/RegistrationActionHandlerTest.kt b/vector/src/test/java/im/vector/app/features/onboarding/RegistrationActionHandlerTest.kt
index a7fa2a6331..f6d9317038 100644
--- a/vector/src/test/java/im/vector/app/features/onboarding/RegistrationActionHandlerTest.kt
+++ b/vector/src/test/java/im/vector/app/features/onboarding/RegistrationActionHandlerTest.kt
@@ -18,16 +18,19 @@ package im.vector.app.features.onboarding
import im.vector.app.test.fakes.FakeRegistrationWizard
import im.vector.app.test.fakes.FakeSession
+import im.vector.app.test.fixtures.a401ServerError
import io.mockk.coVerifyAll
import kotlinx.coroutines.test.runTest
import org.amshove.kluent.shouldBeEqualTo
import org.junit.Test
import org.matrix.android.sdk.api.auth.registration.RegisterThreePid
-import org.matrix.android.sdk.api.auth.registration.RegistrationResult
import org.matrix.android.sdk.api.auth.registration.RegistrationWizard
+import org.matrix.android.sdk.api.auth.registration.RegistrationResult as SdkResult
+private const val IGNORED_DELAY = 0L
+private val AN_ERROR = RuntimeException()
private val A_SESSION = FakeSession()
-private val AN_EXPECTED_RESULT = RegistrationResult.Success(A_SESSION)
+private val AN_EXPECTED_RESULT = RegistrationResult.Complete(A_SESSION)
private const val A_USERNAME = "a username"
private const val A_PASSWORD = "a password"
private const val AN_INITIAL_DEVICE_NAME = "a device name"
@@ -38,6 +41,9 @@ private val A_PID_TO_REGISTER = RegisterThreePid.Email("an email")
class RegistrationActionHandlerTest {
+ private val fakeRegistrationWizard = FakeRegistrationWizard()
+ private val registrationActionHandler = RegistrationActionHandler()
+
@Test
fun `when handling register action then delegates to wizard`() = runTest {
val cases = listOf(
@@ -57,9 +63,52 @@ class RegistrationActionHandlerTest {
cases.forEach { testSuccessfulActionDelegation(it) }
}
+ @Test
+ fun `given adding an email ThreePid fails with 401, when handling register action, then infer EmailSuccess`() = runTest {
+ fakeRegistrationWizard.givenAddEmailThreePidErrors(
+ cause = a401ServerError(),
+ email = A_PID_TO_REGISTER.email
+ )
+
+ val result = registrationActionHandler.handleRegisterAction(fakeRegistrationWizard, RegisterAction.AddThreePid(A_PID_TO_REGISTER))
+
+ result shouldBeEqualTo RegistrationResult.SendEmailSuccess(A_PID_TO_REGISTER.email)
+ }
+
+ @Test
+ fun `given email verification errors with 401 then fatal error, when checking email validation, then continues to poll until non 401 error`() = runTest {
+ val errorsToThrow = listOf(
+ a401ServerError(),
+ a401ServerError(),
+ a401ServerError(),
+ AN_ERROR
+ )
+ fakeRegistrationWizard.givenCheckIfEmailHasBeenValidatedErrors(errorsToThrow)
+
+ val result = registrationActionHandler.handleRegisterAction(fakeRegistrationWizard, RegisterAction.CheckIfEmailHasBeenValidated(IGNORED_DELAY))
+
+ fakeRegistrationWizard.verifyCheckedEmailedVerification(times = errorsToThrow.size)
+ result shouldBeEqualTo RegistrationResult.Error(AN_ERROR)
+ }
+
+ @Test
+ fun `given email verification errors with 401 and succeeds, when checking email validation, then continues to poll until success`() = runTest {
+ val errorsToThrow = listOf(
+ a401ServerError(),
+ a401ServerError(),
+ a401ServerError()
+ )
+ fakeRegistrationWizard.givenCheckIfEmailHasBeenValidatedErrors(errorsToThrow, finally = SdkResult.Success(A_SESSION))
+
+ val result = registrationActionHandler.handleRegisterAction(fakeRegistrationWizard, RegisterAction.CheckIfEmailHasBeenValidated(IGNORED_DELAY))
+
+ fakeRegistrationWizard.verifyCheckedEmailedVerification(times = errorsToThrow.size + 1)
+ result shouldBeEqualTo RegistrationResult.Complete(A_SESSION)
+ }
+
private suspend fun testSuccessfulActionDelegation(case: Case) {
- val registrationActionHandler = RegistrationActionHandler()
val fakeRegistrationWizard = FakeRegistrationWizard()
+ val registrationActionHandler = RegistrationActionHandler()
fakeRegistrationWizard.givenSuccessFor(result = A_SESSION, case.expect)
val result = registrationActionHandler.handleRegisterAction(fakeRegistrationWizard, case.action)
@@ -69,6 +118,6 @@ class RegistrationActionHandlerTest {
}
}
-private fun case(action: RegisterAction, expect: suspend RegistrationWizard.() -> RegistrationResult) = Case(action, expect)
+private fun case(action: RegisterAction, expect: suspend RegistrationWizard.() -> SdkResult) = Case(action, expect)
-private class Case(val action: RegisterAction, val expect: suspend RegistrationWizard.() -> RegistrationResult)
+private class Case(val action: RegisterAction, val expect: suspend RegistrationWizard.() -> SdkResult)
diff --git a/vector/src/test/java/im/vector/app/features/onboarding/ftueauth/FtueMissingRegistrationStagesComparatorTest.kt b/vector/src/test/java/im/vector/app/features/onboarding/ftueauth/MatrixOrgRegistrationStagesComparatorTest.kt
similarity index 92%
rename from vector/src/test/java/im/vector/app/features/onboarding/ftueauth/FtueMissingRegistrationStagesComparatorTest.kt
rename to vector/src/test/java/im/vector/app/features/onboarding/ftueauth/MatrixOrgRegistrationStagesComparatorTest.kt
index 010cf5de60..08be0ee058 100644
--- a/vector/src/test/java/im/vector/app/features/onboarding/ftueauth/FtueMissingRegistrationStagesComparatorTest.kt
+++ b/vector/src/test/java/im/vector/app/features/onboarding/ftueauth/MatrixOrgRegistrationStagesComparatorTest.kt
@@ -25,7 +25,7 @@ import im.vector.app.test.fixtures.anOtherStage
import org.amshove.kluent.shouldBeEqualTo
import org.junit.Test
-class FtueMissingRegistrationStagesComparatorTest {
+class MatrixOrgRegistrationStagesComparatorTest {
@Test
fun `when ordering stages, then prioritizes email`() {
@@ -38,7 +38,7 @@ class FtueMissingRegistrationStagesComparatorTest {
aTermsStage()
)
- val result = input.sortedWith(FtueMissingRegistrationStagesComparator())
+ val result = input.sortedWith(MatrixOrgRegistrationStagesComparator())
result shouldBeEqualTo listOf(
anEmailStage(),
diff --git a/vector/src/test/java/im/vector/app/test/fakes/FakeDirectLoginUseCase.kt b/vector/src/test/java/im/vector/app/test/fakes/FakeDirectLoginUseCase.kt
index 8a5c6b1cee..289c0a6159 100644
--- a/vector/src/test/java/im/vector/app/test/fakes/FakeDirectLoginUseCase.kt
+++ b/vector/src/test/java/im/vector/app/test/fakes/FakeDirectLoginUseCase.kt
@@ -17,7 +17,7 @@
package im.vector.app.test.fakes
import im.vector.app.features.onboarding.DirectLoginUseCase
-import im.vector.app.features.onboarding.OnboardingAction
+import im.vector.app.features.onboarding.OnboardingAction.AuthenticateAction
import io.mockk.coEvery
import io.mockk.mockk
import org.matrix.android.sdk.api.auth.data.HomeServerConnectionConfig
@@ -25,11 +25,11 @@ import org.matrix.android.sdk.api.auth.data.HomeServerConnectionConfig
class FakeDirectLoginUseCase {
val instance = mockk()
- fun givenSuccessResult(action: OnboardingAction.LoginOrRegister, config: HomeServerConnectionConfig?, result: FakeSession) {
+ fun givenSuccessResult(action: AuthenticateAction.LoginDirect, config: HomeServerConnectionConfig?, result: FakeSession) {
coEvery { instance.execute(action, config) } returns Result.success(result)
}
- fun givenFailureResult(action: OnboardingAction.LoginOrRegister, config: HomeServerConnectionConfig?, cause: Throwable) {
+ fun givenFailureResult(action: AuthenticateAction.LoginDirect, config: HomeServerConnectionConfig?, cause: Throwable) {
coEvery { instance.execute(action, config) } returns Result.failure(cause)
}
}
diff --git a/vector/src/test/java/im/vector/app/test/fakes/FakeRegisterActionHandler.kt b/vector/src/test/java/im/vector/app/test/fakes/FakeRegisterActionHandler.kt
index 61d0e438ab..f5824e5866 100644
--- a/vector/src/test/java/im/vector/app/test/fakes/FakeRegisterActionHandler.kt
+++ b/vector/src/test/java/im/vector/app/test/fakes/FakeRegisterActionHandler.kt
@@ -18,9 +18,9 @@ package im.vector.app.test.fakes
import im.vector.app.features.onboarding.RegisterAction
import im.vector.app.features.onboarding.RegistrationActionHandler
+import im.vector.app.features.onboarding.RegistrationResult
import io.mockk.coEvery
import io.mockk.mockk
-import org.matrix.android.sdk.api.auth.registration.RegistrationResult
import org.matrix.android.sdk.api.auth.registration.RegistrationWizard
class FakeRegisterActionHandler {
diff --git a/vector/src/test/java/im/vector/app/test/fakes/FakeRegistrationWizard.kt b/vector/src/test/java/im/vector/app/test/fakes/FakeRegistrationWizard.kt
index 4e6e511abb..0fc69ae995 100644
--- a/vector/src/test/java/im/vector/app/test/fakes/FakeRegistrationWizard.kt
+++ b/vector/src/test/java/im/vector/app/test/fakes/FakeRegistrationWizard.kt
@@ -17,7 +17,9 @@
package im.vector.app.test.fakes
import io.mockk.coEvery
+import io.mockk.coVerify
import io.mockk.mockk
+import org.matrix.android.sdk.api.auth.registration.RegisterThreePid
import org.matrix.android.sdk.api.auth.registration.RegistrationResult
import org.matrix.android.sdk.api.auth.registration.RegistrationWizard
import org.matrix.android.sdk.api.session.Session
@@ -27,4 +29,21 @@ class FakeRegistrationWizard : RegistrationWizard by mockk(relaxed = false) {
fun givenSuccessFor(result: Session, expect: suspend RegistrationWizard.() -> RegistrationResult) {
coEvery { expect(this@FakeRegistrationWizard) } returns RegistrationResult.Success(result)
}
+
+ fun givenAddEmailThreePidErrors(cause: Throwable, email: String) {
+ coEvery { addThreePid(RegisterThreePid.Email(email)) } throws cause
+ }
+
+ fun givenCheckIfEmailHasBeenValidatedErrors(errors: List, finally: RegistrationResult? = null) {
+ var index = 0
+ coEvery { checkIfEmailHasBeenValidated(any()) } answers {
+ val current = index
+ index++
+ errors.getOrNull(current)?.let { throw it } ?: finally ?: throw RuntimeException("Developer error")
+ }
+ }
+
+ fun verifyCheckedEmailedVerification(times: Int) {
+ coVerify(exactly = times) { checkIfEmailHasBeenValidated(any()) }
+ }
}
diff --git a/vector/src/test/java/im/vector/app/test/fixtures/FailureFixture.kt b/vector/src/test/java/im/vector/app/test/fixtures/FailureFixture.kt
new file mode 100644
index 0000000000..39c139c208
--- /dev/null
+++ b/vector/src/test/java/im/vector/app/test/fixtures/FailureFixture.kt
@@ -0,0 +1,25 @@
+/*
+ * Copyright (c) 2022 New Vector Ltd
+ *
+ * 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 im.vector.app.test.fixtures
+
+import org.matrix.android.sdk.api.failure.Failure
+import org.matrix.android.sdk.api.failure.MatrixError
+import javax.net.ssl.HttpsURLConnection
+
+fun a401ServerError() = Failure.ServerError(
+ MatrixError(MatrixError.M_UNAUTHORIZED, ""), HttpsURLConnection.HTTP_UNAUTHORIZED
+)