Merge pull request #6241 from element-hq/feature/bma/fixRedactedNotification

Ensure that redacted event from encrypted room does not trigger a fallback notification
This commit is contained in:
Benoit Marty
2026-03-03 11:43:45 +01:00
committed by GitHub
11 changed files with 68 additions and 19 deletions

View File

@@ -189,9 +189,13 @@ class LinkNewDeviceFlowNode(
is ErrorType.InvalidCheckCode -> ErrorScreenType.InsecureChannelDetected
is ErrorType.MissingSecretsBackup -> ErrorScreenType.UnknownError
is ErrorType.NotFound -> ErrorScreenType.Expired
is ErrorType.UnableToCreateDevice -> ErrorScreenType.UnknownError
is ErrorType.DeviceNotFound -> ErrorScreenType.UnknownError
is ErrorType.Unknown -> ErrorScreenType.UnknownError
is ErrorType.UnsupportedProtocol -> ErrorScreenType.UnknownError
is ErrorType.Cancelled -> ErrorScreenType.UnknownError
is ErrorType.ConnectionInsecure -> ErrorScreenType.InsecureChannelDetected
is ErrorType.Expired -> ErrorScreenType.Expired
is ErrorType.OtherDeviceAlreadySignedIn -> ErrorScreenType.UnknownError
}
// It is OK to push on backstack, since when user leaves the error screen, a new root will be set,
// or the whole flow will be popped.

View File

