Merge pull request #3320 from element-hq/feature/bma/cleanNotificationOfRedactedEvent

Redact message on displayed notification
This commit is contained in:
Benoit Marty 2024-08-29 14:46:13 +02:00 committed by GitHub
commit 82fa369c8b
19 changed files with 644 additions and 260 deletions

View file

@ -79,8 +79,8 @@ sealed interface NotificationContent {
) : MessageLike ) : MessageLike
data class RoomRedaction( data class RoomRedaction(
val redactedEventId: String?, val redactedEventId: EventId?,
val reason: String? val reason: String?,
) : MessageLike ) : MessageLike
data object Sticker : MessageLike data object Sticker : MessageLike

View file

@ -16,6 +16,7 @@
package io.element.android.libraries.matrix.impl.notification package io.element.android.libraries.matrix.impl.notification
import io.element.android.libraries.matrix.api.core.EventId
import io.element.android.libraries.matrix.api.core.UserId import io.element.android.libraries.matrix.api.core.UserId
import io.element.android.libraries.matrix.api.notification.CallNotifyType import io.element.android.libraries.matrix.api.notification.CallNotifyType
import io.element.android.libraries.matrix.api.notification.NotificationContent import io.element.android.libraries.matrix.api.notification.NotificationContent
@ -94,7 +95,10 @@ private fun MessageLikeEventContent.toContent(senderId: UserId): NotificationCon
is MessageLikeEventContent.RoomMessage -> { is MessageLikeEventContent.RoomMessage -> {
NotificationContent.MessageLike.RoomMessage(senderId, EventMessageMapper().mapMessageType(messageType)) NotificationContent.MessageLike.RoomMessage(senderId, EventMessageMapper().mapMessageType(messageType))
} }
is MessageLikeEventContent.RoomRedaction -> NotificationContent.MessageLike.RoomRedaction(redactedEventId = redactedEventId, reason = reason) is MessageLikeEventContent.RoomRedaction -> NotificationContent.MessageLike.RoomRedaction(
redactedEventId = redactedEventId?.let(::EventId),
reason = reason,
)
MessageLikeEventContent.Sticker -> NotificationContent.MessageLike.Sticker MessageLikeEventContent.Sticker -> NotificationContent.MessageLike.Sticker
is MessageLikeEventContent.Poll -> NotificationContent.MessageLike.Poll(senderId, question) is MessageLikeEventContent.Poll -> NotificationContent.MessageLike.Poll(senderId, question)
} }

View file

@ -63,6 +63,8 @@ const val A_MESSAGE = "Hello world!"
const val A_REPLY = "OK, I'll be there!" const val A_REPLY = "OK, I'll be there!"
const val ANOTHER_MESSAGE = "Hello universe!" const val ANOTHER_MESSAGE = "Hello universe!"
const val A_REDACTION_REASON = "A redaction reason"
const val A_HOMESERVER_URL = "matrix.org" const val A_HOMESERVER_URL = "matrix.org"
const val A_HOMESERVER_URL_2 = "matrix-client.org" const val A_HOMESERVER_URL_2 = "matrix-client.org"

View file

