diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml
index 6ee85168af..1bb8366900 100644
--- a/.github/workflows/tests.yml
+++ b/.github/workflows/tests.yml
@@ -32,7 +32,7 @@ jobs:
- uses: actions/setup-java@v3
with:
distribution: 'adopt'
- java-version: '11'
+ java-version: '17'
- uses: gradle/gradle-build-action@v2
with:
cache-read-only: ${{ github.ref != 'refs/heads/develop' }}
diff --git a/CHANGES.md b/CHANGES.md
index 5a90fd3f01..e3e7f46744 100644
--- a/CHANGES.md
+++ b/CHANGES.md
@@ -1,3 +1,17 @@
+Changes in Element v1.6.18 (2024-06-25)
+=======================================
+
+Bugfixes 🐛
+----------
+ - Fix redacted events not grouped correctly when hidden events are inserted between. ([#8840](https://github.com/element-hq/element-android/issues/8840))
+ - Element-Android session doesn't encrypt for a dehydrated device ([#8842](https://github.com/element-hq/element-android/issues/8842))
+ - Intercept only links from `element.io` well known hosts. The previous behaviour broke OIDC login in Element X. ([#8894](https://github.com/element-hq/element-android/issues/8894))
+
+Other changes
+-------------
+ - Posthog | report platform code for EA ([#8839](https://github.com/element-hq/element-android/issues/8839))
+
+
Changes in Element v1.6.16 (2024-05-29)
=======================================
diff --git a/dependencies.gradle b/dependencies.gradle
index 7c9ca63536..1f5b905250 100644
--- a/dependencies.gradle
+++ b/dependencies.gradle
@@ -101,7 +101,7 @@ ext.libs = [
],
element : [
'opusencoder' : "io.element.android:opusencoder:1.1.0",
- 'wysiwyg' : "io.element.android:wysiwyg:2.35.0"
+ 'wysiwyg' : "io.element.android:wysiwyg:2.37.3"
],
squareup : [
'moshi' : "com.squareup.moshi:moshi:$moshi",
diff --git a/fastlane/metadata/android/en-US/changelogs/40106180.txt b/fastlane/metadata/android/en-US/changelogs/40106180.txt
new file mode 100644
index 0000000000..bc5a0f731a
--- /dev/null
+++ b/fastlane/metadata/android/en-US/changelogs/40106180.txt
@@ -0,0 +1,2 @@
+Main changes in this version: Bugfixes.
+Full changelog: https://github.com/element-hq/element-android/releases
diff --git a/matrix-sdk-android/build.gradle b/matrix-sdk-android/build.gradle
index 678aab3d1f..d86f71b8a1 100644
--- a/matrix-sdk-android/build.gradle
+++ b/matrix-sdk-android/build.gradle
@@ -62,7 +62,7 @@ android {
// that the app's state is completely cleared between tests.
testInstrumentationRunnerArguments clearPackageData: 'true'
- buildConfigField "String", "SDK_VERSION", "\"1.6.16\""
+ buildConfigField "String", "SDK_VERSION", "\"1.6.18\""
buildConfigField "String", "GIT_SDK_REVISION", "\"${gitRevision()}\""
buildConfigField "String", "GIT_SDK_REVISION_UNIX_DATE", "\"${gitRevisionUnixDate()}\""
diff --git a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/session/room/send/MarkdownParserTest.kt b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/session/room/send/MarkdownParserTest.kt
index 0560cfec95..7fe8a75dc9 100644
--- a/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/session/room/send/MarkdownParserTest.kt
+++ b/matrix-sdk-android/src/androidTest/java/org/matrix/android/sdk/internal/session/room/send/MarkdownParserTest.kt
@@ -26,10 +26,7 @@ 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.MatrixConfiguration
import org.matrix.android.sdk.api.util.TextContent
-import org.matrix.android.sdk.common.TestRoomDisplayNameFallbackProvider
-import org.matrix.android.sdk.internal.session.displayname.DisplayNameResolver
import org.matrix.android.sdk.internal.session.room.send.pills.MentionLinkSpecComparator
import org.matrix.android.sdk.internal.session.room.send.pills.TextPillsUtils
@@ -56,12 +53,6 @@ class MarkdownParserTest : InstrumentedTest {
HtmlRenderer.builder().softbreak("
").build(),
TextPillsUtils(
MentionLinkSpecComparator(),
- DisplayNameResolver(
- MatrixConfiguration(
- applicationFlavor = "TestFlavor",
- roomDisplayNameFallbackProvider = TestRoomDisplayNameFallbackProvider()
- )
- ),
TestPermalinkService()
)
)
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/Matrix.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/Matrix.kt
index 8893229a76..b9780b8021 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/Matrix.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/Matrix.kt
@@ -147,5 +147,12 @@ class Matrix(context: Context, matrixConfiguration: MatrixConfiguration) {
fun getSdkVersion(): String {
return BuildConfig.SDK_VERSION + " (" + BuildConfig.GIT_SDK_REVISION + ")"
}
+
+ fun getCryptoVersion(longFormat: Boolean): String {
+ val version = org.matrix.rustcomponents.sdk.crypto.version()
+ val gitHash = org.matrix.rustcomponents.sdk.crypto.versionInfo().gitSha
+ val vodozemac = org.matrix.rustcomponents.sdk.crypto.vodozemacVersion()
+ return if (longFormat) "Rust SDK $version ($gitHash), Vodozemac $vodozemac" else version
+ }
}
}
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/CryptoService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/CryptoService.kt
index 3ed6dd1450..fa1208059a 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/CryptoService.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/crypto/CryptoService.kt
@@ -16,7 +16,6 @@
package org.matrix.android.sdk.api.session.crypto
-import android.content.Context
import androidx.annotation.Size
import androidx.lifecycle.LiveData
import androidx.paging.PagedList
@@ -61,8 +60,6 @@ interface CryptoService {
suspend fun deleteDevices(@Size(min = 1) deviceIds: List, userInteractiveAuthInterceptor: UserInteractiveAuthInterceptor)
- fun getCryptoVersion(context: Context, longFormat: Boolean): String
-
fun isCryptoEnabled(): Boolean
fun isRoomBlacklistUnverifiedDevices(roomId: String?): Boolean
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/RustCryptoService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/RustCryptoService.kt
index 5ba74f705b..a6e4efd875 100755
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/RustCryptoService.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/RustCryptoService.kt
@@ -16,7 +16,6 @@
package org.matrix.android.sdk.internal.crypto
-import android.content.Context
import androidx.lifecycle.LiveData
import androidx.lifecycle.map
import androidx.paging.PagedList
@@ -184,13 +183,6 @@ internal class RustCryptoService @Inject constructor(
deleteDevices(listOf(deviceId), userInteractiveAuthInterceptor)
}
- override fun getCryptoVersion(context: Context, longFormat: Boolean): String {
- val version = org.matrix.rustcomponents.sdk.crypto.version()
- val gitHash = org.matrix.rustcomponents.sdk.crypto.versionInfo().gitSha
- val vodozemac = org.matrix.rustcomponents.sdk.crypto.vodozemacVersion()
- return if (longFormat) "Rust SDK $version ($gitHash), Vodozemac $vodozemac" else version
- }
-
override suspend fun getMyCryptoDevice(): CryptoDeviceInfo = withContext(coroutineDispatchers.io) {
olmMachine.ownDevice()
}
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/CryptoInfoMapper.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/CryptoInfoMapper.kt
index de9b3f24ff..119372ad32 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/CryptoInfoMapper.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/CryptoInfoMapper.kt
@@ -18,23 +18,10 @@ package org.matrix.android.sdk.internal.crypto.model
import org.matrix.android.sdk.api.session.crypto.crosssigning.CryptoCrossSigningKey
import org.matrix.android.sdk.api.session.crypto.model.CryptoDeviceInfo
import org.matrix.android.sdk.internal.crypto.model.rest.DeviceKeys
-import org.matrix.android.sdk.internal.crypto.model.rest.DeviceKeysWithUnsigned
import org.matrix.android.sdk.internal.crypto.model.rest.RestKeyInfo
internal object CryptoInfoMapper {
- fun map(deviceKeysWithUnsigned: DeviceKeysWithUnsigned): CryptoDeviceInfo {
- return CryptoDeviceInfo(
- deviceId = deviceKeysWithUnsigned.deviceId,
- userId = deviceKeysWithUnsigned.userId,
- algorithms = deviceKeysWithUnsigned.algorithms,
- keys = deviceKeysWithUnsigned.keys,
- signatures = deviceKeysWithUnsigned.signatures,
- unsigned = deviceKeysWithUnsigned.unsigned,
- trustLevel = null
- )
- }
-
fun map(cryptoDeviceInfo: CryptoDeviceInfo): DeviceKeys {
return DeviceKeys(
deviceId = cryptoDeviceInfo.deviceId,
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/rest/DeviceKeysWithUnsigned.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/rest/DeviceKeysWithUnsigned.kt
deleted file mode 100644
index 32f577c99b..0000000000
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/rest/DeviceKeysWithUnsigned.kt
+++ /dev/null
@@ -1,62 +0,0 @@
-/*
- * Copyright 2020 The Matrix.org Foundation C.I.C.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.matrix.android.sdk.internal.crypto.model.rest
-
-import com.squareup.moshi.Json
-import com.squareup.moshi.JsonClass
-import org.matrix.android.sdk.api.session.crypto.model.UnsignedDeviceInfo
-
-@JsonClass(generateAdapter = true)
-internal data class DeviceKeysWithUnsigned(
- /**
- * Required. The ID of the user the device belongs to. Must match the user ID used when logging in.
- */
- @Json(name = "user_id")
- val userId: String,
-
- /**
- * Required. The ID of the device these keys belong to. Must match the device ID used when logging in.
- */
- @Json(name = "device_id")
- val deviceId: String,
-
- /**
- * Required. The encryption algorithms supported by this device.
- */
- @Json(name = "algorithms")
- val algorithms: List?,
-
- /**
- * Required. Public identity keys. The names of the properties should be in the format :.
- * The keys themselves should be encoded as specified by the key algorithm.
- */
- @Json(name = "keys")
- val keys: Map?,
-
- /**
- * Required. Signatures for the device key object. A map from user ID, to a map from : to the signature.
- * The signature is calculated using the process described at https://matrix.org/docs/spec/appendices.html#signing-json.
- */
- @Json(name = "signatures")
- val signatures: Map>?,
-
- /**
- * Additional data added to the device key information by intermediate servers, and not covered by the signatures.
- */
- @Json(name = "unsigned")
- val unsigned: UnsignedDeviceInfo? = null
-)
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/rest/KeysQueryResponse.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/rest/KeysQueryResponse.kt
index a099419c3c..ce10af8c67 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/rest/KeysQueryResponse.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/model/rest/KeysQueryResponse.kt
@@ -34,7 +34,7 @@ internal data class KeysQueryResponse(
* For each device, the information returned will be the same as uploaded via /keys/upload, with the addition of an unsigned property.
*/
@Json(name = "device_keys")
- val deviceKeys: Map>? = null,
+ val deviceKeys: Map>>? = null,
/**
* If any remote homeservers could not be reached, they are recorded here. The names of the
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/tasks/DownloadKeysForUsersTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/tasks/DownloadKeysForUsersTask.kt
index 70af859ddb..8fd943afcc 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/tasks/DownloadKeysForUsersTask.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/tasks/DownloadKeysForUsersTask.kt
@@ -22,7 +22,6 @@ import kotlinx.coroutines.joinAll
import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock
import org.matrix.android.sdk.internal.crypto.api.CryptoApi
-import org.matrix.android.sdk.internal.crypto.model.rest.DeviceKeysWithUnsigned
import org.matrix.android.sdk.internal.crypto.model.rest.KeysQueryBody
import org.matrix.android.sdk.internal.crypto.model.rest.KeysQueryResponse
import org.matrix.android.sdk.internal.crypto.model.rest.RestKeyInfo
@@ -52,7 +51,7 @@ internal class DefaultDownloadKeysForUsers @Inject constructor(
return if (bestChunkSize.shouldChunk()) {
// Store server results in these mutable maps
- val deviceKeys = mutableMapOf>()
+ val deviceKeys = mutableMapOf>>()
val failures = mutableMapOf>()
val masterKeys = mutableMapOf()
val selfSigningKeys = mutableMapOf()
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/pills/TextPillsUtils.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/pills/TextPillsUtils.kt
index 38bddae951..4ac0b4d674 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/pills/TextPillsUtils.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/pills/TextPillsUtils.kt
@@ -19,7 +19,6 @@ import android.text.SpannableString
import org.matrix.android.sdk.api.session.permalinks.PermalinkService
import org.matrix.android.sdk.api.session.room.send.MatrixItemSpan
import org.matrix.android.sdk.api.util.MatrixItem
-import org.matrix.android.sdk.internal.session.displayname.DisplayNameResolver
import java.util.Collections
import javax.inject.Inject
@@ -29,7 +28,6 @@ import javax.inject.Inject
*/
internal class TextPillsUtils @Inject constructor(
private val mentionLinkSpecComparator: MentionLinkSpecComparator,
- private val displayNameResolver: DisplayNameResolver,
private val permalinkService: PermalinkService
) {
@@ -70,7 +68,7 @@ internal class TextPillsUtils @Inject constructor(
// append text before pill
append(text, currIndex, start)
// append the pill
- append(String.format(template, urlSpan.matrixItem.id, displayNameResolver.getBestName(urlSpan.matrixItem)))
+ append(String.format(template, urlSpan.matrixItem.id, urlSpan.matrixItem.id))
currIndex = end
}
// append text after the last pill
diff --git a/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/crypto/KeysQueryResponseTest.kt b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/crypto/KeysQueryResponseTest.kt
new file mode 100644
index 0000000000..d4bf2b9e70
--- /dev/null
+++ b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/crypto/KeysQueryResponseTest.kt
@@ -0,0 +1,103 @@
+/*
+ * 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 org.amshove.kluent.internal.assertEquals
+import org.junit.Test
+import org.matrix.android.sdk.internal.crypto.model.rest.KeysQueryResponse
+import org.matrix.android.sdk.internal.di.MoshiProvider
+
+class KeysQueryResponseTest {
+
+ private val moshi = MoshiProvider.providesMoshi()
+ private val keysQueryResponseAdapter = moshi.adapter(KeysQueryResponse::class.java)
+
+ private fun aKwysQueryResponseWithDehydrated(): KeysQueryResponse {
+ val rawResponseWithDehydratedDevice = """
+ {
+ "device_keys": {
+ "@dehydration2:localhost": {
+ "TDHZGMDVNO": {
+ "algorithms": [
+ "m.olm.v1.curve25519-aes-sha2",
+ "m.megolm.v1.aes-sha2"
+ ],
+ "device_id": "TDHZGMDVNO",
+ "keys": {
+ "curve25519:TDHZGMDVNO": "ClMOrHlQJqaqr4oESYyPURwD4BSQxMlEZZk/AnYxVSk",
+ "ed25519:TDHZGMDVNO": "5iZ4zfk0URyIH8YOIWnXmJo41Vn34IixGYphkMdDzik"
+ },
+ "signatures": {
+ "@dehydration2:localhost": {
+ "ed25519:TDHZGMDVNO": "O6VP+ELiCVAJGHaRdReKga0LGMQahjRnp4znZH7iJO6maZV8aSXnpugSoVsSPRvQ4GBkjX+KXAXU+ODZ0J8MDg",
+ "ed25519:YZ0EmlbDX+t/m/MB5EWkQLw8cEDg7hX4Zy9699h3hd8": "lG3idYliFGOAe4F/7tENIQ6qI0d41VQKY34BHyVvvWKbv63zDDO5kBTwBeXfUSEeRqyxET3SXLXfB1D8E8LUDg"
+ }
+ },
+ "user_id": "@dehydration2:localhost",
+ "unsigned": {
+ "device_display_name": "localhost:8080: Chrome on macOS"
+ }
+ },
+ "Y2gISVBZ024gKKAe6Xos44cDbNlO/49YjaOyiqFwjyQ": {
+ "algorithms": [
+ "m.olm.v1.curve25519-aes-sha2",
+ "m.megolm.v1.aes-sha2"
+ ],
+ "dehydrated": true,
+ "device_id": "Y2gISVBZ024gKKAe6Xos44cDbNlO/49YjaOyiqFwjyQ",
+ "keys": {
+ "curve25519:Y2gISVBZ024gKKAe6Xos44cDbNlO/49YjaOyiqFwjyQ": "Y2gISVBZ024gKKAe6Xos44cDbNlO/49YjaOyiqFwjyQ",
+ "ed25519:Y2gISVBZ024gKKAe6Xos44cDbNlO/49YjaOyiqFwjyQ": "sVY5Xq13sIdhC4We/p5CH69++GsIWRNUhHijtucBirs"
+ },
+ "signatures": {
+ "@dehydration2:localhost": {
+ "ed25519:Y2gISVBZ024gKKAe6Xos44cDbNlO/49YjaOyiqFwjyQ": "e2aVrdnD/kor2T0Ok/4SC32MW4WB5JXFSd2wnXV8apxFJBfbdZErANiUbo1Zz/HAasaXM5NBfkr/9gVTdph9BQ",
+ "ed25519:YZ0EmlbDX+t/m/MB5EWkQLw8cEDg7hX4Zy9699h3hd8": "rVzeE1LbB12XOlckxjRLjt3eq2jVlek6OJ4p08+8g8CMoiJDcw1OVzbJuG/8u6ryarxQF6Yqr4Xu2TqCPBmHDw"
+ }
+ },
+ "user_id": "@dehydration2:localhost",
+ "unsigned": {
+ "device_display_name": "Dehydrated device"
+ }
+ }
+ }
+ }
+ }
+ """.trimIndent()
+
+ return keysQueryResponseAdapter.fromJson(rawResponseWithDehydratedDevice)!!
+ }
+
+ @Test
+ fun `Should parse correctly devices with new dehydrated field`() {
+ val aKeysQueryResponse = aKwysQueryResponseWithDehydrated()
+
+ val pojoToJson = keysQueryResponseAdapter.toJson(aKeysQueryResponse)
+
+ val rawAdapter = moshi.adapter(Map::class.java)
+
+ val rawJson = rawAdapter.fromJson(pojoToJson)!!
+
+ val deviceKeys = (rawJson["device_keys"] as Map<*, *>)["@dehydration2:localhost"] as Map<*, *>
+
+ assertEquals(deviceKeys.keys.size, 2)
+
+ val dehydratedDevice = deviceKeys["Y2gISVBZ024gKKAe6Xos44cDbNlO/49YjaOyiqFwjyQ"] as Map<*, *>
+
+ assertEquals(dehydratedDevice["dehydrated"] as? Boolean, true)
+ }
+}
diff --git a/tools/release/releaseScript.sh b/tools/release/releaseScript.sh
index f31899551e..92ac7c1a7b 100755
--- a/tools/release/releaseScript.sh
+++ b/tools/release/releaseScript.sh
@@ -66,7 +66,7 @@ if [ ${envError} == 1 ]; then
exit 1
fi
-buildToolsVersion="30.0.2"
+buildToolsVersion="35.0.0"
buildToolsPath="${androidHome}/build-tools/${buildToolsVersion}"
if [[ ! -d ${buildToolsPath} ]]; then
diff --git a/vector-app/build.gradle b/vector-app/build.gradle
index 062b1aeb63..829bc5f303 100644
--- a/vector-app/build.gradle
+++ b/vector-app/build.gradle
@@ -37,7 +37,7 @@ ext.versionMinor = 6
// Note: even values are reserved for regular release, odd values for hotfix release.
// When creating a hotfix, you should decrease the value, since the current value
// is the value for the next regular release.
-ext.versionPatch = 16
+ext.versionPatch = 18
static def getGitTimestamp() {
def cmd = 'git show -s --format=%ct'
diff --git a/vector-app/src/main/java/im/vector/app/VectorApplication.kt b/vector-app/src/main/java/im/vector/app/VectorApplication.kt
index fe4cc3311b..df1c584b3a 100644
--- a/vector-app/src/main/java/im/vector/app/VectorApplication.kt
+++ b/vector-app/src/main/java/im/vector/app/VectorApplication.kt
@@ -53,6 +53,7 @@ import im.vector.app.core.pushers.FcmHelper
import im.vector.app.core.resources.BuildMeta
import im.vector.app.features.analytics.DecryptionFailureTracker
import im.vector.app.features.analytics.VectorAnalytics
+import im.vector.app.features.analytics.plan.SuperProperties
import im.vector.app.features.call.webrtc.WebRtcCallManager
import im.vector.app.features.configuration.VectorConfiguration
import im.vector.app.features.invite.InvitesAcceptor
@@ -130,6 +131,13 @@ class VectorApplication :
appContext = this
flipperProxy.init(matrix)
vectorAnalytics.init()
+ vectorAnalytics.updateSuperProperties(
+ SuperProperties(
+ appPlatform = SuperProperties.AppPlatform.EA,
+ cryptoSDK = SuperProperties.CryptoSDK.Rust,
+ cryptoSDKVersion = Matrix.getCryptoVersion(longFormat = false)
+ )
+ )
invitesAcceptor.initialize()
autoRageShaker.initialize()
decryptionFailureTracker.start()
diff --git a/vector/build.gradle b/vector/build.gradle
index 368fa217db..dc8eb0641e 100644
--- a/vector/build.gradle
+++ b/vector/build.gradle
@@ -160,7 +160,7 @@ dependencies {
api 'com.facebook.stetho:stetho:1.6.0'
// Analytics
- api 'com.github.matrix-org:matrix-analytics-events:0.15.0'
+ api 'com.github.matrix-org:matrix-analytics-events:0.23.0'
api libs.google.phonenumber
diff --git a/vector/src/main/AndroidManifest.xml b/vector/src/main/AndroidManifest.xml
index c5d7af2408..1b9bfaf802 100644
--- a/vector/src/main/AndroidManifest.xml
+++ b/vector/src/main/AndroidManifest.xml
@@ -184,7 +184,13 @@
-
+
+
+
+
+
+
+
diff --git a/vector/src/main/java/im/vector/app/core/di/ActiveSessionHolder.kt b/vector/src/main/java/im/vector/app/core/di/ActiveSessionHolder.kt
index 5523c84994..f3d775b39f 100644
--- a/vector/src/main/java/im/vector/app/core/di/ActiveSessionHolder.kt
+++ b/vector/src/main/java/im/vector/app/core/di/ActiveSessionHolder.kt
@@ -22,7 +22,6 @@ import im.vector.app.core.dispatchers.CoroutineDispatchers
import im.vector.app.core.pushers.UnregisterUnifiedPushUseCase
import im.vector.app.core.services.GuardServiceStarter
import im.vector.app.core.session.ConfigureAndStartSessionUseCase
-import im.vector.app.features.analytics.DecryptionFailureTracker
import im.vector.app.features.call.webrtc.WebRtcCallManager
import im.vector.app.features.crypto.keysrequest.KeyRequestHandler
import im.vector.app.features.crypto.verification.IncomingVerificationRequestHandler
@@ -57,7 +56,6 @@ class ActiveSessionHolder @Inject constructor(
private val unregisterUnifiedPushUseCase: UnregisterUnifiedPushUseCase,
private val applicationCoroutineScope: CoroutineScope,
private val coroutineDispatchers: CoroutineDispatchers,
- private val decryptionFailureTracker: DecryptionFailureTracker,
) {
private var activeSessionReference: AtomicReference = AtomicReference()
diff --git a/vector/src/main/java/im/vector/app/features/analytics/AnalyticsTracker.kt b/vector/src/main/java/im/vector/app/features/analytics/AnalyticsTracker.kt
index 871782e473..d233900d2c 100644
--- a/vector/src/main/java/im/vector/app/features/analytics/AnalyticsTracker.kt
+++ b/vector/src/main/java/im/vector/app/features/analytics/AnalyticsTracker.kt
@@ -18,6 +18,7 @@ package im.vector.app.features.analytics
import im.vector.app.features.analytics.itf.VectorAnalyticsEvent
import im.vector.app.features.analytics.itf.VectorAnalyticsScreen
+import im.vector.app.features.analytics.plan.SuperProperties
import im.vector.app.features.analytics.plan.UserProperties
interface AnalyticsTracker {
@@ -35,4 +36,10 @@ interface AnalyticsTracker {
* Update user specific properties.
*/
fun updateUserProperties(userProperties: UserProperties)
+
+ /**
+ * Update the super properties.
+ * Super properties are added to any tracked event automatically.
+ */
+ fun updateSuperProperties(updatedProperties: SuperProperties)
}
diff --git a/vector/src/main/java/im/vector/app/features/analytics/impl/DefaultVectorAnalytics.kt b/vector/src/main/java/im/vector/app/features/analytics/impl/DefaultVectorAnalytics.kt
index 8520a40ca2..a7df8c54a8 100644
--- a/vector/src/main/java/im/vector/app/features/analytics/impl/DefaultVectorAnalytics.kt
+++ b/vector/src/main/java/im/vector/app/features/analytics/impl/DefaultVectorAnalytics.kt
@@ -23,6 +23,7 @@ import im.vector.app.features.analytics.VectorAnalytics
import im.vector.app.features.analytics.itf.VectorAnalyticsEvent
import im.vector.app.features.analytics.itf.VectorAnalyticsScreen
import im.vector.app.features.analytics.log.analyticsTag
+import im.vector.app.features.analytics.plan.SuperProperties
import im.vector.app.features.analytics.plan.UserProperties
import im.vector.app.features.analytics.store.AnalyticsStore
import kotlinx.coroutines.CoroutineScope
@@ -63,6 +64,8 @@ class DefaultVectorAnalytics @Inject constructor(
// Cache for the properties to send
private var pendingUserProperties: UserProperties? = null
+ private var superProperties: SuperProperties? = null
+
override fun init() {
observeUserConsent()
observeAnalyticsId()
@@ -168,20 +171,14 @@ class DefaultVectorAnalytics @Inject constructor(
override fun capture(event: VectorAnalyticsEvent) {
Timber.tag(analyticsTag.value).d("capture($event)")
- posthog
- ?.takeIf { userConsent == true }
- ?.capture(
- event.getName(),
- analyticsId,
- event.getProperties()?.toPostHogProperties()
+ posthog?.takeIf { userConsent == true }?.capture(
+ event.getName(), analyticsId, event.getProperties()?.toPostHogProperties().orEmpty().withSuperProperties()
)
}
override fun screen(screen: VectorAnalyticsScreen) {
Timber.tag(analyticsTag.value).d("screen($screen)")
- posthog
- ?.takeIf { userConsent == true }
- ?.screen(screen.getName(), screen.getProperties()?.toPostHogProperties())
+ posthog?.takeIf { userConsent == true }?.screen(screen.getName(), screen.getProperties()?.toPostHogProperties().orEmpty().withSuperProperties())
}
override fun updateUserProperties(userProperties: UserProperties) {
@@ -195,9 +192,7 @@ class DefaultVectorAnalytics @Inject constructor(
private fun doUpdateUserProperties(userProperties: UserProperties) {
// we need a distinct id to set user properties
val distinctId = analyticsId ?: return
- posthog
- ?.takeIf { userConsent == true }
- ?.identify(distinctId, userProperties.getProperties())
+ posthog?.takeIf { userConsent == true }?.identify(distinctId, userProperties.getProperties())
}
private fun Map?.toPostHogProperties(): Map? {
@@ -226,9 +221,32 @@ class DefaultVectorAnalytics @Inject constructor(
return nonNulls
}
+ /**
+ * Adds super properties to the actual property set.
+ * If a property of the same name is already on the reported event it will not be overwritten.
+ */
+ private fun Map.withSuperProperties(): Map? {
+ val withSuperProperties = this.toMutableMap()
+ val superProperties = this@DefaultVectorAnalytics.superProperties?.getProperties()
+ superProperties?.forEach {
+ if (!withSuperProperties.containsKey(it.key)) {
+ withSuperProperties[it.key] = it.value
+ }
+ }
+ return withSuperProperties.takeIf { it.isEmpty().not() }
+ }
+
override fun trackError(throwable: Throwable) {
sentryAnalytics
.takeIf { userConsent == true }
?.trackError(throwable)
}
+
+ override fun updateSuperProperties(updatedProperties: SuperProperties) {
+ this.superProperties = SuperProperties(
+ cryptoSDK = updatedProperties.cryptoSDK ?: this.superProperties?.cryptoSDK,
+ appPlatform = updatedProperties.appPlatform ?: this.superProperties?.appPlatform,
+ cryptoSDKVersion = updatedProperties.cryptoSDKVersion ?: superProperties?.cryptoSDKVersion
+ )
+ }
}
diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/AutoCompleter.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/AutoCompleter.kt
index daf401efc3..e78ce0d551 100644
--- a/vector/src/main/java/im/vector/app/features/home/room/detail/AutoCompleter.kt
+++ b/vector/src/main/java/im/vector/app/features/home/room/detail/AutoCompleter.kt
@@ -246,10 +246,10 @@ class AutoCompleter @AssistedInject constructor(
val linkText = when (matrixItem) {
is MatrixItem.RoomAliasItem,
is MatrixItem.RoomItem,
- is MatrixItem.SpaceItem ->
+ is MatrixItem.SpaceItem,
+ is MatrixItem.UserItem ->
matrixItem.id
is MatrixItem.EveryoneInRoomItem,
- is MatrixItem.UserItem,
is MatrixItem.EventItem ->
matrixItem.getBestName()
}
diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/composer/MessageComposerFragment.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/composer/MessageComposerFragment.kt
index 038f009d22..53770aada9 100644
--- a/vector/src/main/java/im/vector/app/features/home/room/detail/composer/MessageComposerFragment.kt
+++ b/vector/src/main/java/im/vector/app/features/home/room/detail/composer/MessageComposerFragment.kt
@@ -796,14 +796,14 @@ class MessageComposerFragment : VectorBaseFragment(), A
composer.editText.setSelection(Command.EMOTE.command.length + 1)
} else {
val roomMember = timelineViewModel.getMember(userId)
- val displayName = sanitizeDisplayName(roomMember?.displayName ?: userId)
if ((composer as? RichTextComposerLayout)?.isTextFormattingEnabled == true) {
// Rich text editor is enabled so we need to use its APIs
permalinkService.createPermalink(userId)?.let { url ->
- (composer as RichTextComposerLayout).insertMention(url, displayName)
+ (composer as RichTextComposerLayout).insertMention(url, userId)
composer.editText.append(" ")
}
} else {
+ val displayName = sanitizeDisplayName(roomMember?.displayName ?: userId)
val pill = buildSpannedString {
append(displayName)
setSpan(
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 2149857ec2..b9457b9dc1 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
@@ -84,7 +84,7 @@ class MergedHeaderItemFactory @Inject constructor(
buildRoomCreationMergedSummary(currentPosition, items, partialState, event, eventIdToHighlight, requestModelBuild, callback)
isStartOfSameTypeEventsSummary(event, nextEvent, addDaySeparator) ->
buildSameTypeEventsMergedSummary(currentPosition, items, partialState, event, eventIdToHighlight, requestModelBuild, callback)
- isStartOfRedactedEventsSummary(event, items, currentPosition, addDaySeparator) ->
+ isStartOfRedactedEventsSummary(event, items, currentPosition, partialState, addDaySeparator) ->
buildRedactedEventsMergedSummary(currentPosition, items, partialState, event, eventIdToHighlight, requestModelBuild, callback)
else -> null
}
@@ -122,19 +122,25 @@ class MergedHeaderItemFactory @Inject constructor(
* @param event the main timeline event
* @param items all known items, sorted from newer event to oldest event
* @param currentPosition the current position
+ * @param partialState partial state data
* @param addDaySeparator true to add a day separator
*/
private fun isStartOfRedactedEventsSummary(
event: TimelineEvent,
items: List,
currentPosition: Int,
+ partialState: TimelineEventController.PartialState,
addDaySeparator: Boolean,
): Boolean {
- val nextNonRedactionEvent = items
- .subList(fromIndex = currentPosition + 1, toIndex = items.size)
- .find { it.root.getClearType() != EventType.REDACTION }
- return event.root.isRedacted() &&
- (!nextNonRedactionEvent?.root?.isRedacted().orFalse() || addDaySeparator)
+ val nextDisplayableEvent = items.subList(currentPosition + 1, items.size).firstOrNull {
+ timelineEventVisibilityHelper.shouldShowEvent(
+ timelineEvent = it,
+ highlightedEventId = partialState.highlightedEventId,
+ isFromThreadTimeline = partialState.isFromThreadTimeline(),
+ rootThreadEventId = partialState.rootThreadEventId
+ )
+ }
+ return event.root.isRedacted() && (nextDisplayableEvent?.root?.isRedacted() == false || addDaySeparator)
}
private fun buildSameTypeEventsMergedSummary(
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 703a5cb911..09c22aa771 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
@@ -151,16 +151,20 @@ class TimelineEventVisibilityHelper @Inject constructor(
rootThreadEventId: String?,
isFromThreadTimeline: Boolean
): List {
- val prevSub = timelineEvents
- .subList(0, index + 1)
- // Ensure to not take the REDACTION events into account
- .filter { it.root.getClearType() != EventType.REDACTION }
- return prevSub
+ val prevDisplayableEvents = timelineEvents.subList(0, index + 1)
+ .filter {
+ shouldShowEvent(
+ timelineEvent = it,
+ highlightedEventId = eventIdToHighlight,
+ isFromThreadTimeline = isFromThreadTimeline,
+ rootThreadEventId = rootThreadEventId)
+ }
+ return prevDisplayableEvents
.reversed()
.let {
nextEventsUntil(it, 0, minSize, eventIdToHighlight, rootThreadEventId, isFromThreadTimeline, object : PredicateToStopSearch {
override fun shouldStopSearch(oldEvent: Event, newEvent: Event): Boolean {
- return oldEvent.isRedacted() && !newEvent.isRedacted()
+ return !newEvent.isRedacted()
}
})
}
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 f1b01b2909..611c210f39 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
@@ -265,7 +265,7 @@ class BugReporter @Inject constructor(
activeSessionHolder.getSafeActiveSession()?.let { session ->
userId = session.myUserId
deviceId = session.sessionParams.deviceId
- olmVersion = session.cryptoService().getCryptoVersion(context, true)
+ olmVersion = Matrix.getCryptoVersion(true)
}
if (!mIsCancelled) {
diff --git a/vector/src/main/java/im/vector/app/features/settings/VectorSettingsHelpAboutFragment.kt b/vector/src/main/java/im/vector/app/features/settings/VectorSettingsHelpAboutFragment.kt
index 7777602166..359cc041f8 100644
--- a/vector/src/main/java/im/vector/app/features/settings/VectorSettingsHelpAboutFragment.kt
+++ b/vector/src/main/java/im/vector/app/features/settings/VectorSettingsHelpAboutFragment.kt
@@ -96,7 +96,7 @@ class VectorSettingsHelpAboutFragment :
// olm version
findPreference(VectorPreferences.SETTINGS_CRYPTO_VERSION_PREFERENCE_KEY)!!
- .summary = session.cryptoService().getCryptoVersion(requireContext(), true)
+ .summary = Matrix.getCryptoVersion(true)
}
companion object {
diff --git a/vector/src/test/java/im/vector/app/features/analytics/impl/DefaultVectorAnalyticsTest.kt b/vector/src/test/java/im/vector/app/features/analytics/impl/DefaultVectorAnalyticsTest.kt
index ea8beaa86c..72ff56bc27 100644
--- a/vector/src/test/java/im/vector/app/features/analytics/impl/DefaultVectorAnalyticsTest.kt
+++ b/vector/src/test/java/im/vector/app/features/analytics/impl/DefaultVectorAnalyticsTest.kt
@@ -16,6 +16,7 @@
package im.vector.app.features.analytics.impl
+import im.vector.app.features.analytics.plan.SuperProperties
import im.vector.app.test.fakes.FakeAnalyticsStore
import im.vector.app.test.fakes.FakeLateInitUserPropertiesFactory
import im.vector.app.test.fakes.FakePostHog
@@ -51,7 +52,7 @@ class DefaultVectorAnalyticsTest {
analyticsStore = fakeAnalyticsStore.instance,
globalScope = CoroutineScope(Dispatchers.Unconfined),
analyticsConfig = anAnalyticsConfig(isEnabled = true),
- lateInitUserPropertiesFactory = fakeLateInitUserPropertiesFactory.instance
+ lateInitUserPropertiesFactory = fakeLateInitUserPropertiesFactory.instance,
)
@Before
@@ -174,6 +175,117 @@ class DefaultVectorAnalyticsTest {
fakeSentryAnalytics.verifyNoErrorTracking()
}
+ @Test
+ fun `Super properties should be added to all captured events`() = runTest {
+ fakeAnalyticsStore.givenUserContent(consent = true)
+
+ val updatedProperties = SuperProperties(
+ appPlatform = SuperProperties.AppPlatform.EA,
+ cryptoSDKVersion = "0.0",
+ cryptoSDK = SuperProperties.CryptoSDK.Rust
+ )
+
+ defaultVectorAnalytics.updateSuperProperties(updatedProperties)
+
+ val fakeEvent = aVectorAnalyticsEvent("THE_NAME", mutableMapOf("foo" to "bar"))
+ defaultVectorAnalytics.capture(fakeEvent)
+
+ fakePostHog.verifyEventTracked(
+ "THE_NAME",
+ fakeEvent.getProperties().clearNulls()?.toMutableMap()?.apply {
+ updatedProperties.getProperties()?.let { putAll(it) }
+ }
+ )
+
+ // Check with a screen event
+ val fakeScreen = aVectorAnalyticsScreen("Screen", mutableMapOf("foo" to "bar"))
+ defaultVectorAnalytics.screen(fakeScreen)
+
+ fakePostHog.verifyScreenTracked(
+ "Screen",
+ fakeScreen.getProperties().clearNulls()?.toMutableMap()?.apply {
+ updatedProperties.getProperties()?.let { putAll(it) }
+ }
+ )
+ }
+
+ @Test
+ fun `Super properties can be updated`() = runTest {
+ fakeAnalyticsStore.givenUserContent(consent = true)
+
+ val superProperties = SuperProperties(
+ appPlatform = SuperProperties.AppPlatform.EA,
+ cryptoSDKVersion = "0.0",
+ cryptoSDK = SuperProperties.CryptoSDK.Rust
+ )
+
+ defaultVectorAnalytics.updateSuperProperties(superProperties)
+
+ val fakeEvent = aVectorAnalyticsEvent("THE_NAME", mutableMapOf("foo" to "bar"))
+ defaultVectorAnalytics.capture(fakeEvent)
+
+ fakePostHog.verifyEventTracked(
+ "THE_NAME",
+ fakeEvent.getProperties().clearNulls()?.toMutableMap()?.apply {
+ superProperties.getProperties()?.let { putAll(it) }
+ }
+ )
+
+ val superPropertiesUpdate = superProperties.copy(cryptoSDKVersion = "1.0")
+ defaultVectorAnalytics.updateSuperProperties(superPropertiesUpdate)
+
+ defaultVectorAnalytics.capture(fakeEvent)
+
+ fakePostHog.verifyEventTracked(
+ "THE_NAME",
+ fakeEvent.getProperties().clearNulls()?.toMutableMap()?.apply {
+ superPropertiesUpdate.getProperties()?.let { putAll(it) }
+ }
+ )
+ }
+
+ @Test
+ fun `Super properties should not override event property`() = runTest {
+ fakeAnalyticsStore.givenUserContent(consent = true)
+
+ val superProperties = SuperProperties(
+ cryptoSDKVersion = "0.0",
+ )
+
+ defaultVectorAnalytics.updateSuperProperties(superProperties)
+
+ val fakeEvent = aVectorAnalyticsEvent("THE_NAME", mutableMapOf("cryptoSDKVersion" to "XXX"))
+ defaultVectorAnalytics.capture(fakeEvent)
+
+ fakePostHog.verifyEventTracked(
+ "THE_NAME",
+ mapOf(
+ "cryptoSDKVersion" to "XXX"
+ )
+ )
+ }
+
+ @Test
+ fun `Super properties should be added to event with no properties`() = runTest {
+ fakeAnalyticsStore.givenUserContent(consent = true)
+
+ val superProperties = SuperProperties(
+ cryptoSDKVersion = "0.0",
+ )
+
+ defaultVectorAnalytics.updateSuperProperties(superProperties)
+
+ val fakeEvent = aVectorAnalyticsEvent("THE_NAME", null)
+ defaultVectorAnalytics.capture(fakeEvent)
+
+ fakePostHog.verifyEventTracked(
+ "THE_NAME",
+ mapOf(
+ "cryptoSDKVersion" to "0.0"
+ )
+ )
+ }
+
private fun Map?.clearNulls(): Map? {
if (this == null) return null