@@ -178,7 +178,7 @@ test_detekt_test = { module = "io.gitlab.arturbosch.detekt:detekt-test", version
# https://github.com/matrix-org/matrix-rust-components-kotlin/commits/main/sdk/sdk-android/src/main/kotlin/org/matrix/rustcomponents/sdk/matrix_sdk_ffi.kt
# All new features should not be implemented in the pull request that upgrades the version, developers should
# only fix API breaks and may add some TODOs.
matrix_sdk = "org.matrix.rustcomponents:sdk-android:26.03.0"
matrix_sdk = "org.matrix.rustcomponents:sdk-android:26.03.1"
# Others
coil = { module = "io.coil-kt.coil3:coil", version.ref = "coil" }

View File

@@ -22,6 +22,11 @@ sealed class NotificationResolverException : Exception() {
*/
data object EventFilteredOut : NotificationResolverException()
/**
* The event was found but it has been redacted.
*/
data object EventRedacted : NotificationResolverException()
/**
* An unexpected error occurred while trying to resolve the event.
*/

View File

@@ -33,13 +33,33 @@ sealed class ErrorType(message: String) : Exception(message) {
*/
class NotFound(message: String) : ErrorType(message)
/**
* The device could not be created.
*/
class UnableToCreateDevice(message: String) : ErrorType(message)
/**
* An unknown error has happened.
*/
class Unknown(message: String) : ErrorType(message)
/**
* The requested device was not returned by the homeserver.
*/
class DeviceNotFound(message: String) : ErrorType(message)
/**
* The other device is already signed in and so does not need to sign in.
*/
class OtherDeviceAlreadySignedIn(message: String) : ErrorType(message)
/**
* The sign in was cancelled.
*/
class Cancelled(message: String) : ErrorType(message)
/**
* The sign in was not completed in the required time.
*/
class Expired(message: String) : ErrorType(message)
/**
* A secure connection could not have been established between the two devices.
*/
class ConnectionInsecure(message: String) : ErrorType(message)
}

View File

@@ -15,7 +15,11 @@ internal fun HumanQrGrantLoginException.map() = when (this) {
is HumanQrGrantLoginException.InvalidCheckCode -> ErrorType.InvalidCheckCode(message.orEmpty())
is HumanQrGrantLoginException.MissingSecretsBackup -> ErrorType.MissingSecretsBackup(message.orEmpty())
is HumanQrGrantLoginException.NotFound -> ErrorType.NotFound(message.orEmpty())
is HumanQrGrantLoginException.UnableToCreateDevice -> ErrorType.UnableToCreateDevice(message.orEmpty())
is HumanQrGrantLoginException.Cancelled -> ErrorType.Cancelled(message.orEmpty())
is HumanQrGrantLoginException.ConnectionInsecure -> ErrorType.ConnectionInsecure(message.orEmpty())
is HumanQrGrantLoginException.DeviceNotFound -> ErrorType.DeviceNotFound(message.orEmpty())
is HumanQrGrantLoginException.Expired -> ErrorType.Expired(message.orEmpty())
is HumanQrGrantLoginException.OtherDeviceAlreadySignedIn -> ErrorType.OtherDeviceAlreadySignedIn(message.orEmpty())
is HumanQrGrantLoginException.Unknown -> ErrorType.Unknown(message.orEmpty())
is HumanQrGrantLoginException.UnsupportedProtocol -> ErrorType.UnsupportedProtocol(message.orEmpty())
}

View File

@@ -66,6 +66,10 @@ class RustNotificationService(
Timber.d("Could not retrieve event for notification with $eventId - event filtered out")
put(eventId, Result.failure(NotificationResolverException.EventFilteredOut))
}
NotificationStatus.EventRedacted -> {
Timber.d("Could not retrieve event for notification with $eventId - event redacted")
put(eventId, Result.failure(NotificationResolverException.EventRedacted))
}
}
}
is BatchNotificationResult.Error -> {

View File

@@ -21,11 +21,11 @@ import org.matrix.rustcomponents.sdk.TimelineDiff
import org.matrix.rustcomponents.sdk.TimelineInterface
import org.matrix.rustcomponents.sdk.TimelineListener
import timber.log.Timber
import uniffi.matrix_sdk.RoomPaginationStatus
import uniffi.matrix_sdk.PaginationStatus
internal fun TimelineInterface.liveBackPaginationStatus(): Flow<RoomPaginationStatus> = callbackFlow {
internal fun TimelineInterface.liveBackPaginationStatus(): Flow<PaginationStatus> = callbackFlow {
val listener = object : PaginationStatusListener {
override fun onUpdate(status: RoomPaginationStatus) {
override fun onUpdate(status: PaginationStatus) {
trySend(status)
}
}

View File

@@ -71,7 +71,7 @@ import org.matrix.rustcomponents.sdk.UploadParameters
import org.matrix.rustcomponents.sdk.UploadSource
import org.matrix.rustcomponents.sdk.use
import timber.log.Timber
import uniffi.matrix_sdk.RoomPaginationStatus
import uniffi.matrix_sdk.PaginationStatus
import java.io.File
import org.matrix.rustcomponents.sdk.EventOrTransactionId as RustEventOrTransactionId
import org.matrix.rustcomponents.sdk.Timeline as InnerTimeline
@@ -147,8 +147,8 @@ class RustTimeline(
.onEach { backPaginationStatus ->
updatePaginationStatus(Timeline.PaginationDirection.BACKWARDS) {
when (backPaginationStatus) {
is RoomPaginationStatus.Idle -> it.copy(isPaginating = false, hasMoreToLoad = !backPaginationStatus.hitTimelineStart)
is RoomPaginationStatus.Paginating -> it.copy(isPaginating = true, hasMoreToLoad = true)
is PaginationStatus.Idle -> it.copy(isPaginating = false, hasMoreToLoad = !backPaginationStatus.hitTimelineStart)
is PaginationStatus.Paginating -> it.copy(isPaginating = true, hasMoreToLoad = true)
}
}
}

View File

@@ -14,7 +14,7 @@ import org.matrix.rustcomponents.sdk.TaskHandle
import org.matrix.rustcomponents.sdk.Timeline
import org.matrix.rustcomponents.sdk.TimelineDiff
import org.matrix.rustcomponents.sdk.TimelineListener
import uniffi.matrix_sdk.RoomPaginationStatus
import uniffi.matrix_sdk.PaginationStatus
class FakeFfiTimeline : Timeline(NoHandle) {
private var listener: TimelineListener? = null
@@ -33,7 +33,7 @@ class FakeFfiTimeline : Timeline(NoHandle) {
return FakeFfiTaskHandle()
}
fun emitPaginationStatus(status: RoomPaginationStatus) {
fun emitPaginationStatus(status: PaginationStatus) {
paginationStatusListener!!.onUpdate(status)
}

View File

@@ -32,7 +32,7 @@ import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
import org.junit.Test
import org.matrix.rustcomponents.sdk.TimelineDiff
import uniffi.matrix_sdk.RoomPaginationStatus
import uniffi.matrix_sdk.PaginationStatus
import org.matrix.rustcomponents.sdk.Timeline as InnerTimeline
class RustTimelineTest {
@@ -68,10 +68,10 @@ class RustTimelineTest {
// Start pagination
sut.paginate(Timeline.PaginationDirection.BACKWARDS)
// Simulate SDK starting pagination
inner.emitPaginationStatus(RoomPaginationStatus.Paginating)
inner.emitPaginationStatus(PaginationStatus.Paginating)
// No new events received
// Simulate SDK stopping pagination, more event to load
inner.emitPaginationStatus(RoomPaginationStatus.Idle(hitTimelineStart = false))
inner.emitPaginationStatus(PaginationStatus.Idle(hitTimelineStart = false))
// expect an item to be emitted, with an updated timestamp
with(awaitItem()) {
assertThat(size).isEqualTo(2)

View File

@@ -124,6 +124,14 @@ class DefaultPushHandler(
sessionId = request.sessionId,
comment = "Push handled successfully but notification was filtered out",
)
} else if (exception is NotificationResolverException.EventRedacted) {
pushHistoryService.onSuccess(
providerInfo = request.providerInfo,
eventId = request.eventId,
roomId = request.roomId,
sessionId = request.sessionId,
comment = "Push handled successfully but event has been redacted",
)
} else {
val reason = when (exception) {
is NotificationResolverException.EventNotFound -> "Event not found"
@@ -155,6 +163,10 @@ class DefaultPushHandler(
// Do nothing, we don't want to show a notification for filtered out events
null
}
is NotificationResolverException.EventRedacted -> {
// Do nothing, we don't want to show a notification for redacted events
null
}
else -> {
Timber.tag(loggerTag.value).e(exception, "Failed to resolve push event")
ResolvedPushEvent.Event(