@ -26,7 +26,6 @@ import io.element.android.libraries.push.api.notifications.NotificationIdProvide
import javax.inject.Inject import javax.inject.Inject
interface ActiveNotificationsProvider { interface ActiveNotificationsProvider {
fun getAllNotifications(): List<StatusBarNotification>
fun getMessageNotificationsForRoom(sessionId: SessionId, roomId: RoomId): List<StatusBarNotification> fun getMessageNotificationsForRoom(sessionId: SessionId, roomId: RoomId): List<StatusBarNotification>
fun getNotificationsForSession(sessionId: SessionId): List<StatusBarNotification> fun getNotificationsForSession(sessionId: SessionId): List<StatusBarNotification>
fun getMembershipNotificationForSession(sessionId: SessionId): List<StatusBarNotification> fun getMembershipNotificationForSession(sessionId: SessionId): List<StatusBarNotification>
@ -39,10 +38,6 @@ interface ActiveNotificationsProvider {
class DefaultActiveNotificationsProvider @Inject constructor( class DefaultActiveNotificationsProvider @Inject constructor(
private val notificationManager: NotificationManagerCompat, private val notificationManager: NotificationManagerCompat,
) : ActiveNotificationsProvider { ) : ActiveNotificationsProvider {
override fun getAllNotifications(): List<StatusBarNotification> {
return notificationManager.activeNotifications
}
override fun getNotificationsForSession(sessionId: SessionId): List<StatusBarNotification> { override fun getNotificationsForSession(sessionId: SessionId): List<StatusBarNotification> {
return notificationManager.activeNotifications.filter { it.notification.group == sessionId.value } return notificationManager.activeNotifications.filter { it.notification.group == sessionId.value }
} }

View file

@ -50,8 +50,8 @@ import io.element.android.libraries.matrix.ui.messages.toPlainText
import io.element.android.libraries.push.impl.R import io.element.android.libraries.push.impl.R
import io.element.android.libraries.push.impl.notifications.model.FallbackNotifiableEvent import io.element.android.libraries.push.impl.notifications.model.FallbackNotifiableEvent
import io.element.android.libraries.push.impl.notifications.model.InviteNotifiableEvent import io.element.android.libraries.push.impl.notifications.model.InviteNotifiableEvent
import io.element.android.libraries.push.impl.notifications.model.NotifiableEvent
import io.element.android.libraries.push.impl.notifications.model.NotifiableMessageEvent import io.element.android.libraries.push.impl.notifications.model.NotifiableMessageEvent
import io.element.android.libraries.push.impl.notifications.model.ResolvedPushEvent
import io.element.android.libraries.ui.strings.CommonStrings import io.element.android.libraries.ui.strings.CommonStrings
import io.element.android.services.toolbox.api.strings.StringProvider import io.element.android.services.toolbox.api.strings.StringProvider
import io.element.android.services.toolbox.api.systemclock.SystemClock import io.element.android.services.toolbox.api.systemclock.SystemClock
@ -67,7 +67,7 @@ private val loggerTag = LoggerTag("DefaultNotifiableEventResolver", LoggerTag.No
* this pattern allow decoupling between the object responsible of displaying notifications and the matrix sdk. * this pattern allow decoupling between the object responsible of displaying notifications and the matrix sdk.
*/ */
interface NotifiableEventResolver { interface NotifiableEventResolver {
suspend fun resolveEvent(sessionId: SessionId, roomId: RoomId, eventId: EventId): NotifiableEvent? suspend fun resolveEvent(sessionId: SessionId, roomId: RoomId, eventId: EventId): ResolvedPushEvent?
} }
@ContributesBinding(AppScope::class) @ContributesBinding(AppScope::class)
@ -80,7 +80,7 @@ class DefaultNotifiableEventResolver @Inject constructor(
private val permalinkParser: PermalinkParser, private val permalinkParser: PermalinkParser,
private val callNotificationEventResolver: CallNotificationEventResolver, private val callNotificationEventResolver: CallNotificationEventResolver,
) : NotifiableEventResolver { ) : NotifiableEventResolver {
override suspend fun resolveEvent(sessionId: SessionId, roomId: RoomId, eventId: EventId): NotifiableEvent? { override suspend fun resolveEvent(sessionId: SessionId, roomId: RoomId, eventId: EventId): ResolvedPushEvent? {
// Restore session // Restore session
val client = matrixClientProvider.getOrRestore(sessionId).getOrNull() ?: return null val client = matrixClientProvider.getOrRestore(sessionId).getOrNull() ?: return null
val notificationService = client.notificationService() val notificationService = client.notificationService()
@ -99,8 +99,9 @@ class DefaultNotifiableEventResolver @Inject constructor(
private suspend fun NotificationData.asNotifiableEvent( private suspend fun NotificationData.asNotifiableEvent(
client: MatrixClient, client: MatrixClient,
userId: SessionId, userId: SessionId,
): NotifiableEvent? { ): ResolvedPushEvent? {
return when (val content = this.content) { val content = this.content
val notifiableEvent = when (content) {
is NotificationContent.MessageLike.RoomMessage -> { is NotificationContent.MessageLike.RoomMessage -> {
val senderDisambiguatedDisplayName = getDisambiguatedDisplayName(content.senderId) val senderDisambiguatedDisplayName = getDisambiguatedDisplayName(content.senderId)
val messageBody = descriptionFromMessageContent(content, senderDisambiguatedDisplayName) val messageBody = descriptionFromMessageContent(content, senderDisambiguatedDisplayName)
@ -204,8 +205,9 @@ class DefaultNotifiableEventResolver @Inject constructor(
NotificationContent.MessageLike.RoomEncrypted -> fallbackNotifiableEvent(userId, roomId, eventId).also { NotificationContent.MessageLike.RoomEncrypted -> fallbackNotifiableEvent(userId, roomId, eventId).also {
Timber.tag(loggerTag.value).w("Notification with encrypted content -> fallback") Timber.tag(loggerTag.value).w("Notification with encrypted content -> fallback")
} }
is NotificationContent.MessageLike.RoomRedaction -> null.also { is NotificationContent.MessageLike.RoomRedaction -> {
Timber.tag(loggerTag.value).d("Ignoring notification for redaction") // Note: this case will be handled below
null
} }
NotificationContent.MessageLike.Sticker -> null.also { NotificationContent.MessageLike.Sticker -> null.also {
Timber.tag(loggerTag.value).d("Ignoring notification for sticker") Timber.tag(loggerTag.value).d("Ignoring notification for sticker")
@ -233,6 +235,25 @@ class DefaultNotifiableEventResolver @Inject constructor(
Timber.tag(loggerTag.value).d("Ignoring notification for state event ${content.javaClass.simpleName}") Timber.tag(loggerTag.value).d("Ignoring notification for state event ${content.javaClass.simpleName}")
} }
} }
return if (notifiableEvent != null) {
ResolvedPushEvent.Event(notifiableEvent)
} else if (content is NotificationContent.MessageLike.RoomRedaction) {
val redactedEventId = content.redactedEventId
if (redactedEventId == null) {
Timber.tag(loggerTag.value).d("redactedEventId is null.")
null
} else {
ResolvedPushEvent.Redaction(
sessionId = userId,
roomId = roomId,
redactedEventId = redactedEventId,
reason = content.reason,
)
}
} else {
null
}
} }
private fun fallbackNotifiableEvent( private fun fallbackNotifiableEvent(

View file

@ -42,77 +42,75 @@ class NotificationRenderer @Inject constructor(
imageLoader: ImageLoader, imageLoader: ImageLoader,
) { ) {
val groupedEvents = eventsToProcess.groupByType() val groupedEvents = eventsToProcess.groupByType()
with(notificationDataFactory) { val roomNotifications = notificationDataFactory.toNotifications(groupedEvents.roomEvents, currentUser, imageLoader)
val roomNotifications = toNotifications(groupedEvents.roomEvents, currentUser, imageLoader) val invitationNotifications = notificationDataFactory.toNotifications(groupedEvents.invitationEvents)
val invitationNotifications = toNotifications(groupedEvents.invitationEvents) val simpleNotifications = notificationDataFactory.toNotifications(groupedEvents.simpleEvents)
val simpleNotifications = toNotifications(groupedEvents.simpleEvents) val fallbackNotifications = notificationDataFactory.toNotifications(groupedEvents.fallbackEvents)
val fallbackNotifications = toNotifications(groupedEvents.fallbackEvents) val summaryNotification = notificationDataFactory.createSummaryNotification(
val summaryNotification = createSummaryNotification( currentUser = currentUser,
currentUser = currentUser, roomNotifications = roomNotifications,
roomNotifications = roomNotifications, invitationNotifications = invitationNotifications,
invitationNotifications = invitationNotifications, simpleNotifications = simpleNotifications,
simpleNotifications = simpleNotifications, fallbackNotifications = fallbackNotifications,
fallbackNotifications = fallbackNotifications, )
// Remove summary first to avoid briefly displaying it after dismissing the last notification
if (summaryNotification == SummaryNotification.Removed) {
Timber.tag(loggerTag.value).d("Removing summary notification")
notificationDisplayer.cancelNotificationMessage(
tag = null,
id = NotificationIdProvider.getSummaryNotificationId(currentUser.userId)
) )
}
// Remove summary first to avoid briefly displaying it after dismissing the last notification roomNotifications.forEach { notificationData ->
if (summaryNotification == SummaryNotification.Removed) { notificationDisplayer.showNotificationMessage(
Timber.tag(loggerTag.value).d("Removing summary notification") tag = notificationData.roomId.value,
notificationDisplayer.cancelNotificationMessage( id = NotificationIdProvider.getRoomMessagesNotificationId(currentUser.userId),
tag = null, notification = notificationData.notification
id = NotificationIdProvider.getSummaryNotificationId(currentUser.userId) )
) }
}
roomNotifications.forEach { notificationData -> invitationNotifications.forEach { notificationData ->
if (useCompleteNotificationFormat) {
Timber.tag(loggerTag.value).d("Updating invitation notification ${notificationData.key}")
notificationDisplayer.showNotificationMessage( notificationDisplayer.showNotificationMessage(
tag = notificationData.roomId.value, tag = notificationData.key,
id = NotificationIdProvider.getRoomMessagesNotificationId(currentUser.userId), id = NotificationIdProvider.getRoomInvitationNotificationId(currentUser.userId),
notification = notificationData.notification notification = notificationData.notification
) )
} }
}
invitationNotifications.forEach { notificationData -> simpleNotifications.forEach { notificationData ->
if (useCompleteNotificationFormat) { if (useCompleteNotificationFormat) {
Timber.tag(loggerTag.value).d("Updating invitation notification ${notificationData.key}") Timber.tag(loggerTag.value).d("Updating simple notification ${notificationData.key}")
notificationDisplayer.showNotificationMessage(
tag = notificationData.key,
id = NotificationIdProvider.getRoomInvitationNotificationId(currentUser.userId),
notification = notificationData.notification
)
}
}
simpleNotifications.forEach { notificationData ->
if (useCompleteNotificationFormat) {
Timber.tag(loggerTag.value).d("Updating simple notification ${notificationData.key}")
notificationDisplayer.showNotificationMessage(
tag = notificationData.key,
id = NotificationIdProvider.getRoomEventNotificationId(currentUser.userId),
notification = notificationData.notification
)
}
}
// Show only the first fallback notification
if (fallbackNotifications.isNotEmpty()) {
Timber.tag(loggerTag.value).d("Showing fallback notification")
notificationDisplayer.showNotificationMessage( notificationDisplayer.showNotificationMessage(
tag = "FALLBACK", tag = notificationData.key,
id = NotificationIdProvider.getFallbackNotificationId(currentUser.userId), id = NotificationIdProvider.getRoomEventNotificationId(currentUser.userId),
notification = fallbackNotifications.first().notification notification = notificationData.notification
) )
} }
}
// Update summary last to avoid briefly displaying it before other notifications // Show only the first fallback notification
if (summaryNotification is SummaryNotification.Update) { if (fallbackNotifications.isNotEmpty()) {
Timber.tag(loggerTag.value).d("Updating summary notification") Timber.tag(loggerTag.value).d("Showing fallback notification")
notificationDisplayer.showNotificationMessage( notificationDisplayer.showNotificationMessage(
tag = null, tag = "FALLBACK",
id = NotificationIdProvider.getSummaryNotificationId(currentUser.userId), id = NotificationIdProvider.getFallbackNotificationId(currentUser.userId),
notification = summaryNotification.notification notification = fallbackNotifications.first().notification
) )
} }
// Update summary last to avoid briefly displaying it before other notifications
if (summaryNotification is SummaryNotification.Update) {
Timber.tag(loggerTag.value).d("Updating summary notification")
notificationDisplayer.showNotificationMessage(
tag = null,
id = NotificationIdProvider.getSummaryNotificationId(currentUser.userId),
notification = summaryNotification.notification
)
} }
} }
} }

View file

@ -430,6 +430,7 @@ class DefaultNotificationCreator @Inject constructor(
event.imageUri?.let { event.imageUri?.let {
message.setData("image/", it) message.setData("image/", it)
} }
message.extras.putString(MESSAGE_EVENT_ID, event.eventId.value)
} }
addMessage(message) addMessage(message)
} }
@ -446,10 +447,10 @@ class DefaultNotificationCreator @Inject constructor(
): MessagingStyle { ): MessagingStyle {
return MessagingStyle( return MessagingStyle(
Person.Builder() Person.Builder()
.setName(user.displayName?.annotateForDebug(50)) .setName(user.displayName?.annotateForDebug(50))
.setIcon(bitmapLoader.getUserIcon(user.avatarUrl, imageLoader)) .setIcon(bitmapLoader.getUserIcon(user.avatarUrl, imageLoader))
.setKey(sessionId.value) .setKey(sessionId.value)
.build() .build()
).also { ).also {
it.conversationTitle = roomName.takeIf { roomIsGroup } it.conversationTitle = roomName.takeIf { roomIsGroup }
it.isGroupConversation = roomIsGroup it.isGroupConversation = roomIsGroup
@ -465,6 +466,10 @@ class DefaultNotificationCreator @Inject constructor(
drawable.draw(canvas) drawable.draw(canvas)
return bitmap return bitmap
} }
companion object {
const val MESSAGE_EVENT_ID = "message_event_id"
}
} }
fun NotifiableMessageEvent.isSmartReplyError() = outGoingMessage && outGoingMessageFailed fun NotifiableMessageEvent.isSmartReplyError() = outGoingMessage && outGoingMessageFailed

View file

@ -19,12 +19,11 @@ package io.element.android.libraries.push.impl.notifications.model
import io.element.android.libraries.matrix.api.core.EventId import io.element.android.libraries.matrix.api.core.EventId
import io.element.android.libraries.matrix.api.core.RoomId import io.element.android.libraries.matrix.api.core.RoomId
import io.element.android.libraries.matrix.api.core.SessionId import io.element.android.libraries.matrix.api.core.SessionId
import java.io.Serializable
/** /**
* Parent interface for all events which can be displayed as a Notification. * Parent interface for all events which can be displayed as a Notification.
*/ */
sealed interface NotifiableEvent : Serializable { sealed interface NotifiableEvent {
val sessionId: SessionId val sessionId: SessionId
val roomId: RoomId val roomId: RoomId
val eventId: EventId val eventId: EventId

View file

@ -0,0 +1,32 @@
/*
* 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
*
* https://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.push.impl.notifications.model
import io.element.android.libraries.matrix.api.core.EventId
import io.element.android.libraries.matrix.api.core.RoomId
import io.element.android.libraries.matrix.api.core.SessionId
sealed interface ResolvedPushEvent {
data class Event(val notifiableEvent: NotifiableEvent) : ResolvedPushEvent
data class Redaction(
val sessionId: SessionId,
val roomId: RoomId,
val redactedEventId: EventId,
val reason: String?,
) : ResolvedPushEvent
}

View file

@ -26,6 +26,7 @@ import io.element.android.libraries.matrix.api.auth.MatrixAuthenticationService
import io.element.android.libraries.push.impl.notifications.NotifiableEventResolver import io.element.android.libraries.push.impl.notifications.NotifiableEventResolver
import io.element.android.libraries.push.impl.notifications.channels.NotificationChannels import io.element.android.libraries.push.impl.notifications.channels.NotificationChannels
import io.element.android.libraries.push.impl.notifications.model.NotifiableRingingCallEvent import io.element.android.libraries.push.impl.notifications.model.NotifiableRingingCallEvent
import io.element.android.libraries.push.impl.notifications.model.ResolvedPushEvent
import io.element.android.libraries.push.impl.test.DefaultTestPush import io.element.android.libraries.push.impl.test.DefaultTestPush
import io.element.android.libraries.push.impl.troubleshoot.DiagnosticPushHandler import io.element.android.libraries.push.impl.troubleshoot.DiagnosticPushHandler
import io.element.android.libraries.pushproviders.api.PushData import io.element.android.libraries.pushproviders.api.PushData
@ -41,6 +42,7 @@ private val loggerTag = LoggerTag("PushHandler", LoggerTag.PushLoggerTag)
@ContributesBinding(AppScope::class) @ContributesBinding(AppScope::class)
class DefaultPushHandler @Inject constructor( class DefaultPushHandler @Inject constructor(
private val onNotifiableEventReceived: OnNotifiableEventReceived, private val onNotifiableEventReceived: OnNotifiableEventReceived,
private val onRedactedEventReceived: OnRedactedEventReceived,
private val notifiableEventResolver: NotifiableEventResolver, private val notifiableEventResolver: NotifiableEventResolver,
private val incrementPushDataStore: IncrementPushDataStore, private val incrementPushDataStore: IncrementPushDataStore,
private val userPushStoreFactory: UserPushStoreFactory, private val userPushStoreFactory: UserPushStoreFactory,
@ -96,19 +98,26 @@ class DefaultPushHandler @Inject constructor(
Timber.w("Unable to get a session") Timber.w("Unable to get a session")
return return
} }
val notifiableEvent = notifiableEventResolver.resolveEvent(userId, pushData.roomId, pushData.eventId) val resolvedPushEvent = notifiableEventResolver.resolveEvent(userId, pushData.roomId, pushData.eventId)
when (notifiableEvent) { when (resolvedPushEvent) {
null -> Timber.tag(loggerTag.value).w("Unable to get a notification data") null -> Timber.tag(loggerTag.value).w("Unable to get a notification data")
is NotifiableRingingCallEvent -> handleRingingCallEvent(notifiableEvent) is ResolvedPushEvent.Event -> {
else -> { when (resolvedPushEvent.notifiableEvent) {
val userPushStore = userPushStoreFactory.getOrCreate(userId) is NotifiableRingingCallEvent -> handleRingingCallEvent(resolvedPushEvent.notifiableEvent)
val areNotificationsEnabled = userPushStore.getNotificationEnabledForDevice().first() else -> {
if (areNotificationsEnabled) { val userPushStore = userPushStoreFactory.getOrCreate(userId)
onNotifiableEventReceived.onNotifiableEventReceived(notifiableEvent) val areNotificationsEnabled = userPushStore.getNotificationEnabledForDevice().first()
} else { if (areNotificationsEnabled) {
Timber.tag(loggerTag.value).i("Notification are disabled for this device, ignore push.") onNotifiableEventReceived.onNotifiableEventReceived(resolvedPushEvent.notifiableEvent)
} else {
Timber.tag(loggerTag.value).i("Notification are disabled for this device, ignore push.")
}
}
} }
} }
is ResolvedPushEvent.Redaction -> {
onRedactedEventReceived.onRedactedEventReceived(resolvedPushEvent)
}
} }
} catch (e: Exception) { } catch (e: Exception) {
Timber.tag(loggerTag.value).e(e, "## handleInternal() failed") Timber.tag(loggerTag.value).e(e, "## handleInternal() failed")

View file

@ -0,0 +1,97 @@
/*
* 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.push.impl.push
import android.content.Context
import android.graphics.Typeface
import android.text.style.StyleSpan
import androidx.core.app.NotificationCompat
import androidx.core.app.NotificationCompat.MessagingStyle
import androidx.core.text.buildSpannedString
import androidx.core.text.inSpans
import com.squareup.anvil.annotations.ContributesBinding
import io.element.android.libraries.di.AppScope
import io.element.android.libraries.di.ApplicationContext
import io.element.android.libraries.push.impl.notifications.ActiveNotificationsProvider
import io.element.android.libraries.push.impl.notifications.NotificationDisplayer
import io.element.android.libraries.push.impl.notifications.factories.DefaultNotificationCreator
import io.element.android.libraries.push.impl.notifications.model.ResolvedPushEvent
import io.element.android.libraries.ui.strings.CommonStrings
import io.element.android.services.toolbox.api.strings.StringProvider
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.launch
import timber.log.Timber
import javax.inject.Inject
interface OnRedactedEventReceived {
fun onRedactedEventReceived(redaction: ResolvedPushEvent.Redaction)
}
@ContributesBinding(AppScope::class)
class DefaultOnRedactedEventReceived @Inject constructor(
private val activeNotificationsProvider: ActiveNotificationsProvider,
private val notificationDisplayer: NotificationDisplayer,
private val coroutineScope: CoroutineScope,
@ApplicationContext private val context: Context,
private val stringProvider: StringProvider,
) : OnRedactedEventReceived {
override fun onRedactedEventReceived(redaction: ResolvedPushEvent.Redaction) {
coroutineScope.launch {
val notifications = activeNotificationsProvider.getMessageNotificationsForRoom(
redaction.sessionId,
redaction.roomId,
)
if (notifications.isEmpty()) {
Timber.d("No notifications found for redacted event")
}
notifications.forEach { statusBarNotification ->
val notification = statusBarNotification.notification
val messagingStyle = MessagingStyle.extractMessagingStyleFromNotification(notification)
if (messagingStyle == null) {
Timber.w("Unable to retrieve messaging style from notification")
return@forEach
}
val messageToRedactIndex = messagingStyle.messages.indexOfFirst { message ->
message.extras.getString(DefaultNotificationCreator.MESSAGE_EVENT_ID) == redaction.redactedEventId.value
}
if (messageToRedactIndex == -1) {
Timber.d("Unable to find the message to remove from notification")
return@forEach
}
val oldMessage = messagingStyle.messages[messageToRedactIndex]
val content = buildSpannedString {
inSpans(StyleSpan(Typeface.ITALIC)) {
append(stringProvider.getString(CommonStrings.common_message_removed))
}
}
val newMessage = MessagingStyle.Message(
content,
oldMessage.timestamp,
oldMessage.person
)
messagingStyle.messages[messageToRedactIndex] = newMessage
notificationDisplayer.showNotificationMessage(
statusBarNotification.tag,
statusBarNotification.id,
NotificationCompat.Builder(context, notification)
.setStyle(messagingStyle)
.build()
)
}
}
}
}

View file

@ -36,27 +36,6 @@ import org.robolectric.RobolectricTestRunner
class DefaultActiveNotificationsProviderTest { class DefaultActiveNotificationsProviderTest {
private val notificationIdProvider = NotificationIdProvider private val notificationIdProvider = NotificationIdProvider
@Test
fun `getAllNotifications with no active notifications returns empty list`() {
val activeNotificationsProvider = createActiveNotificationsProvider(activeNotifications = emptyList())
val emptyNotifications = activeNotificationsProvider.getAllNotifications()
assertThat(emptyNotifications).isEmpty()
}
@Test
fun `getAllNotifications with active notifications returns all`() {
val activeNotifications = listOf(
aStatusBarNotification(id = notificationIdProvider.getRoomMessagesNotificationId(A_SESSION_ID), groupId = A_SESSION_ID.value),
aStatusBarNotification(id = notificationIdProvider.getSummaryNotificationId(A_SESSION_ID), groupId = A_SESSION_ID.value),
aStatusBarNotification(id = notificationIdProvider.getRoomInvitationNotificationId(A_SESSION_ID), groupId = A_SESSION_ID.value),
)
val activeNotificationsProvider = createActiveNotificationsProvider(activeNotifications = activeNotifications)
val result = activeNotificationsProvider.getAllNotifications()
assertThat(result).hasSize(3)
}
@Test @Test
fun `getNotificationsForSession returns only notifications for that session id`() { fun `getNotificationsForSession returns only notifications for that session id`() {
val activeNotifications = listOf( val activeNotifications = listOf(

View file

@ -40,6 +40,7 @@ import io.element.android.libraries.matrix.api.timeline.item.event.VoiceMessageT
import io.element.android.libraries.matrix.test.AN_EVENT_ID import io.element.android.libraries.matrix.test.AN_EVENT_ID
import io.element.android.libraries.matrix.test.AN_EVENT_ID_2 import io.element.android.libraries.matrix.test.AN_EVENT_ID_2
import io.element.android.libraries.matrix.test.AN_EXCEPTION import io.element.android.libraries.matrix.test.AN_EXCEPTION
import io.element.android.libraries.matrix.test.A_REDACTION_REASON
import io.element.android.libraries.matrix.test.A_ROOM_ID import io.element.android.libraries.matrix.test.A_ROOM_ID
import io.element.android.libraries.matrix.test.A_SESSION_ID import io.element.android.libraries.matrix.test.A_SESSION_ID
import io.element.android.libraries.matrix.test.A_USER_ID_2 import io.element.android.libraries.matrix.test.A_USER_ID_2
@ -52,6 +53,7 @@ import io.element.android.libraries.push.impl.notifications.model.FallbackNotifi
import io.element.android.libraries.push.impl.notifications.model.InviteNotifiableEvent import io.element.android.libraries.push.impl.notifications.model.InviteNotifiableEvent
import io.element.android.libraries.push.impl.notifications.model.NotifiableMessageEvent import io.element.android.libraries.push.impl.notifications.model.NotifiableMessageEvent
import io.element.android.libraries.push.impl.notifications.model.NotifiableRingingCallEvent import io.element.android.libraries.push.impl.notifications.model.NotifiableRingingCallEvent
import io.element.android.libraries.push.impl.notifications.model.ResolvedPushEvent
import io.element.android.services.toolbox.impl.strings.AndroidStringProvider import io.element.android.services.toolbox.impl.strings.AndroidStringProvider
import io.element.android.services.toolbox.impl.systemclock.DefaultSystemClock import io.element.android.services.toolbox.impl.systemclock.DefaultSystemClock
import io.element.android.services.toolbox.test.systemclock.A_FAKE_TIMESTAMP import io.element.android.services.toolbox.test.systemclock.A_FAKE_TIMESTAMP
@ -104,7 +106,9 @@ class DefaultNotifiableEventResolverTest {
) )
) )
val result = sut.resolveEvent(A_SESSION_ID, A_ROOM_ID, AN_EVENT_ID) val result = sut.resolveEvent(A_SESSION_ID, A_ROOM_ID, AN_EVENT_ID)
val expectedResult = createNotifiableMessageEvent(body = "Hello world") val expectedResult = ResolvedPushEvent.Event(
createNotifiableMessageEvent(body = "Hello world")
)
assertThat(result).isEqualTo(expectedResult) assertThat(result).isEqualTo(expectedResult)
} }
@ -123,7 +127,9 @@ class DefaultNotifiableEventResolverTest {
) )
) )
val result = sut.resolveEvent(A_SESSION_ID, A_ROOM_ID, AN_EVENT_ID) val result = sut.resolveEvent(A_SESSION_ID, A_ROOM_ID, AN_EVENT_ID)
val expectedResult = createNotifiableMessageEvent(body = "Hello world", hasMentionOrReply = true) val expectedResult = ResolvedPushEvent.Event(
createNotifiableMessageEvent(body = "Hello world", hasMentionOrReply = true)
)
assertThat(result).isEqualTo(expectedResult) assertThat(result).isEqualTo(expectedResult)
} }
@ -146,7 +152,9 @@ class DefaultNotifiableEventResolverTest {
) )
) )
val result = sut.resolveEvent(A_SESSION_ID, A_ROOM_ID, AN_EVENT_ID) val result = sut.resolveEvent(A_SESSION_ID, A_ROOM_ID, AN_EVENT_ID)
val expectedResult = createNotifiableMessageEvent(body = "Hello world") val expectedResult = ResolvedPushEvent.Event(
createNotifiableMessageEvent(body = "Hello world")
)
assertThat(result).isEqualTo(expectedResult) assertThat(result).isEqualTo(expectedResult)
} }
@ -169,7 +177,9 @@ class DefaultNotifiableEventResolverTest {
) )
) )
val result = sut.resolveEvent(A_SESSION_ID, A_ROOM_ID, AN_EVENT_ID) val result = sut.resolveEvent(A_SESSION_ID, A_ROOM_ID, AN_EVENT_ID)
val expectedResult = createNotifiableMessageEvent(body = "Hello world") val expectedResult = ResolvedPushEvent.Event(
createNotifiableMessageEvent(body = "Hello world")
)
assertThat(result).isEqualTo(expectedResult) assertThat(result).isEqualTo(expectedResult)
} }
@ -186,7 +196,9 @@ class DefaultNotifiableEventResolverTest {
) )
) )
val result = sut.resolveEvent(A_SESSION_ID, A_ROOM_ID, AN_EVENT_ID) val result = sut.resolveEvent(A_SESSION_ID, A_ROOM_ID, AN_EVENT_ID)
val expectedResult = createNotifiableMessageEvent(body = "Audio") val expectedResult = ResolvedPushEvent.Event(
createNotifiableMessageEvent(body = "Audio")
)
assertThat(result).isEqualTo(expectedResult) assertThat(result).isEqualTo(expectedResult)
} }
@ -203,7 +215,9 @@ class DefaultNotifiableEventResolverTest {
) )
) )
val result = sut.resolveEvent(A_SESSION_ID, A_ROOM_ID, AN_EVENT_ID) val result = sut.resolveEvent(A_SESSION_ID, A_ROOM_ID, AN_EVENT_ID)
val expectedResult = createNotifiableMessageEvent(body = "Video") val expectedResult = ResolvedPushEvent.Event(
createNotifiableMessageEvent(body = "Video")
)
assertThat(result).isEqualTo(expectedResult) assertThat(result).isEqualTo(expectedResult)
} }
@ -220,7 +234,9 @@ class DefaultNotifiableEventResolverTest {
) )
) )
val result = sut.resolveEvent(A_SESSION_ID, A_ROOM_ID, AN_EVENT_ID) val result = sut.resolveEvent(A_SESSION_ID, A_ROOM_ID, AN_EVENT_ID)
val expectedResult = createNotifiableMessageEvent(body = "Voice message") val expectedResult = ResolvedPushEvent.Event(
createNotifiableMessageEvent(body = "Voice message")
)
assertThat(result).isEqualTo(expectedResult) assertThat(result).isEqualTo(expectedResult)
} }
@ -237,7 +253,9 @@ class DefaultNotifiableEventResolverTest {
) )
) )
val result = sut.resolveEvent(A_SESSION_ID, A_ROOM_ID, AN_EVENT_ID) val result = sut.resolveEvent(A_SESSION_ID, A_ROOM_ID, AN_EVENT_ID)
val expectedResult = createNotifiableMessageEvent(body = "Image") val expectedResult = ResolvedPushEvent.Event(
createNotifiableMessageEvent(body = "Image")
)
assertThat(result).isEqualTo(expectedResult) assertThat(result).isEqualTo(expectedResult)
} }
@ -254,7 +272,9 @@ class DefaultNotifiableEventResolverTest {
) )
) )
val result = sut.resolveEvent(A_SESSION_ID, A_ROOM_ID, AN_EVENT_ID) val result = sut.resolveEvent(A_SESSION_ID, A_ROOM_ID, AN_EVENT_ID)
val expectedResult = createNotifiableMessageEvent(body = "Sticker") val expectedResult = ResolvedPushEvent.Event(
createNotifiableMessageEvent(body = "Sticker")
)
assertThat(result).isEqualTo(expectedResult) assertThat(result).isEqualTo(expectedResult)
} }
@ -271,7 +291,9 @@ class DefaultNotifiableEventResolverTest {
) )
) )
val result = sut.resolveEvent(A_SESSION_ID, A_ROOM_ID, AN_EVENT_ID) val result = sut.resolveEvent(A_SESSION_ID, A_ROOM_ID, AN_EVENT_ID)
val expectedResult = createNotifiableMessageEvent(body = "File") val expectedResult = ResolvedPushEvent.Event(
createNotifiableMessageEvent(body = "File")
)
assertThat(result).isEqualTo(expectedResult) assertThat(result).isEqualTo(expectedResult)
} }
@ -288,7 +310,9 @@ class DefaultNotifiableEventResolverTest {
) )
) )
val result = sut.resolveEvent(A_SESSION_ID, A_ROOM_ID, AN_EVENT_ID) val result = sut.resolveEvent(A_SESSION_ID, A_ROOM_ID, AN_EVENT_ID)
val expectedResult = createNotifiableMessageEvent(body = "Location") val expectedResult = ResolvedPushEvent.Event(
createNotifiableMessageEvent(body = "Location")
)
assertThat(result).isEqualTo(expectedResult) assertThat(result).isEqualTo(expectedResult)
} }
@ -305,7 +329,9 @@ class DefaultNotifiableEventResolverTest {
) )
) )
val result = sut.resolveEvent(A_SESSION_ID, A_ROOM_ID, AN_EVENT_ID) val result = sut.resolveEvent(A_SESSION_ID, A_ROOM_ID, AN_EVENT_ID)
val expectedResult = createNotifiableMessageEvent(body = "Notice") val expectedResult = ResolvedPushEvent.Event(
createNotifiableMessageEvent(body = "Notice")
)
assertThat(result).isEqualTo(expectedResult) assertThat(result).isEqualTo(expectedResult)
} }
@ -322,7 +348,9 @@ class DefaultNotifiableEventResolverTest {
) )
) )
val result = sut.resolveEvent(A_SESSION_ID, A_ROOM_ID, AN_EVENT_ID) val result = sut.resolveEvent(A_SESSION_ID, A_ROOM_ID, AN_EVENT_ID)
val expectedResult = createNotifiableMessageEvent(body = "* Bob is happy") val expectedResult = ResolvedPushEvent.Event(
createNotifiableMessageEvent(body = "* Bob is happy")
)
assertThat(result).isEqualTo(expectedResult) assertThat(result).isEqualTo(expectedResult)
} }
@ -339,7 +367,9 @@ class DefaultNotifiableEventResolverTest {
) )
) )
val result = sut.resolveEvent(A_SESSION_ID, A_ROOM_ID, AN_EVENT_ID) val result = sut.resolveEvent(A_SESSION_ID, A_ROOM_ID, AN_EVENT_ID)
val expectedResult = createNotifiableMessageEvent(body = "Poll: A question") val expectedResult = ResolvedPushEvent.Event(
createNotifiableMessageEvent(body = "Poll: A question")
)
assertThat(result).isEqualTo(expectedResult) assertThat(result).isEqualTo(expectedResult)
} }
@ -357,21 +387,23 @@ class DefaultNotifiableEventResolverTest {
) )
) )
val result = sut.resolveEvent(A_SESSION_ID, A_ROOM_ID, AN_EVENT_ID) val result = sut.resolveEvent(A_SESSION_ID, A_ROOM_ID, AN_EVENT_ID)
val expectedResult = InviteNotifiableEvent( val expectedResult = ResolvedPushEvent.Event(
sessionId = A_SESSION_ID, InviteNotifiableEvent(
roomId = A_ROOM_ID, sessionId = A_SESSION_ID,
eventId = AN_EVENT_ID, roomId = A_ROOM_ID,
editedEventId = null, eventId = AN_EVENT_ID,
canBeReplaced = true, editedEventId = null,
roomName = null, canBeReplaced = true,
noisy = false, roomName = null,
title = null, noisy = false,
description = "Invited you to join the room", title = null,
type = null, description = "Invited you to join the room",
timestamp = A_TIMESTAMP, type = null,
soundName = null, timestamp = A_TIMESTAMP,
isRedacted = false, soundName = null,
isUpdated = false, isRedacted = false,
isUpdated = false,
)
) )
assertThat(result).isEqualTo(expectedResult) assertThat(result).isEqualTo(expectedResult)
} }
@ -390,21 +422,23 @@ class DefaultNotifiableEventResolverTest {
) )
) )
val result = sut.resolveEvent(A_SESSION_ID, A_ROOM_ID, AN_EVENT_ID) val result = sut.resolveEvent(A_SESSION_ID, A_ROOM_ID, AN_EVENT_ID)
val expectedResult = InviteNotifiableEvent( val expectedResult = ResolvedPushEvent.Event(
sessionId = A_SESSION_ID, InviteNotifiableEvent(
roomId = A_ROOM_ID, sessionId = A_SESSION_ID,
eventId = AN_EVENT_ID, roomId = A_ROOM_ID,
editedEventId = null, eventId = AN_EVENT_ID,
canBeReplaced = true, editedEventId = null,
roomName = null, canBeReplaced = true,
noisy = false, roomName = null,
title = null, noisy = false,
description = "Invited you to chat", title = null,
type = null, description = "Invited you to chat",
timestamp = A_TIMESTAMP, type = null,
soundName = null, timestamp = A_TIMESTAMP,
isRedacted = false, soundName = null,
isUpdated = false, isRedacted = false,
isUpdated = false,
)
) )
assertThat(result).isEqualTo(expectedResult) assertThat(result).isEqualTo(expectedResult)
} }
@ -435,16 +469,18 @@ class DefaultNotifiableEventResolverTest {
) )
) )
val result = sut.resolveEvent(A_SESSION_ID, A_ROOM_ID, AN_EVENT_ID) val result = sut.resolveEvent(A_SESSION_ID, A_ROOM_ID, AN_EVENT_ID)
val expectedResult = FallbackNotifiableEvent( val expectedResult = ResolvedPushEvent.Event(
sessionId = A_SESSION_ID, FallbackNotifiableEvent(
roomId = A_ROOM_ID, sessionId = A_SESSION_ID,
eventId = AN_EVENT_ID, roomId = A_ROOM_ID,
editedEventId = null, eventId = AN_EVENT_ID,
description = "Notification", editedEventId = null,
canBeReplaced = true, description = "Notification",
isRedacted = false, canBeReplaced = true,
isUpdated = false, isRedacted = false,
timestamp = A_FAKE_TIMESTAMP, isUpdated = false,
timestamp = A_FAKE_TIMESTAMP,
)
) )
assertThat(result).isEqualTo(expectedResult) assertThat(result).isEqualTo(expectedResult)
} }
@ -459,27 +495,29 @@ class DefaultNotifiableEventResolverTest {
) )
) )
val result = sut.resolveEvent(A_SESSION_ID, A_ROOM_ID, AN_EVENT_ID) val result = sut.resolveEvent(A_SESSION_ID, A_ROOM_ID, AN_EVENT_ID)
val expectedResult = NotifiableMessageEvent( val expectedResult = ResolvedPushEvent.Event(
sessionId = A_SESSION_ID, NotifiableMessageEvent(
roomId = A_ROOM_ID, sessionId = A_SESSION_ID,
eventId = AN_EVENT_ID, roomId = A_ROOM_ID,
editedEventId = null, eventId = AN_EVENT_ID,
canBeReplaced = false, editedEventId = null,
senderId = A_USER_ID_2, canBeReplaced = false,
noisy = false, senderId = A_USER_ID_2,
timestamp = A_TIMESTAMP, noisy = false,
senderDisambiguatedDisplayName = "Bob", timestamp = A_TIMESTAMP,
body = "Call in progress (unsupported)", senderDisambiguatedDisplayName = "Bob",
imageUriString = null, body = "Call in progress (unsupported)",
threadId = null, imageUriString = null,
roomName = null, threadId = null,
roomAvatarPath = null, roomName = null,
senderAvatarPath = null, roomAvatarPath = null,
soundName = null, senderAvatarPath = null,
outGoingMessage = false, soundName = null,
outGoingMessageFailed = false, outGoingMessage = false,
isRedacted = false, outGoingMessageFailed = false,
isUpdated = false isRedacted = false,
isUpdated = false
)
) )
assertThat(result).isEqualTo(expectedResult) assertThat(result).isEqualTo(expectedResult)
} }
@ -498,21 +536,23 @@ class DefaultNotifiableEventResolverTest {
) )
) )
) )
val expectedResult = NotifiableRingingCallEvent( val expectedResult = ResolvedPushEvent.Event(
sessionId = A_SESSION_ID, NotifiableRingingCallEvent(
roomId = A_ROOM_ID, sessionId = A_SESSION_ID,
eventId = AN_EVENT_ID, roomId = A_ROOM_ID,
senderId = A_USER_ID_2, eventId = AN_EVENT_ID,
roomName = null, senderId = A_USER_ID_2,
editedEventId = null, roomName = null,
description = "Incoming call", editedEventId = null,
timestamp = timestamp, description = "Incoming call",
canBeReplaced = true, timestamp = timestamp,
isRedacted = false, canBeReplaced = true,
isUpdated = false, isRedacted = false,
senderDisambiguatedDisplayName = "Bob", isUpdated = false,
senderAvatarUrl = null, senderDisambiguatedDisplayName = "Bob",
callNotifyType = CallNotifyType.RING, senderAvatarUrl = null,
callNotifyType = CallNotifyType.RING,
)
) )
val result = sut.resolveEvent(A_SESSION_ID, A_ROOM_ID, AN_EVENT_ID) val result = sut.resolveEvent(A_SESSION_ID, A_ROOM_ID, AN_EVENT_ID)
assertThat(result).isEqualTo(expectedResult) assertThat(result).isEqualTo(expectedResult)
@ -531,22 +571,24 @@ class DefaultNotifiableEventResolverTest {
) )
) )
) )
val expectedResult = NotifiableMessageEvent( val expectedResult = ResolvedPushEvent.Event(
sessionId = A_SESSION_ID, NotifiableMessageEvent(
eventId = AN_EVENT_ID, sessionId = A_SESSION_ID,
editedEventId = null, eventId = AN_EVENT_ID,
noisy = true, editedEventId = null,
timestamp = 0L, noisy = true,
senderDisambiguatedDisplayName = "Bob", timestamp = 0L,
senderId = UserId("@bob:server.org"), senderDisambiguatedDisplayName = "Bob",
body = "\uFE0F Incoming call", senderId = UserId("@bob:server.org"),
roomId = A_ROOM_ID, body = "\uFE0F Incoming call",
threadId = null, roomId = A_ROOM_ID,
roomName = null, threadId = null,
canBeReplaced = false, roomName = null,
isRedacted = false, canBeReplaced = false,
imageUriString = null, isRedacted = false,
type = EventType.CALL_NOTIFY, imageUriString = null,
type = EventType.CALL_NOTIFY,
)
) )
val result = sut.resolveEvent(A_SESSION_ID, A_ROOM_ID, AN_EVENT_ID) val result = sut.resolveEvent(A_SESSION_ID, A_ROOM_ID, AN_EVENT_ID)
assertThat(result).isEqualTo(expectedResult) assertThat(result).isEqualTo(expectedResult)
@ -564,27 +606,67 @@ class DefaultNotifiableEventResolverTest {
) )
) )
) )
val expectedResult = NotifiableMessageEvent( val expectedResult = ResolvedPushEvent.Event(
sessionId = A_SESSION_ID, NotifiableMessageEvent(
eventId = AN_EVENT_ID, sessionId = A_SESSION_ID,
editedEventId = null, eventId = AN_EVENT_ID,
noisy = true, editedEventId = null,
timestamp = A_TIMESTAMP, noisy = true,
senderDisambiguatedDisplayName = "Bob", timestamp = A_TIMESTAMP,
senderId = UserId("@bob:server.org"), senderDisambiguatedDisplayName = "Bob",
body = "\uFE0F Incoming call", senderId = UserId("@bob:server.org"),
roomId = A_ROOM_ID, body = "\uFE0F Incoming call",
threadId = null, roomId = A_ROOM_ID,
roomName = null, threadId = null,
canBeReplaced = false, roomName = null,
isRedacted = false, canBeReplaced = false,
imageUriString = null, isRedacted = false,
type = EventType.CALL_NOTIFY, imageUriString = null,
type = EventType.CALL_NOTIFY,
)
) )
val result = sut.resolveEvent(A_SESSION_ID, A_ROOM_ID, AN_EVENT_ID) val result = sut.resolveEvent(A_SESSION_ID, A_ROOM_ID, AN_EVENT_ID)
assertThat(result).isEqualTo(expectedResult) assertThat(result).isEqualTo(expectedResult)
} }
@Test
fun `resolve RoomRedaction`() = runTest {
val sut = createDefaultNotifiableEventResolver(
notificationResult = Result.success(
createNotificationData(
content = NotificationContent.MessageLike.RoomRedaction(
AN_EVENT_ID_2,
A_REDACTION_REASON,
)
)
)
)
val expectedResult = ResolvedPushEvent.Redaction(
sessionId = A_SESSION_ID,
roomId = A_ROOM_ID,
redactedEventId = AN_EVENT_ID_2,
reason = A_REDACTION_REASON,
)
val result = sut.resolveEvent(A_SESSION_ID, A_ROOM_ID, AN_EVENT_ID)
assertThat(result).isEqualTo(expectedResult)
}
@Test
fun `resolve RoomRedaction with null redactedEventId should return null`() = runTest {
val sut = createDefaultNotifiableEventResolver(
notificationResult = Result.success(
createNotificationData(
content = NotificationContent.MessageLike.RoomRedaction(
null,
A_REDACTION_REASON,
)
)
)
)
val result = sut.resolveEvent(A_SESSION_ID, A_ROOM_ID, AN_EVENT_ID)
assertThat(result).isNull()
}
@Test @Test
fun `resolve null cases`() { fun `resolve null cases`() {
testNull(NotificationContent.MessageLike.CallAnswer) testNull(NotificationContent.MessageLike.CallAnswer)
@ -598,7 +680,6 @@ class DefaultNotifiableEventResolverTest {
testNull(NotificationContent.MessageLike.KeyVerificationMac) testNull(NotificationContent.MessageLike.KeyVerificationMac)
testNull(NotificationContent.MessageLike.KeyVerificationDone) testNull(NotificationContent.MessageLike.KeyVerificationDone)
testNull(NotificationContent.MessageLike.ReactionContent(relatedEventId = AN_EVENT_ID_2.value)) testNull(NotificationContent.MessageLike.ReactionContent(relatedEventId = AN_EVENT_ID_2.value))
testNull(NotificationContent.MessageLike.RoomRedaction(redactedEventId = AN_EVENT_ID_2.value, reason = null))
testNull(NotificationContent.MessageLike.Sticker) testNull(NotificationContent.MessageLike.Sticker)
testNull(NotificationContent.StateEvent.PolicyRuleRoom) testNull(NotificationContent.StateEvent.PolicyRuleRoom)
testNull(NotificationContent.StateEvent.PolicyRuleServer) testNull(NotificationContent.StateEvent.PolicyRuleServer)

View file

@ -72,7 +72,7 @@ class DefaultNotificationDrawerManagerTest {
// For now just call all the API. Later, add more valuable tests. // For now just call all the API. Later, add more valuable tests.
val matrixUser = aMatrixUser(id = A_SESSION_ID.value, displayName = "alice", avatarUrl = "mxc://data") val matrixUser = aMatrixUser(id = A_SESSION_ID.value, displayName = "alice", avatarUrl = "mxc://data")
val mockRoomGroupMessageCreator = FakeRoomGroupMessageCreator( val mockRoomGroupMessageCreator = FakeRoomGroupMessageCreator(
createRoomMessageResult = lambdaRecorder { user, _, roomId, _, existingNotification, -> createRoomMessageResult = lambdaRecorder { user, _, roomId, _, existingNotification ->
assertThat(user).isEqualTo(matrixUser) assertThat(user).isEqualTo(matrixUser)
assertThat(roomId).isEqualTo(A_ROOM_ID) assertThat(roomId).isEqualTo(A_ROOM_ID)
assertThat(existingNotification).isNull() assertThat(existingNotification).isNull()
@ -167,11 +167,12 @@ class DefaultNotificationDrawerManagerTest {
} }
val summaryId = NotificationIdProvider.getSummaryNotificationId(A_SESSION_ID) val summaryId = NotificationIdProvider.getSummaryNotificationId(A_SESSION_ID)
val activeNotificationsProvider = FakeActiveNotificationsProvider( val activeNotificationsProvider = FakeActiveNotificationsProvider(
mutableListOf( getSummaryNotificationResult = {
mockk { mockk {
every { id } returns summaryId every { id } returns summaryId
} }
) },
countResult = { 1 },
) )
val defaultNotificationDrawerManager = createDefaultNotificationDrawerManager( val defaultNotificationDrawerManager = createDefaultNotificationDrawerManager(
notificationManager = notificationManager, notificationManager = notificationManager,

View file

@ -19,13 +19,13 @@ package io.element.android.libraries.push.impl.notifications
import io.element.android.libraries.matrix.api.core.EventId import io.element.android.libraries.matrix.api.core.EventId
import io.element.android.libraries.matrix.api.core.RoomId import io.element.android.libraries.matrix.api.core.RoomId
import io.element.android.libraries.matrix.api.core.SessionId import io.element.android.libraries.matrix.api.core.SessionId
import io.element.android.libraries.push.impl.notifications.model.NotifiableEvent import io.element.android.libraries.push.impl.notifications.model.ResolvedPushEvent
import io.element.android.tests.testutils.lambda.lambdaError import io.element.android.tests.testutils.lambda.lambdaError
class FakeNotifiableEventResolver( class FakeNotifiableEventResolver(
private val notifiableEventResult: (SessionId, RoomId, EventId) -> NotifiableEvent? = { _, _, _ -> lambdaError() } private val notifiableEventResult: (SessionId, RoomId, EventId) -> ResolvedPushEvent? = { _, _, _ -> lambdaError() }
) : NotifiableEventResolver { ) : NotifiableEventResolver {
override suspend fun resolveEvent(sessionId: SessionId, roomId: RoomId, eventId: EventId): NotifiableEvent? { override suspend fun resolveEvent(sessionId: SessionId, roomId: RoomId, eventId: EventId): ResolvedPushEvent? {
return notifiableEventResult(sessionId, roomId, eventId) return notifiableEventResult(sessionId, roomId, eventId)
} }
} }

View file

@ -22,33 +22,34 @@ import io.element.android.libraries.matrix.api.core.SessionId
import io.element.android.libraries.push.impl.notifications.ActiveNotificationsProvider import io.element.android.libraries.push.impl.notifications.ActiveNotificationsProvider
class FakeActiveNotificationsProvider( class FakeActiveNotificationsProvider(
var activeNotifications: MutableList<StatusBarNotification> = mutableListOf(), private val getMessageNotificationsForRoomResult: (SessionId, RoomId) -> List<StatusBarNotification> = { _, _ -> emptyList() },
private val getNotificationsForSessionResult: (SessionId) -> List<StatusBarNotification> = { emptyList() },
private val getMembershipNotificationForSessionResult: (SessionId) -> List<StatusBarNotification> = { emptyList() },
private val getMembershipNotificationForRoomResult: (SessionId, RoomId) -> List<StatusBarNotification> = { _, _ -> emptyList() },
private val getSummaryNotificationResult: (SessionId) -> StatusBarNotification? = { null },
private val countResult: (SessionId) -> Int = { 0 },
) : ActiveNotificationsProvider { ) : ActiveNotificationsProvider {
override fun getAllNotifications(): List<StatusBarNotification> {
return activeNotifications
}
override fun getMessageNotificationsForRoom(sessionId: SessionId, roomId: RoomId): List<StatusBarNotification> { override fun getMessageNotificationsForRoom(sessionId: SessionId, roomId: RoomId): List<StatusBarNotification> {
return activeNotifications return getMessageNotificationsForRoomResult(sessionId, roomId)
} }
override fun getNotificationsForSession(sessionId: SessionId): List<StatusBarNotification> { override fun getNotificationsForSession(sessionId: SessionId): List<StatusBarNotification> {
return activeNotifications return getNotificationsForSessionResult(sessionId)
} }
override fun getMembershipNotificationForSession(sessionId: SessionId): List<StatusBarNotification> { override fun getMembershipNotificationForSession(sessionId: SessionId): List<StatusBarNotification> {
return activeNotifications return getMembershipNotificationForSessionResult(sessionId)
} }
override fun getMembershipNotificationForRoom(sessionId: SessionId, roomId: RoomId): List<StatusBarNotification> { override fun getMembershipNotificationForRoom(sessionId: SessionId, roomId: RoomId): List<StatusBarNotification> {
return activeNotifications return getMembershipNotificationForRoomResult(sessionId, roomId)
} }
override fun getSummaryNotification(sessionId: SessionId): StatusBarNotification? { override fun getSummaryNotification(sessionId: SessionId): StatusBarNotification? {
return activeNotifications.firstOrNull() return getSummaryNotificationResult(sessionId)
} }
override fun count(sessionId: SessionId): Int { override fun count(sessionId: SessionId): Int {
return activeNotifications.size return countResult(sessionId)
} }
} }

View file

@ -0,0 +1,82 @@
/*
* 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
*
* https://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.push.impl.push
import android.service.notification.StatusBarNotification
import androidx.test.platform.app.InstrumentationRegistry
import io.element.android.libraries.matrix.api.core.RoomId
import io.element.android.libraries.matrix.api.core.SessionId
import io.element.android.libraries.matrix.test.AN_EVENT_ID
import io.element.android.libraries.matrix.test.A_ROOM_ID
import io.element.android.libraries.matrix.test.A_SESSION_ID
import io.element.android.libraries.push.impl.notifications.fake.FakeActiveNotificationsProvider
import io.element.android.libraries.push.impl.notifications.fake.FakeNotificationDisplayer
import io.element.android.libraries.push.impl.notifications.model.ResolvedPushEvent
import io.element.android.services.toolbox.test.strings.FakeStringProvider
import io.element.android.tests.testutils.lambda.lambdaError
import io.mockk.every
import io.mockk.mockk
import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.runTest
import org.junit.Test
import org.junit.runner.RunWith
import org.robolectric.RobolectricTestRunner
@RunWith(RobolectricTestRunner::class)
class DefaultOnRedactedEventReceivedTest {
@Test
fun `when no notifications are found, nothing happen`() = runTest {
val sut = createDefaultOnRedactedEventReceived(
getMessageNotificationsForRoomResult = { _, _ -> emptyList() }
)
sut.onRedactedEventReceived(ResolvedPushEvent.Redaction(A_SESSION_ID, A_ROOM_ID, AN_EVENT_ID, null))
}
@Test
fun `when a notification is found, try to retrieve the message`() = runTest {
val sut = createDefaultOnRedactedEventReceived(
getMessageNotificationsForRoomResult = { _, _ ->
listOf(
mockk {
every { notification } returns mockk {}
}
)
}
)
sut.onRedactedEventReceived(ResolvedPushEvent.Redaction(A_SESSION_ID, A_ROOM_ID, AN_EVENT_ID, null))
}
private fun TestScope.createDefaultOnRedactedEventReceived(
getMessageNotificationsForRoomResult: (SessionId, RoomId) -> List<StatusBarNotification> = { _, _ -> lambdaError() },
): DefaultOnRedactedEventReceived {
val context = InstrumentationRegistry.getInstrumentation().context
return DefaultOnRedactedEventReceived(
activeNotificationsProvider = FakeActiveNotificationsProvider(
getMessageNotificationsForRoomResult = getMessageNotificationsForRoomResult,
getNotificationsForSessionResult = { lambdaError() },
getMembershipNotificationForSessionResult = { lambdaError() },
getMembershipNotificationForRoomResult = { _, _ -> lambdaError() },
getSummaryNotificationResult = { lambdaError() },
countResult = { lambdaError() },
),
notificationDisplayer = FakeNotificationDisplayer(),
coroutineScope = this,
context = context,
stringProvider = FakeStringProvider(),
)
}
}

View file

@ -30,8 +30,10 @@ import io.element.android.libraries.matrix.api.core.UserId
import io.element.android.libraries.matrix.api.notification.CallNotifyType import io.element.android.libraries.matrix.api.notification.CallNotifyType
import io.element.android.libraries.matrix.api.timeline.item.event.EventType import io.element.android.libraries.matrix.api.timeline.item.event.EventType
import io.element.android.libraries.matrix.test.AN_EVENT_ID import io.element.android.libraries.matrix.test.AN_EVENT_ID
import io.element.android.libraries.matrix.test.AN_EVENT_ID_2
import io.element.android.libraries.matrix.test.A_ROOM_ID import io.element.android.libraries.matrix.test.A_ROOM_ID
import io.element.android.libraries.matrix.test.A_SECRET import io.element.android.libraries.matrix.test.A_SECRET
import io.element.android.libraries.matrix.test.A_SESSION_ID
import io.element.android.libraries.matrix.test.A_USER_ID import io.element.android.libraries.matrix.test.A_USER_ID
import io.element.android.libraries.matrix.test.auth.FakeMatrixAuthenticationService import io.element.android.libraries.matrix.test.auth.FakeMatrixAuthenticationService
import io.element.android.libraries.matrix.test.core.aBuildMeta import io.element.android.libraries.matrix.test.core.aBuildMeta
@ -40,6 +42,7 @@ import io.element.android.libraries.push.impl.notifications.channels.FakeNotific
import io.element.android.libraries.push.impl.notifications.fixtures.aNotifiableCallEvent import io.element.android.libraries.push.impl.notifications.fixtures.aNotifiableCallEvent
import io.element.android.libraries.push.impl.notifications.fixtures.aNotifiableMessageEvent import io.element.android.libraries.push.impl.notifications.fixtures.aNotifiableMessageEvent
import io.element.android.libraries.push.impl.notifications.model.NotifiableEvent import io.element.android.libraries.push.impl.notifications.model.NotifiableEvent
import io.element.android.libraries.push.impl.notifications.model.ResolvedPushEvent
import io.element.android.libraries.push.impl.test.DefaultTestPush import io.element.android.libraries.push.impl.test.DefaultTestPush
import io.element.android.libraries.push.impl.troubleshoot.DiagnosticPushHandler import io.element.android.libraries.push.impl.troubleshoot.DiagnosticPushHandler
import io.element.android.libraries.pushproviders.api.PushData import io.element.android.libraries.pushproviders.api.PushData
@ -61,7 +64,9 @@ class DefaultPushHandlerTest {
fun `when classical PushData is received, the notification drawer is informed`() = runTest { fun `when classical PushData is received, the notification drawer is informed`() = runTest {
val aNotifiableMessageEvent = aNotifiableMessageEvent() val aNotifiableMessageEvent = aNotifiableMessageEvent()
val notifiableEventResult = val notifiableEventResult =
lambdaRecorder<SessionId, RoomId, EventId, NotifiableEvent> { _, _, _ -> aNotifiableMessageEvent } lambdaRecorder<SessionId, RoomId, EventId, ResolvedPushEvent> { _, _, _ ->
ResolvedPushEvent.Event(aNotifiableMessageEvent)
}
val onNotifiableEventReceived = lambdaRecorder<NotifiableEvent, Unit> {} val onNotifiableEventReceived = lambdaRecorder<NotifiableEvent, Unit> {}
val incrementPushCounterResult = lambdaRecorder<Unit> {} val incrementPushCounterResult = lambdaRecorder<Unit> {}
val aPushData = PushData( val aPushData = PushData(
@ -94,7 +99,9 @@ class DefaultPushHandlerTest {
runTest { runTest {
val aNotifiableMessageEvent = aNotifiableMessageEvent() val aNotifiableMessageEvent = aNotifiableMessageEvent()
val notifiableEventResult = val notifiableEventResult =
lambdaRecorder<SessionId, RoomId, EventId, NotifiableEvent> { _, _, _ -> aNotifiableMessageEvent } lambdaRecorder<SessionId, RoomId, EventId, ResolvedPushEvent.Event> { _, _, _ ->
ResolvedPushEvent.Event(aNotifiableMessageEvent)
}
val onNotifiableEventReceived = lambdaRecorder<NotifiableEvent, Unit> {} val onNotifiableEventReceived = lambdaRecorder<NotifiableEvent, Unit> {}
val incrementPushCounterResult = lambdaRecorder<Unit> {} val incrementPushCounterResult = lambdaRecorder<Unit> {}
val aPushData = PushData( val aPushData = PushData(
@ -128,7 +135,9 @@ class DefaultPushHandlerTest {
runTest { runTest {
val aNotifiableMessageEvent = aNotifiableMessageEvent() val aNotifiableMessageEvent = aNotifiableMessageEvent()
val notifiableEventResult = val notifiableEventResult =
lambdaRecorder<SessionId, RoomId, EventId, NotifiableEvent> { _, _, _ -> aNotifiableMessageEvent } lambdaRecorder<SessionId, RoomId, EventId, ResolvedPushEvent.Event> { _, _, _ ->
ResolvedPushEvent.Event(aNotifiableMessageEvent)
}
val onNotifiableEventReceived = lambdaRecorder<NotifiableEvent, Unit> {} val onNotifiableEventReceived = lambdaRecorder<NotifiableEvent, Unit> {}
val incrementPushCounterResult = lambdaRecorder<Unit> {} val incrementPushCounterResult = lambdaRecorder<Unit> {}
val aPushData = PushData( val aPushData = PushData(
@ -164,7 +173,9 @@ class DefaultPushHandlerTest {
runTest { runTest {
val aNotifiableMessageEvent = aNotifiableMessageEvent() val aNotifiableMessageEvent = aNotifiableMessageEvent()
val notifiableEventResult = val notifiableEventResult =
lambdaRecorder<SessionId, RoomId, EventId, NotifiableEvent> { _, _, _ -> aNotifiableMessageEvent } lambdaRecorder<SessionId, RoomId, EventId, ResolvedPushEvent.Event> { _, _, _ ->
ResolvedPushEvent.Event(aNotifiableMessageEvent)
}
val onNotifiableEventReceived = lambdaRecorder<NotifiableEvent, Unit> {} val onNotifiableEventReceived = lambdaRecorder<NotifiableEvent, Unit> {}
val incrementPushCounterResult = lambdaRecorder<Unit> {} val incrementPushCounterResult = lambdaRecorder<Unit> {}
val aPushData = PushData( val aPushData = PushData(
@ -197,7 +208,7 @@ class DefaultPushHandlerTest {
fun `when classical PushData is received, but not able to resolve the event, nothing happen`() = fun `when classical PushData is received, but not able to resolve the event, nothing happen`() =
runTest { runTest {
val notifiableEventResult = val notifiableEventResult =
lambdaRecorder<SessionId, RoomId, EventId, NotifiableEvent?> { _, _, _ -> null } lambdaRecorder<SessionId, RoomId, EventId, ResolvedPushEvent.Event?> { _, _, _ -> null }
val onNotifiableEventReceived = lambdaRecorder<NotifiableEvent, Unit> {} val onNotifiableEventReceived = lambdaRecorder<NotifiableEvent, Unit> {}
val incrementPushCounterResult = lambdaRecorder<Unit> {} val incrementPushCounterResult = lambdaRecorder<Unit> {}
val aPushData = PushData( val aPushData = PushData(
@ -240,7 +251,9 @@ class DefaultPushHandlerTest {
val elementCallEntryPoint = FakeElementCallEntryPoint(handleIncomingCallResult = handleIncomingCallLambda) val elementCallEntryPoint = FakeElementCallEntryPoint(handleIncomingCallResult = handleIncomingCallLambda)
val defaultPushHandler = createDefaultPushHandler( val defaultPushHandler = createDefaultPushHandler(
elementCallEntryPoint = elementCallEntryPoint, elementCallEntryPoint = elementCallEntryPoint,
notifiableEventResult = { _, _, _ -> aNotifiableCallEvent(callNotifyType = CallNotifyType.RING, timestamp = Instant.now().toEpochMilli()) }, notifiableEventResult = { _, _, _ ->
ResolvedPushEvent.Event(aNotifiableCallEvent(callNotifyType = CallNotifyType.RING, timestamp = Instant.now().toEpochMilli()))
},
incrementPushCounterResult = {}, incrementPushCounterResult = {},
pushClientSecret = FakePushClientSecret( pushClientSecret = FakePushClientSecret(
getUserIdFromSecretResult = { A_USER_ID } getUserIdFromSecretResult = { A_USER_ID }
@ -265,7 +278,9 @@ class DefaultPushHandlerTest {
val defaultPushHandler = createDefaultPushHandler( val defaultPushHandler = createDefaultPushHandler(
elementCallEntryPoint = elementCallEntryPoint, elementCallEntryPoint = elementCallEntryPoint,
onNotifiableEventReceived = onNotifiableEventReceived, onNotifiableEventReceived = onNotifiableEventReceived,
notifiableEventResult = { _, _, _ -> aNotifiableMessageEvent(type = EventType.CALL_NOTIFY) }, notifiableEventResult = { _, _, _ ->
ResolvedPushEvent.Event(aNotifiableMessageEvent(type = EventType.CALL_NOTIFY))
},
incrementPushCounterResult = {}, incrementPushCounterResult = {},
pushClientSecret = FakePushClientSecret( pushClientSecret = FakePushClientSecret(
getUserIdFromSecretResult = { A_USER_ID } getUserIdFromSecretResult = { A_USER_ID }
@ -291,7 +306,9 @@ class DefaultPushHandlerTest {
val defaultPushHandler = createDefaultPushHandler( val defaultPushHandler = createDefaultPushHandler(
elementCallEntryPoint = elementCallEntryPoint, elementCallEntryPoint = elementCallEntryPoint,
onNotifiableEventReceived = onNotifiableEventReceived, onNotifiableEventReceived = onNotifiableEventReceived,
notifiableEventResult = { _, _, _ -> aNotifiableCallEvent() }, notifiableEventResult = { _, _, _ ->
ResolvedPushEvent.Event(aNotifiableCallEvent())
},
incrementPushCounterResult = {}, incrementPushCounterResult = {},
userPushStore = FakeUserPushStore().apply { userPushStore = FakeUserPushStore().apply {
setNotificationEnabledForDevice(false) setNotificationEnabledForDevice(false)
@ -305,6 +322,37 @@ class DefaultPushHandlerTest {
onNotifiableEventReceived.assertions().isNeverCalled() onNotifiableEventReceived.assertions().isNeverCalled()
} }
@Test
fun `when a redaction is received, the onRedactedEventReceived is informed`() = runTest {
val aPushData = PushData(
eventId = AN_EVENT_ID,
roomId = A_ROOM_ID,
unread = 0,
clientSecret = A_SECRET,
)
val aRedaction = ResolvedPushEvent.Redaction(
sessionId = A_SESSION_ID,
roomId = A_ROOM_ID,
redactedEventId = AN_EVENT_ID_2,
reason = null
)
val onRedactedEventReceived = lambdaRecorder<ResolvedPushEvent.Redaction, Unit> { }
val incrementPushCounterResult = lambdaRecorder<Unit> {}
val defaultPushHandler = createDefaultPushHandler(
onRedactedEventReceived = onRedactedEventReceived,
incrementPushCounterResult = incrementPushCounterResult,
notifiableEventResult = { _, _, _ -> aRedaction },
pushClientSecret = FakePushClientSecret(
getUserIdFromSecretResult = { A_USER_ID }
),
)
defaultPushHandler.handle(aPushData)
incrementPushCounterResult.assertions()
.isCalledOnce()
onRedactedEventReceived.assertions().isCalledOnce()
.with(value(aRedaction))
}
@Test @Test
fun `when diagnostic PushData is received, the diagnostic push handler is informed`() = fun `when diagnostic PushData is received, the diagnostic push handler is informed`() =
runTest { runTest {
@ -327,7 +375,8 @@ class DefaultPushHandlerTest {
private fun createDefaultPushHandler( private fun createDefaultPushHandler(
onNotifiableEventReceived: (NotifiableEvent) -> Unit = { lambdaError() }, onNotifiableEventReceived: (NotifiableEvent) -> Unit = { lambdaError() },
notifiableEventResult: (SessionId, RoomId, EventId) -> NotifiableEvent? = { _, _, _ -> lambdaError() }, onRedactedEventReceived: (ResolvedPushEvent.Redaction) -> Unit = { lambdaError() },
notifiableEventResult: (SessionId, RoomId, EventId) -> ResolvedPushEvent? = { _, _, _ -> lambdaError() },
incrementPushCounterResult: () -> Unit = { lambdaError() }, incrementPushCounterResult: () -> Unit = { lambdaError() },
userPushStore: UserPushStore = FakeUserPushStore(), userPushStore: UserPushStore = FakeUserPushStore(),
pushClientSecret: PushClientSecret = FakePushClientSecret(), pushClientSecret: PushClientSecret = FakePushClientSecret(),
@ -339,6 +388,7 @@ class DefaultPushHandlerTest {
): DefaultPushHandler { ): DefaultPushHandler {
return DefaultPushHandler( return DefaultPushHandler(
onNotifiableEventReceived = FakeOnNotifiableEventReceived(onNotifiableEventReceived), onNotifiableEventReceived = FakeOnNotifiableEventReceived(onNotifiableEventReceived),
onRedactedEventReceived = FakeOnRedactedEventReceived(onRedactedEventReceived),
notifiableEventResolver = FakeNotifiableEventResolver(notifiableEventResult), notifiableEventResolver = FakeNotifiableEventResolver(notifiableEventResult),
incrementPushDataStore = object : IncrementPushDataStore { incrementPushDataStore = object : IncrementPushDataStore {
override suspend fun incrementPushCounter() { override suspend fun incrementPushCounter() {

View file

@ -0,0 +1,28 @@
/*
* 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.push.impl.push
import io.element.android.libraries.push.impl.notifications.model.ResolvedPushEvent
import io.element.android.tests.testutils.lambda.lambdaError
class FakeOnRedactedEventReceived(
private val onRedactedEventReceivedResult: (ResolvedPushEvent.Redaction) -> Unit = { lambdaError() },
) : OnRedactedEventReceived {
override fun onRedactedEventReceived(redaction: ResolvedPushEvent.Redaction) {
onRedactedEventReceivedResult(redaction)
}
}