Merge pull request #2754 from element-hq/feature/valere/expected_utd_integration
Expected UTDs due to membership support
This commit is contained in:
commit
60c8d6e147
15 changed files with 147 additions and 13 deletions
1
changelog.d/2754.feature
Normal file
1
changelog.d/2754.feature
Normal file
|
|
@ -0,0 +1 @@
|
|||
Add support for expected decryption errors due to membership (UX and analytics).
|
||||
|
|
@ -19,24 +19,33 @@ package io.element.android.features.messages.impl.timeline.components.event
|
|||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.tooling.preview.PreviewParameter
|
||||
import io.element.android.features.messages.impl.timeline.components.layout.ContentAvoidingLayoutData
|
||||
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemEncryptedContent
|
||||
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemEncryptedContentProvider
|
||||
import io.element.android.libraries.designsystem.icons.CompoundDrawables
|
||||
import io.element.android.libraries.designsystem.preview.ElementPreview
|
||||
import io.element.android.libraries.designsystem.preview.PreviewsDayNight
|
||||
import io.element.android.libraries.matrix.api.timeline.item.event.UnableToDecryptContent
|
||||
import io.element.android.libraries.matrix.api.timeline.item.event.UtdCause
|
||||
import io.element.android.libraries.ui.strings.CommonStrings
|
||||
|
||||
@Composable
|
||||
fun TimelineItemEncryptedView(
|
||||
@Suppress("UNUSED_PARAMETER") content: TimelineItemEncryptedContent,
|
||||
content: TimelineItemEncryptedContent,
|
||||
onContentLayoutChanged: (ContentAvoidingLayoutData) -> Unit,
|
||||
modifier: Modifier = Modifier
|
||||
) {
|
||||
val isMembershipUtd = (content.data as? UnableToDecryptContent.Data.MegolmV1AesSha2)?.utdCause == UtdCause.Membership
|
||||
val (textId, iconId) = if (isMembershipUtd) {
|
||||
CommonStrings.common_unable_to_decrypt_no_access to CompoundDrawables.ic_compound_block
|
||||
} else {
|
||||
CommonStrings.common_waiting_for_decryption_key to CompoundDrawables.ic_compound_time
|
||||
}
|
||||
TimelineItemInformativeView(
|
||||
text = stringResource(id = CommonStrings.common_waiting_for_decryption_key),
|
||||
text = stringResource(id = textId),
|
||||
iconDescription = stringResource(id = CommonStrings.dialog_title_warning),
|
||||
iconResourceId = CompoundDrawables.ic_compound_time,
|
||||
iconResourceId = iconId,
|
||||
onContentLayoutChanged = onContentLayoutChanged,
|
||||
modifier = modifier
|
||||
)
|
||||
|
|
@ -44,11 +53,11 @@ fun TimelineItemEncryptedView(
|
|||
|
||||
@PreviewsDayNight
|
||||
@Composable
|
||||
internal fun TimelineItemEncryptedViewPreview() = ElementPreview {
|
||||
internal fun TimelineItemEncryptedViewPreview(
|
||||
@PreviewParameter(TimelineItemEncryptedContentProvider::class) content: TimelineItemEncryptedContent
|
||||
) = ElementPreview {
|
||||
TimelineItemEncryptedView(
|
||||
content = TimelineItemEncryptedContent(
|
||||
data = UnableToDecryptContent.Data.Unknown
|
||||
),
|
||||
content = content,
|
||||
onContentLayoutChanged = {},
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,46 @@
|
|||
/*
|
||||
* Copyright (c) 2024 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 io.element.android.features.messages.impl.timeline.model.event
|
||||
|
||||
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
|
||||
import io.element.android.libraries.matrix.api.timeline.item.event.UnableToDecryptContent
|
||||
import io.element.android.libraries.matrix.api.timeline.item.event.UtdCause
|
||||
|
||||
open class TimelineItemEncryptedContentProvider : PreviewParameterProvider<TimelineItemEncryptedContent> {
|
||||
override val values: Sequence<TimelineItemEncryptedContent>
|
||||
get() = sequenceOf(
|
||||
aTimelineItemEncryptedContent(),
|
||||
aTimelineItemEncryptedContent(
|
||||
data = UnableToDecryptContent.Data.MegolmV1AesSha2(
|
||||
sessionId = "sessionId",
|
||||
utdCause = UtdCause.Membership,
|
||||
)
|
||||
),
|
||||
aTimelineItemEncryptedContent(
|
||||
data = UnableToDecryptContent.Data.MegolmV1AesSha2(
|
||||
sessionId = "sessionId",
|
||||
utdCause = UtdCause.Unknown,
|
||||
)
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
private fun aTimelineItemEncryptedContent(
|
||||
data: UnableToDecryptContent.Data = UnableToDecryptContent.Data.Unknown
|
||||
) = TimelineItemEncryptedContent(
|
||||
data = data
|
||||
)
|
||||
|
|
@ -182,9 +182,8 @@ kotlinpoet = "com.squareup:kotlinpoet:1.16.0"
|
|||
# Analytics
|
||||
posthog = "com.posthog:posthog-android:3.1.18"
|
||||
sentry = "io.sentry:sentry-android:7.8.0"
|
||||
# Note: only 0.19.0 will compile properly
|
||||
# main branch can be tested replacing the version with main-SNAPSHOT
|
||||
matrix_analytics_events = "com.github.matrix-org:matrix-analytics-events:0.15.0"
|
||||
matrix_analytics_events = "com.github.matrix-org:matrix-analytics-events:0.20.0"
|
||||
|
||||
# Emojibase
|
||||
matrix_emojibase_bindings = "io.element.android:emojibase-bindings:1.1.3"
|
||||
|
|
|
|||
|
|
@ -63,7 +63,8 @@ data class UnableToDecryptContent(
|
|||
) : Data
|
||||
|
||||
data class MegolmV1AesSha2(
|
||||
val sessionId: String
|
||||
val sessionId: String,
|
||||
val utdCause: UtdCause
|
||||
) : Data
|
||||
|
||||
data object Unknown : Data
|
||||
|
|
|
|||
|
|
@ -0,0 +1,22 @@
|
|||
/*
|
||||
* Copyright (c) 2024 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 io.element.android.libraries.matrix.api.timeline.item.event
|
||||
|
||||
enum class UtdCause {
|
||||
Unknown,
|
||||
Membership,
|
||||
}
|
||||
|
|
@ -21,6 +21,7 @@ import io.element.android.services.analytics.api.AnalyticsService
|
|||
import org.matrix.rustcomponents.sdk.UnableToDecryptDelegate
|
||||
import org.matrix.rustcomponents.sdk.UnableToDecryptInfo
|
||||
import timber.log.Timber
|
||||
import uniffi.matrix_sdk_crypto.UtdCause
|
||||
import javax.inject.Inject
|
||||
|
||||
class UtdTracker @Inject constructor(
|
||||
|
|
@ -28,6 +29,10 @@ class UtdTracker @Inject constructor(
|
|||
) : UnableToDecryptDelegate {
|
||||
override fun onUtd(info: UnableToDecryptInfo) {
|
||||
Timber.d("onUtd for event ${info.eventId}, timeToDecryptMs: ${info.timeToDecryptMs}")
|
||||
val name = when (info.cause) {
|
||||
UtdCause.UNKNOWN -> Error.Name.OlmKeysNotSentError
|
||||
UtdCause.MEMBERSHIP -> Error.Name.ExpectedDueToMembership
|
||||
}
|
||||
val event = Error(
|
||||
context = null,
|
||||
// Keep cryptoModule for compatibility.
|
||||
|
|
@ -35,8 +40,7 @@ class UtdTracker @Inject constructor(
|
|||
cryptoSDK = Error.CryptoSDK.Rust,
|
||||
timeToDecryptMillis = info.timeToDecryptMs?.toInt() ?: -1,
|
||||
domain = Error.Domain.E2EE,
|
||||
// TODO get a more specific error name from `info`
|
||||
name = Error.Name.OlmKeysNotSentError,
|
||||
name = name,
|
||||
)
|
||||
analyticsService.capture(event)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -31,6 +31,7 @@ import io.element.android.libraries.matrix.api.timeline.item.event.StateContent
|
|||
import io.element.android.libraries.matrix.api.timeline.item.event.StickerContent
|
||||
import io.element.android.libraries.matrix.api.timeline.item.event.UnableToDecryptContent
|
||||
import io.element.android.libraries.matrix.api.timeline.item.event.UnknownContent
|
||||
import io.element.android.libraries.matrix.api.timeline.item.event.UtdCause
|
||||
import io.element.android.libraries.matrix.impl.media.map
|
||||
import io.element.android.libraries.matrix.impl.poll.map
|
||||
import kotlinx.collections.immutable.toImmutableList
|
||||
|
|
@ -41,6 +42,7 @@ import org.matrix.rustcomponents.sdk.use
|
|||
import org.matrix.rustcomponents.sdk.EncryptedMessage as RustEncryptedMessage
|
||||
import org.matrix.rustcomponents.sdk.MembershipChange as RustMembershipChange
|
||||
import org.matrix.rustcomponents.sdk.OtherState as RustOtherState
|
||||
import uniffi.matrix_sdk_crypto.UtdCause as RustUtdCause
|
||||
|
||||
class TimelineEventContentMapper(private val eventMessageMapper: EventMessageMapper = EventMessageMapper()) {
|
||||
fun map(content: TimelineItemContent): EventContent {
|
||||
|
|
@ -148,6 +150,13 @@ private fun RustMembershipChange.map(): MembershipChange {
|
|||
}
|
||||
}
|
||||
|
||||
private fun RustUtdCause.map(): UtdCause {
|
||||
return when (this) {
|
||||
RustUtdCause.MEMBERSHIP -> UtdCause.Membership
|
||||
RustUtdCause.UNKNOWN -> UtdCause.Unknown
|
||||
}
|
||||
}
|
||||
|
||||
// TODO extract state events?
|
||||
private fun RustOtherState.map(): OtherState {
|
||||
return when (this) {
|
||||
|
|
@ -177,7 +186,7 @@ private fun RustOtherState.map(): OtherState {
|
|||
|
||||
private fun RustEncryptedMessage.map(): UnableToDecryptContent.Data {
|
||||
return when (this) {
|
||||
is RustEncryptedMessage.MegolmV1AesSha2 -> UnableToDecryptContent.Data.MegolmV1AesSha2(sessionId)
|
||||
is RustEncryptedMessage.MegolmV1AesSha2 -> UnableToDecryptContent.Data.MegolmV1AesSha2(sessionId, cause.map())
|
||||
is RustEncryptedMessage.OlmV1Curve25519AesSha2 -> UnableToDecryptContent.Data.OlmV1Curve25519AesSha2(senderKey)
|
||||
RustEncryptedMessage.Unknown -> UnableToDecryptContent.Data.Unknown
|
||||
}
|
||||
|
|
|
|||
|
|
@ -74,4 +74,29 @@ class UtdTrackerTest {
|
|||
assertThat(fakeAnalyticsService.screenEvents).isEmpty()
|
||||
assertThat(fakeAnalyticsService.trackedErrors).isEmpty()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `when onUtd is called with membership cause, the expected analytics Event is sent`() {
|
||||
val fakeAnalyticsService = FakeAnalyticsService()
|
||||
val sut = UtdTracker(fakeAnalyticsService)
|
||||
sut.onUtd(
|
||||
UnableToDecryptInfo(
|
||||
eventId = AN_EVENT_ID.value,
|
||||
timeToDecryptMs = 123.toULong(),
|
||||
cause = UtdCause.MEMBERSHIP,
|
||||
)
|
||||
)
|
||||
assertThat(fakeAnalyticsService.capturedEvents).containsExactly(
|
||||
Error(
|
||||
context = null,
|
||||
cryptoModule = Error.CryptoModule.Rust,
|
||||
cryptoSDK = Error.CryptoSDK.Rust,
|
||||
timeToDecryptMillis = 123,
|
||||
domain = Error.Domain.E2EE,
|
||||
name = Error.Name.ExpectedDueToMembership
|
||||
)
|
||||
)
|
||||
assertThat(fakeAnalyticsService.screenEvents).isEmpty()
|
||||
assertThat(fakeAnalyticsService.trackedErrors).isEmpty()
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,3 @@
|
|||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:6b5fe193d680293c89c5faad9e74e4f887d1202f2f58ba9e145529c9d3a21fab
|
||||
size 10022
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:42816394d24d8bad66d38b97b1ffdb180b4c44ae205397d817a828e72649163b
|
||||
size 11761
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:6b5fe193d680293c89c5faad9e74e4f887d1202f2f58ba9e145529c9d3a21fab
|
||||
size 10022
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:80ee405fc93dfc1074e840a078b71d270058e75d7913ceeefe7883698ff8134c
|
||||
size 9869
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:4364b6c6fe2d308ba89492d8444138fd85c63e378fb538351dd56237cd846a7e
|
||||
size 11616
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:80ee405fc93dfc1074e840a078b71d270058e75d7913ceeefe7883698ff8134c
|
||||
size 9869
|
||||
Loading…
Add table
Add a link
Reference in a new issue