Merge pull request #3320 from element-hq/feature/bma/cleanNotificationOfRedactedEvent
Redact message on displayed notification
This commit is contained in:
commit
82fa369c8b
19 changed files with 644 additions and 260 deletions
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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"
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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 }
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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(
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
}
|
||||||
|
|
@ -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")
|
||||||
|
|
|
||||||
|
|
@ -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()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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(
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
|
|
|
||||||
|
|
@ -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,
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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() {
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue