mirror of
https://github.com/SchildiChat/SchildiChat-android.git
synced 2024-11-22 01:15:54 +03:00
Merge branch 'release/1.6.18' into main
This commit is contained in:
commit
026318304f
30 changed files with 328 additions and 141 deletions
2
.github/workflows/tests.yml
vendored
2
.github/workflows/tests.yml
vendored
|
@ -32,7 +32,7 @@ jobs:
|
||||||
- uses: actions/setup-java@v3
|
- uses: actions/setup-java@v3
|
||||||
with:
|
with:
|
||||||
distribution: 'adopt'
|
distribution: 'adopt'
|
||||||
java-version: '11'
|
java-version: '17'
|
||||||
- uses: gradle/gradle-build-action@v2
|
- uses: gradle/gradle-build-action@v2
|
||||||
with:
|
with:
|
||||||
cache-read-only: ${{ github.ref != 'refs/heads/develop' }}
|
cache-read-only: ${{ github.ref != 'refs/heads/develop' }}
|
||||||
|
|
14
CHANGES.md
14
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)
|
Changes in Element v1.6.16 (2024-05-29)
|
||||||
=======================================
|
=======================================
|
||||||
|
|
||||||
|
|
|
@ -101,7 +101,7 @@ ext.libs = [
|
||||||
],
|
],
|
||||||
element : [
|
element : [
|
||||||
'opusencoder' : "io.element.android:opusencoder:1.1.0",
|
'opusencoder' : "io.element.android:opusencoder:1.1.0",
|
||||||
'wysiwyg' : "io.element.android:wysiwyg:2.35.0"
|
'wysiwyg' : "io.element.android:wysiwyg:2.37.3"
|
||||||
],
|
],
|
||||||
squareup : [
|
squareup : [
|
||||||
'moshi' : "com.squareup.moshi:moshi:$moshi",
|
'moshi' : "com.squareup.moshi:moshi:$moshi",
|
||||||
|
|
2
fastlane/metadata/android/en-US/changelogs/40106180.txt
Normal file
2
fastlane/metadata/android/en-US/changelogs/40106180.txt
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
Main changes in this version: Bugfixes.
|
||||||
|
Full changelog: https://github.com/element-hq/element-android/releases
|
|
@ -62,7 +62,7 @@ android {
|
||||||
// that the app's state is completely cleared between tests.
|
// that the app's state is completely cleared between tests.
|
||||||
testInstrumentationRunnerArguments clearPackageData: 'true'
|
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", "\"${gitRevision()}\""
|
||||||
buildConfigField "String", "GIT_SDK_REVISION_UNIX_DATE", "\"${gitRevisionUnixDate()}\""
|
buildConfigField "String", "GIT_SDK_REVISION_UNIX_DATE", "\"${gitRevisionUnixDate()}\""
|
||||||
|
|
|
@ -26,10 +26,7 @@ import org.junit.Test
|
||||||
import org.junit.runner.RunWith
|
import org.junit.runner.RunWith
|
||||||
import org.junit.runners.MethodSorters
|
import org.junit.runners.MethodSorters
|
||||||
import org.matrix.android.sdk.InstrumentedTest
|
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.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.MentionLinkSpecComparator
|
||||||
import org.matrix.android.sdk.internal.session.room.send.pills.TextPillsUtils
|
import org.matrix.android.sdk.internal.session.room.send.pills.TextPillsUtils
|
||||||
|
|
||||||
|
@ -56,12 +53,6 @@ class MarkdownParserTest : InstrumentedTest {
|
||||||
HtmlRenderer.builder().softbreak("<br />").build(),
|
HtmlRenderer.builder().softbreak("<br />").build(),
|
||||||
TextPillsUtils(
|
TextPillsUtils(
|
||||||
MentionLinkSpecComparator(),
|
MentionLinkSpecComparator(),
|
||||||
DisplayNameResolver(
|
|
||||||
MatrixConfiguration(
|
|
||||||
applicationFlavor = "TestFlavor",
|
|
||||||
roomDisplayNameFallbackProvider = TestRoomDisplayNameFallbackProvider()
|
|
||||||
)
|
|
||||||
),
|
|
||||||
TestPermalinkService()
|
TestPermalinkService()
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
|
@ -147,5 +147,12 @@ class Matrix(context: Context, matrixConfiguration: MatrixConfiguration) {
|
||||||
fun getSdkVersion(): String {
|
fun getSdkVersion(): String {
|
||||||
return BuildConfig.SDK_VERSION + " (" + BuildConfig.GIT_SDK_REVISION + ")"
|
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
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,7 +16,6 @@
|
||||||
|
|
||||||
package org.matrix.android.sdk.api.session.crypto
|
package org.matrix.android.sdk.api.session.crypto
|
||||||
|
|
||||||
import android.content.Context
|
|
||||||
import androidx.annotation.Size
|
import androidx.annotation.Size
|
||||||
import androidx.lifecycle.LiveData
|
import androidx.lifecycle.LiveData
|
||||||
import androidx.paging.PagedList
|
import androidx.paging.PagedList
|
||||||
|
@ -61,8 +60,6 @@ interface CryptoService {
|
||||||
|
|
||||||
suspend fun deleteDevices(@Size(min = 1) deviceIds: List<String>, userInteractiveAuthInterceptor: UserInteractiveAuthInterceptor)
|
suspend fun deleteDevices(@Size(min = 1) deviceIds: List<String>, userInteractiveAuthInterceptor: UserInteractiveAuthInterceptor)
|
||||||
|
|
||||||
fun getCryptoVersion(context: Context, longFormat: Boolean): String
|
|
||||||
|
|
||||||
fun isCryptoEnabled(): Boolean
|
fun isCryptoEnabled(): Boolean
|
||||||
|
|
||||||
fun isRoomBlacklistUnverifiedDevices(roomId: String?): Boolean
|
fun isRoomBlacklistUnverifiedDevices(roomId: String?): Boolean
|
||||||
|
|
|
@ -16,7 +16,6 @@
|
||||||
|
|
||||||
package org.matrix.android.sdk.internal.crypto
|
package org.matrix.android.sdk.internal.crypto
|
||||||
|
|
||||||
import android.content.Context
|
|
||||||
import androidx.lifecycle.LiveData
|
import androidx.lifecycle.LiveData
|
||||||
import androidx.lifecycle.map
|
import androidx.lifecycle.map
|
||||||
import androidx.paging.PagedList
|
import androidx.paging.PagedList
|
||||||
|
@ -184,13 +183,6 @@ internal class RustCryptoService @Inject constructor(
|
||||||
deleteDevices(listOf(deviceId), userInteractiveAuthInterceptor)
|
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) {
|
override suspend fun getMyCryptoDevice(): CryptoDeviceInfo = withContext(coroutineDispatchers.io) {
|
||||||
olmMachine.ownDevice()
|
olmMachine.ownDevice()
|
||||||
}
|
}
|
||||||
|
|
|
@ -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.crosssigning.CryptoCrossSigningKey
|
||||||
import org.matrix.android.sdk.api.session.crypto.model.CryptoDeviceInfo
|
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.DeviceKeys
|
||||||
import org.matrix.android.sdk.internal.crypto.model.rest.DeviceKeysWithUnsigned
|
|
||||||
import org.matrix.android.sdk.internal.crypto.model.rest.RestKeyInfo
|
import org.matrix.android.sdk.internal.crypto.model.rest.RestKeyInfo
|
||||||
|
|
||||||
internal object CryptoInfoMapper {
|
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 {
|
fun map(cryptoDeviceInfo: CryptoDeviceInfo): DeviceKeys {
|
||||||
return DeviceKeys(
|
return DeviceKeys(
|
||||||
deviceId = cryptoDeviceInfo.deviceId,
|
deviceId = cryptoDeviceInfo.deviceId,
|
||||||
|
|
|
@ -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<String>?,
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Required. Public identity keys. The names of the properties should be in the format <algorithm>:<device_id>.
|
|
||||||
* The keys themselves should be encoded as specified by the key algorithm.
|
|
||||||
*/
|
|
||||||
@Json(name = "keys")
|
|
||||||
val keys: Map<String, String>?,
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Required. Signatures for the device key object. A map from user ID, to a map from <algorithm>:<device_id> 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<String, Map<String, String>>?,
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Additional data added to the device key information by intermediate servers, and not covered by the signatures.
|
|
||||||
*/
|
|
||||||
@Json(name = "unsigned")
|
|
||||||
val unsigned: UnsignedDeviceInfo? = null
|
|
||||||
)
|
|
|
@ -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.
|
* 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")
|
@Json(name = "device_keys")
|
||||||
val deviceKeys: Map<String, Map<String, DeviceKeysWithUnsigned>>? = null,
|
val deviceKeys: Map<String, Map<String, Map<String, Any>>>? = null,
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* If any remote homeservers could not be reached, they are recorded here. The names of the
|
* If any remote homeservers could not be reached, they are recorded here. The names of the
|
||||||
|
|
|
@ -22,7 +22,6 @@ import kotlinx.coroutines.joinAll
|
||||||
import kotlinx.coroutines.sync.Mutex
|
import kotlinx.coroutines.sync.Mutex
|
||||||
import kotlinx.coroutines.sync.withLock
|
import kotlinx.coroutines.sync.withLock
|
||||||
import org.matrix.android.sdk.internal.crypto.api.CryptoApi
|
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.KeysQueryBody
|
||||||
import org.matrix.android.sdk.internal.crypto.model.rest.KeysQueryResponse
|
import org.matrix.android.sdk.internal.crypto.model.rest.KeysQueryResponse
|
||||||
import org.matrix.android.sdk.internal.crypto.model.rest.RestKeyInfo
|
import org.matrix.android.sdk.internal.crypto.model.rest.RestKeyInfo
|
||||||
|
@ -52,7 +51,7 @@ internal class DefaultDownloadKeysForUsers @Inject constructor(
|
||||||
|
|
||||||
return if (bestChunkSize.shouldChunk()) {
|
return if (bestChunkSize.shouldChunk()) {
|
||||||
// Store server results in these mutable maps
|
// Store server results in these mutable maps
|
||||||
val deviceKeys = mutableMapOf<String, Map<String, DeviceKeysWithUnsigned>>()
|
val deviceKeys = mutableMapOf<String, Map<String, Map<String, Any>>>()
|
||||||
val failures = mutableMapOf<String, Map<String, Any>>()
|
val failures = mutableMapOf<String, Map<String, Any>>()
|
||||||
val masterKeys = mutableMapOf<String, RestKeyInfo?>()
|
val masterKeys = mutableMapOf<String, RestKeyInfo?>()
|
||||||
val selfSigningKeys = mutableMapOf<String, RestKeyInfo?>()
|
val selfSigningKeys = mutableMapOf<String, RestKeyInfo?>()
|
||||||
|
|
|
@ -19,7 +19,6 @@ import android.text.SpannableString
|
||||||
import org.matrix.android.sdk.api.session.permalinks.PermalinkService
|
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.session.room.send.MatrixItemSpan
|
||||||
import org.matrix.android.sdk.api.util.MatrixItem
|
import org.matrix.android.sdk.api.util.MatrixItem
|
||||||
import org.matrix.android.sdk.internal.session.displayname.DisplayNameResolver
|
|
||||||
import java.util.Collections
|
import java.util.Collections
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
@ -29,7 +28,6 @@ import javax.inject.Inject
|
||||||
*/
|
*/
|
||||||
internal class TextPillsUtils @Inject constructor(
|
internal class TextPillsUtils @Inject constructor(
|
||||||
private val mentionLinkSpecComparator: MentionLinkSpecComparator,
|
private val mentionLinkSpecComparator: MentionLinkSpecComparator,
|
||||||
private val displayNameResolver: DisplayNameResolver,
|
|
||||||
private val permalinkService: PermalinkService
|
private val permalinkService: PermalinkService
|
||||||
) {
|
) {
|
||||||
|
|
||||||
|
@ -70,7 +68,7 @@ internal class TextPillsUtils @Inject constructor(
|
||||||
// append text before pill
|
// append text before pill
|
||||||
append(text, currIndex, start)
|
append(text, currIndex, start)
|
||||||
// append the pill
|
// 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
|
currIndex = end
|
||||||
}
|
}
|
||||||
// append text after the last pill
|
// append text after the last pill
|
||||||
|
|
|
@ -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)
|
||||||
|
}
|
||||||
|
}
|
|
@ -66,7 +66,7 @@ if [ ${envError} == 1 ]; then
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
buildToolsVersion="30.0.2"
|
buildToolsVersion="35.0.0"
|
||||||
buildToolsPath="${androidHome}/build-tools/${buildToolsVersion}"
|
buildToolsPath="${androidHome}/build-tools/${buildToolsVersion}"
|
||||||
|
|
||||||
if [[ ! -d ${buildToolsPath} ]]; then
|
if [[ ! -d ${buildToolsPath} ]]; then
|
||||||
|
|
|
@ -37,7 +37,7 @@ ext.versionMinor = 6
|
||||||
// Note: even values are reserved for regular release, odd values for hotfix release.
|
// 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
|
// When creating a hotfix, you should decrease the value, since the current value
|
||||||
// is the value for the next regular release.
|
// is the value for the next regular release.
|
||||||
ext.versionPatch = 16
|
ext.versionPatch = 18
|
||||||
|
|
||||||
static def getGitTimestamp() {
|
static def getGitTimestamp() {
|
||||||
def cmd = 'git show -s --format=%ct'
|
def cmd = 'git show -s --format=%ct'
|
||||||
|
|
|
@ -53,6 +53,7 @@ import im.vector.app.core.pushers.FcmHelper
|
||||||
import im.vector.app.core.resources.BuildMeta
|
import im.vector.app.core.resources.BuildMeta
|
||||||
import im.vector.app.features.analytics.DecryptionFailureTracker
|
import im.vector.app.features.analytics.DecryptionFailureTracker
|
||||||
import im.vector.app.features.analytics.VectorAnalytics
|
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.call.webrtc.WebRtcCallManager
|
||||||
import im.vector.app.features.configuration.VectorConfiguration
|
import im.vector.app.features.configuration.VectorConfiguration
|
||||||
import im.vector.app.features.invite.InvitesAcceptor
|
import im.vector.app.features.invite.InvitesAcceptor
|
||||||
|
@ -130,6 +131,13 @@ class VectorApplication :
|
||||||
appContext = this
|
appContext = this
|
||||||
flipperProxy.init(matrix)
|
flipperProxy.init(matrix)
|
||||||
vectorAnalytics.init()
|
vectorAnalytics.init()
|
||||||
|
vectorAnalytics.updateSuperProperties(
|
||||||
|
SuperProperties(
|
||||||
|
appPlatform = SuperProperties.AppPlatform.EA,
|
||||||
|
cryptoSDK = SuperProperties.CryptoSDK.Rust,
|
||||||
|
cryptoSDKVersion = Matrix.getCryptoVersion(longFormat = false)
|
||||||
|
)
|
||||||
|
)
|
||||||
invitesAcceptor.initialize()
|
invitesAcceptor.initialize()
|
||||||
autoRageShaker.initialize()
|
autoRageShaker.initialize()
|
||||||
decryptionFailureTracker.start()
|
decryptionFailureTracker.start()
|
||||||
|
|
|
@ -160,7 +160,7 @@ dependencies {
|
||||||
api 'com.facebook.stetho:stetho:1.6.0'
|
api 'com.facebook.stetho:stetho:1.6.0'
|
||||||
|
|
||||||
// Analytics
|
// 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
|
api libs.google.phonenumber
|
||||||
|
|
||||||
|
|
|
@ -184,7 +184,13 @@
|
||||||
<category android:name="android.intent.category.BROWSABLE" />
|
<category android:name="android.intent.category.BROWSABLE" />
|
||||||
|
|
||||||
<data android:scheme="https" />
|
<data android:scheme="https" />
|
||||||
<data android:host="*.element.io" />
|
<!-- Note: we can't use "*.element.io" here because it'll intercept the "mas.element.io" domain too. -->
|
||||||
|
<!-- Matching asset file: https://app.element.io/.well-known/assetlinks.json -->
|
||||||
|
<data android:host="app.element.io" />
|
||||||
|
<!-- Matching asset file: https://develop.element.io/.well-known/assetlinks.json -->
|
||||||
|
<data android:host="develop.element.io" />
|
||||||
|
<!-- Matching asset file: https://staging.element.io/.well-known/assetlinks.json -->
|
||||||
|
<data android:host="staging.element.io" />
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
</activity>
|
</activity>
|
||||||
|
|
||||||
|
|
|
@ -22,7 +22,6 @@ import im.vector.app.core.dispatchers.CoroutineDispatchers
|
||||||
import im.vector.app.core.pushers.UnregisterUnifiedPushUseCase
|
import im.vector.app.core.pushers.UnregisterUnifiedPushUseCase
|
||||||
import im.vector.app.core.services.GuardServiceStarter
|
import im.vector.app.core.services.GuardServiceStarter
|
||||||
import im.vector.app.core.session.ConfigureAndStartSessionUseCase
|
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.call.webrtc.WebRtcCallManager
|
||||||
import im.vector.app.features.crypto.keysrequest.KeyRequestHandler
|
import im.vector.app.features.crypto.keysrequest.KeyRequestHandler
|
||||||
import im.vector.app.features.crypto.verification.IncomingVerificationRequestHandler
|
import im.vector.app.features.crypto.verification.IncomingVerificationRequestHandler
|
||||||
|
@ -57,7 +56,6 @@ class ActiveSessionHolder @Inject constructor(
|
||||||
private val unregisterUnifiedPushUseCase: UnregisterUnifiedPushUseCase,
|
private val unregisterUnifiedPushUseCase: UnregisterUnifiedPushUseCase,
|
||||||
private val applicationCoroutineScope: CoroutineScope,
|
private val applicationCoroutineScope: CoroutineScope,
|
||||||
private val coroutineDispatchers: CoroutineDispatchers,
|
private val coroutineDispatchers: CoroutineDispatchers,
|
||||||
private val decryptionFailureTracker: DecryptionFailureTracker,
|
|
||||||
) {
|
) {
|
||||||
|
|
||||||
private var activeSessionReference: AtomicReference<Session?> = AtomicReference()
|
private var activeSessionReference: AtomicReference<Session?> = AtomicReference()
|
||||||
|
|
|
@ -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.VectorAnalyticsEvent
|
||||||
import im.vector.app.features.analytics.itf.VectorAnalyticsScreen
|
import im.vector.app.features.analytics.itf.VectorAnalyticsScreen
|
||||||
|
import im.vector.app.features.analytics.plan.SuperProperties
|
||||||
import im.vector.app.features.analytics.plan.UserProperties
|
import im.vector.app.features.analytics.plan.UserProperties
|
||||||
|
|
||||||
interface AnalyticsTracker {
|
interface AnalyticsTracker {
|
||||||
|
@ -35,4 +36,10 @@ interface AnalyticsTracker {
|
||||||
* Update user specific properties.
|
* Update user specific properties.
|
||||||
*/
|
*/
|
||||||
fun updateUserProperties(userProperties: UserProperties)
|
fun updateUserProperties(userProperties: UserProperties)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update the super properties.
|
||||||
|
* Super properties are added to any tracked event automatically.
|
||||||
|
*/
|
||||||
|
fun updateSuperProperties(updatedProperties: SuperProperties)
|
||||||
}
|
}
|
||||||
|
|
|
@ -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.VectorAnalyticsEvent
|
||||||
import im.vector.app.features.analytics.itf.VectorAnalyticsScreen
|
import im.vector.app.features.analytics.itf.VectorAnalyticsScreen
|
||||||
import im.vector.app.features.analytics.log.analyticsTag
|
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.plan.UserProperties
|
||||||
import im.vector.app.features.analytics.store.AnalyticsStore
|
import im.vector.app.features.analytics.store.AnalyticsStore
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
|
@ -63,6 +64,8 @@ class DefaultVectorAnalytics @Inject constructor(
|
||||||
// Cache for the properties to send
|
// Cache for the properties to send
|
||||||
private var pendingUserProperties: UserProperties? = null
|
private var pendingUserProperties: UserProperties? = null
|
||||||
|
|
||||||
|
private var superProperties: SuperProperties? = null
|
||||||
|
|
||||||
override fun init() {
|
override fun init() {
|
||||||
observeUserConsent()
|
observeUserConsent()
|
||||||
observeAnalyticsId()
|
observeAnalyticsId()
|
||||||
|
@ -168,20 +171,14 @@ class DefaultVectorAnalytics @Inject constructor(
|
||||||
|
|
||||||
override fun capture(event: VectorAnalyticsEvent) {
|
override fun capture(event: VectorAnalyticsEvent) {
|
||||||
Timber.tag(analyticsTag.value).d("capture($event)")
|
Timber.tag(analyticsTag.value).d("capture($event)")
|
||||||
posthog
|
posthog?.takeIf { userConsent == true }?.capture(
|
||||||
?.takeIf { userConsent == true }
|
event.getName(), analyticsId, event.getProperties()?.toPostHogProperties().orEmpty().withSuperProperties()
|
||||||
?.capture(
|
|
||||||
event.getName(),
|
|
||||||
analyticsId,
|
|
||||||
event.getProperties()?.toPostHogProperties()
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun screen(screen: VectorAnalyticsScreen) {
|
override fun screen(screen: VectorAnalyticsScreen) {
|
||||||
Timber.tag(analyticsTag.value).d("screen($screen)")
|
Timber.tag(analyticsTag.value).d("screen($screen)")
|
||||||
posthog
|
posthog?.takeIf { userConsent == true }?.screen(screen.getName(), screen.getProperties()?.toPostHogProperties().orEmpty().withSuperProperties())
|
||||||
?.takeIf { userConsent == true }
|
|
||||||
?.screen(screen.getName(), screen.getProperties()?.toPostHogProperties())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun updateUserProperties(userProperties: UserProperties) {
|
override fun updateUserProperties(userProperties: UserProperties) {
|
||||||
|
@ -195,9 +192,7 @@ class DefaultVectorAnalytics @Inject constructor(
|
||||||
private fun doUpdateUserProperties(userProperties: UserProperties) {
|
private fun doUpdateUserProperties(userProperties: UserProperties) {
|
||||||
// we need a distinct id to set user properties
|
// we need a distinct id to set user properties
|
||||||
val distinctId = analyticsId ?: return
|
val distinctId = analyticsId ?: return
|
||||||
posthog
|
posthog?.takeIf { userConsent == true }?.identify(distinctId, userProperties.getProperties())
|
||||||
?.takeIf { userConsent == true }
|
|
||||||
?.identify(distinctId, userProperties.getProperties())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun Map<String, Any?>?.toPostHogProperties(): Map<String, Any>? {
|
private fun Map<String, Any?>?.toPostHogProperties(): Map<String, Any>? {
|
||||||
|
@ -226,9 +221,32 @@ class DefaultVectorAnalytics @Inject constructor(
|
||||||
return nonNulls
|
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<String, Any>.withSuperProperties(): Map<String, Any>? {
|
||||||
|
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) {
|
override fun trackError(throwable: Throwable) {
|
||||||
sentryAnalytics
|
sentryAnalytics
|
||||||
.takeIf { userConsent == true }
|
.takeIf { userConsent == true }
|
||||||
?.trackError(throwable)
|
?.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
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -246,10 +246,10 @@ class AutoCompleter @AssistedInject constructor(
|
||||||
val linkText = when (matrixItem) {
|
val linkText = when (matrixItem) {
|
||||||
is MatrixItem.RoomAliasItem,
|
is MatrixItem.RoomAliasItem,
|
||||||
is MatrixItem.RoomItem,
|
is MatrixItem.RoomItem,
|
||||||
is MatrixItem.SpaceItem ->
|
is MatrixItem.SpaceItem,
|
||||||
|
is MatrixItem.UserItem ->
|
||||||
matrixItem.id
|
matrixItem.id
|
||||||
is MatrixItem.EveryoneInRoomItem,
|
is MatrixItem.EveryoneInRoomItem,
|
||||||
is MatrixItem.UserItem,
|
|
||||||
is MatrixItem.EventItem ->
|
is MatrixItem.EventItem ->
|
||||||
matrixItem.getBestName()
|
matrixItem.getBestName()
|
||||||
}
|
}
|
||||||
|
|
|
@ -796,14 +796,14 @@ class MessageComposerFragment : VectorBaseFragment<FragmentComposerBinding>(), A
|
||||||
composer.editText.setSelection(Command.EMOTE.command.length + 1)
|
composer.editText.setSelection(Command.EMOTE.command.length + 1)
|
||||||
} else {
|
} else {
|
||||||
val roomMember = timelineViewModel.getMember(userId)
|
val roomMember = timelineViewModel.getMember(userId)
|
||||||
val displayName = sanitizeDisplayName(roomMember?.displayName ?: userId)
|
|
||||||
if ((composer as? RichTextComposerLayout)?.isTextFormattingEnabled == true) {
|
if ((composer as? RichTextComposerLayout)?.isTextFormattingEnabled == true) {
|
||||||
// Rich text editor is enabled so we need to use its APIs
|
// Rich text editor is enabled so we need to use its APIs
|
||||||
permalinkService.createPermalink(userId)?.let { url ->
|
permalinkService.createPermalink(userId)?.let { url ->
|
||||||
(composer as RichTextComposerLayout).insertMention(url, displayName)
|
(composer as RichTextComposerLayout).insertMention(url, userId)
|
||||||
composer.editText.append(" ")
|
composer.editText.append(" ")
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
val displayName = sanitizeDisplayName(roomMember?.displayName ?: userId)
|
||||||
val pill = buildSpannedString {
|
val pill = buildSpannedString {
|
||||||
append(displayName)
|
append(displayName)
|
||||||
setSpan(
|
setSpan(
|
||||||
|
|
|
@ -84,7 +84,7 @@ class MergedHeaderItemFactory @Inject constructor(
|
||||||
buildRoomCreationMergedSummary(currentPosition, items, partialState, event, eventIdToHighlight, requestModelBuild, callback)
|
buildRoomCreationMergedSummary(currentPosition, items, partialState, event, eventIdToHighlight, requestModelBuild, callback)
|
||||||
isStartOfSameTypeEventsSummary(event, nextEvent, addDaySeparator) ->
|
isStartOfSameTypeEventsSummary(event, nextEvent, addDaySeparator) ->
|
||||||
buildSameTypeEventsMergedSummary(currentPosition, items, partialState, event, eventIdToHighlight, requestModelBuild, callback)
|
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)
|
buildRedactedEventsMergedSummary(currentPosition, items, partialState, event, eventIdToHighlight, requestModelBuild, callback)
|
||||||
else -> null
|
else -> null
|
||||||
}
|
}
|
||||||
|
@ -122,19 +122,25 @@ class MergedHeaderItemFactory @Inject constructor(
|
||||||
* @param event the main timeline event
|
* @param event the main timeline event
|
||||||
* @param items all known items, sorted from newer event to oldest event
|
* @param items all known items, sorted from newer event to oldest event
|
||||||
* @param currentPosition the current position
|
* @param currentPosition the current position
|
||||||
|
* @param partialState partial state data
|
||||||
* @param addDaySeparator true to add a day separator
|
* @param addDaySeparator true to add a day separator
|
||||||
*/
|
*/
|
||||||
private fun isStartOfRedactedEventsSummary(
|
private fun isStartOfRedactedEventsSummary(
|
||||||
event: TimelineEvent,
|
event: TimelineEvent,
|
||||||
items: List<TimelineEvent>,
|
items: List<TimelineEvent>,
|
||||||
currentPosition: Int,
|
currentPosition: Int,
|
||||||
|
partialState: TimelineEventController.PartialState,
|
||||||
addDaySeparator: Boolean,
|
addDaySeparator: Boolean,
|
||||||
): Boolean {
|
): Boolean {
|
||||||
val nextNonRedactionEvent = items
|
val nextDisplayableEvent = items.subList(currentPosition + 1, items.size).firstOrNull {
|
||||||
.subList(fromIndex = currentPosition + 1, toIndex = items.size)
|
timelineEventVisibilityHelper.shouldShowEvent(
|
||||||
.find { it.root.getClearType() != EventType.REDACTION }
|
timelineEvent = it,
|
||||||
return event.root.isRedacted() &&
|
highlightedEventId = partialState.highlightedEventId,
|
||||||
(!nextNonRedactionEvent?.root?.isRedacted().orFalse() || addDaySeparator)
|
isFromThreadTimeline = partialState.isFromThreadTimeline(),
|
||||||
|
rootThreadEventId = partialState.rootThreadEventId
|
||||||
|
)
|
||||||
|
}
|
||||||
|
return event.root.isRedacted() && (nextDisplayableEvent?.root?.isRedacted() == false || addDaySeparator)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun buildSameTypeEventsMergedSummary(
|
private fun buildSameTypeEventsMergedSummary(
|
||||||
|
|
|
@ -151,16 +151,20 @@ class TimelineEventVisibilityHelper @Inject constructor(
|
||||||
rootThreadEventId: String?,
|
rootThreadEventId: String?,
|
||||||
isFromThreadTimeline: Boolean
|
isFromThreadTimeline: Boolean
|
||||||
): List<TimelineEvent> {
|
): List<TimelineEvent> {
|
||||||
val prevSub = timelineEvents
|
val prevDisplayableEvents = timelineEvents.subList(0, index + 1)
|
||||||
.subList(0, index + 1)
|
.filter {
|
||||||
// Ensure to not take the REDACTION events into account
|
shouldShowEvent(
|
||||||
.filter { it.root.getClearType() != EventType.REDACTION }
|
timelineEvent = it,
|
||||||
return prevSub
|
highlightedEventId = eventIdToHighlight,
|
||||||
|
isFromThreadTimeline = isFromThreadTimeline,
|
||||||
|
rootThreadEventId = rootThreadEventId)
|
||||||
|
}
|
||||||
|
return prevDisplayableEvents
|
||||||
.reversed()
|
.reversed()
|
||||||
.let {
|
.let {
|
||||||
nextEventsUntil(it, 0, minSize, eventIdToHighlight, rootThreadEventId, isFromThreadTimeline, object : PredicateToStopSearch {
|
nextEventsUntil(it, 0, minSize, eventIdToHighlight, rootThreadEventId, isFromThreadTimeline, object : PredicateToStopSearch {
|
||||||
override fun shouldStopSearch(oldEvent: Event, newEvent: Event): Boolean {
|
override fun shouldStopSearch(oldEvent: Event, newEvent: Event): Boolean {
|
||||||
return oldEvent.isRedacted() && !newEvent.isRedacted()
|
return !newEvent.isRedacted()
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
@ -265,7 +265,7 @@ class BugReporter @Inject constructor(
|
||||||
activeSessionHolder.getSafeActiveSession()?.let { session ->
|
activeSessionHolder.getSafeActiveSession()?.let { session ->
|
||||||
userId = session.myUserId
|
userId = session.myUserId
|
||||||
deviceId = session.sessionParams.deviceId
|
deviceId = session.sessionParams.deviceId
|
||||||
olmVersion = session.cryptoService().getCryptoVersion(context, true)
|
olmVersion = Matrix.getCryptoVersion(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!mIsCancelled) {
|
if (!mIsCancelled) {
|
||||||
|
|
|
@ -96,7 +96,7 @@ class VectorSettingsHelpAboutFragment :
|
||||||
|
|
||||||
// olm version
|
// olm version
|
||||||
findPreference<VectorPreference>(VectorPreferences.SETTINGS_CRYPTO_VERSION_PREFERENCE_KEY)!!
|
findPreference<VectorPreference>(VectorPreferences.SETTINGS_CRYPTO_VERSION_PREFERENCE_KEY)!!
|
||||||
.summary = session.cryptoService().getCryptoVersion(requireContext(), true)
|
.summary = Matrix.getCryptoVersion(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
|
|
@ -16,6 +16,7 @@
|
||||||
|
|
||||||
package im.vector.app.features.analytics.impl
|
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.FakeAnalyticsStore
|
||||||
import im.vector.app.test.fakes.FakeLateInitUserPropertiesFactory
|
import im.vector.app.test.fakes.FakeLateInitUserPropertiesFactory
|
||||||
import im.vector.app.test.fakes.FakePostHog
|
import im.vector.app.test.fakes.FakePostHog
|
||||||
|
@ -51,7 +52,7 @@ class DefaultVectorAnalyticsTest {
|
||||||
analyticsStore = fakeAnalyticsStore.instance,
|
analyticsStore = fakeAnalyticsStore.instance,
|
||||||
globalScope = CoroutineScope(Dispatchers.Unconfined),
|
globalScope = CoroutineScope(Dispatchers.Unconfined),
|
||||||
analyticsConfig = anAnalyticsConfig(isEnabled = true),
|
analyticsConfig = anAnalyticsConfig(isEnabled = true),
|
||||||
lateInitUserPropertiesFactory = fakeLateInitUserPropertiesFactory.instance
|
lateInitUserPropertiesFactory = fakeLateInitUserPropertiesFactory.instance,
|
||||||
)
|
)
|
||||||
|
|
||||||
@Before
|
@Before
|
||||||
|
@ -174,6 +175,117 @@ class DefaultVectorAnalyticsTest {
|
||||||
fakeSentryAnalytics.verifyNoErrorTracking()
|
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<String, Any?>?.clearNulls(): Map<String, Any>? {
|
private fun Map<String, Any?>?.clearNulls(): Map<String, Any>? {
|
||||||
if (this == null) return null
|
if (this == null) return null
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue