Browse Source

Merge pull request #2754 from element-hq/feature/valere/expected_utd_integration

Expected UTDs due to membership support
pull/2771/head
Benoit Marty 5 months ago committed by GitHub
parent
commit
60c8d6e147
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
  1. 1
      changelog.d/2754.feature
  2. 23
      features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/event/TimelineItemEncryptedView.kt
  3. 46
      features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/event/TimelineItemEncryptedContentProvider.kt
  4. 3
      gradle/libs.versions.toml
  5. 3
      libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/timeline/item/event/EventContent.kt
  6. 22
      libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/timeline/item/event/UtdCause.kt
  7. 8
      libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/analytics/UtdTracker.kt
  8. 11
      libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/item/event/TimelineEventContentMapper.kt
  9. 25
      libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/analytics/UtdTrackerTest.kt
  10. BIN
      tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.event_TimelineItemEncryptedView_null_TimelineItemEncryptedView-Day-36_36_null_0,NEXUS_5,1.0,en].png
  11. 3
      tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.event_TimelineItemEncryptedView_null_TimelineItemEncryptedView-Day-36_36_null_1,NEXUS_5,1.0,en].png
  12. BIN
      tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.event_TimelineItemEncryptedView_null_TimelineItemEncryptedView-Day-36_36_null_2,NEXUS_5,1.0,en].png
  13. BIN
      tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.event_TimelineItemEncryptedView_null_TimelineItemEncryptedView-Night-36_37_null_0,NEXUS_5,1.0,en].png
  14. 3
      tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.event_TimelineItemEncryptedView_null_TimelineItemEncryptedView-Night-36_37_null_1,NEXUS_5,1.0,en].png
  15. BIN
      tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.event_TimelineItemEncryptedView_null_TimelineItemEncryptedView-Night-36_37_null_2,NEXUS_5,1.0,en].png

1
changelog.d/2754.feature

@ -0,0 +1 @@ @@ -0,0 +1 @@
Add support for expected decryption errors due to membership (UX and analytics).

23
features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/event/TimelineItemEncryptedView.kt

@ -19,24 +19,33 @@ package io.element.android.features.messages.impl.timeline.components.event @@ -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( @@ -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 = {},
)
}

46
features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/event/TimelineItemEncryptedContentProvider.kt

@ -0,0 +1,46 @@ @@ -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
)

3
gradle/libs.versions.toml

@ -182,9 +182,8 @@ kotlinpoet = "com.squareup:kotlinpoet:1.16.0" @@ -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"

3
libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/timeline/item/event/EventContent.kt

@ -63,7 +63,8 @@ data class UnableToDecryptContent( @@ -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

22
libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/timeline/item/event/UtdCause.kt

@ -0,0 +1,22 @@ @@ -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,
}

8
libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/analytics/UtdTracker.kt

@ -21,6 +21,7 @@ import io.element.android.services.analytics.api.AnalyticsService @@ -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( @@ -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( @@ -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)
}

11
libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/item/event/TimelineEventContentMapper.kt

@ -31,6 +31,7 @@ import io.element.android.libraries.matrix.api.timeline.item.event.StateContent @@ -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 @@ -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 { @@ -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 { @@ -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
}

25
libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/analytics/UtdTrackerTest.kt

@ -74,4 +74,29 @@ class UtdTrackerTest { @@ -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()
}
}

BIN
tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.event_TimelineItemEncryptedView_null_TimelineItemEncryptedView-Day-36_36_null_0,NEXUS_5,1.0,en].png (Stored with Git LFS)

Binary file not shown.

3
tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.event_TimelineItemEncryptedView_null_TimelineItemEncryptedView-Day-36_36_null_1,NEXUS_5,1.0,en].png

@ -0,0 +1,3 @@ @@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:42816394d24d8bad66d38b97b1ffdb180b4c44ae205397d817a828e72649163b
size 11761

BIN
tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.event_TimelineItemEncryptedView_null_TimelineItemEncryptedView-Day-36_36_null_2,NEXUS_5,1.0,en].png (Stored with Git LFS)

Binary file not shown.

BIN
tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.event_TimelineItemEncryptedView_null_TimelineItemEncryptedView-Night-36_37_null_0,NEXUS_5,1.0,en].png (Stored with Git LFS)

Binary file not shown.

3
tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.event_TimelineItemEncryptedView_null_TimelineItemEncryptedView-Night-36_37_null_1,NEXUS_5,1.0,en].png

@ -0,0 +1,3 @@ @@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:4364b6c6fe2d308ba89492d8444138fd85c63e378fb538351dd56237cd846a7e
size 11616

BIN
tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.timeline.components.event_TimelineItemEncryptedView_null_TimelineItemEncryptedView-Night-36_37_null_2,NEXUS_5,1.0,en].png (Stored with Git LFS)

Binary file not shown.
Loading…
Cancel
Save