Update dependency org.matrix.rustcomponents:sdk-android to v25.7.7 (#4989)
Make sure we distinguish between notification events that were filtered out and those that couldn't be resolved. --- Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Co-authored-by: Jorge Martín <jorgem@element.io>
This commit is contained in:
@@ -173,7 +173,7 @@ jsoup = "org.jsoup:jsoup:1.21.1"
|
||||
appyx_core = { module = "com.bumble.appyx:core", version.ref = "appyx" }
|
||||
molecule-runtime = "app.cash.molecule:molecule-runtime:2.1.0"
|
||||
timber = "com.jakewharton.timber:timber:5.0.1"
|
||||
matrix_sdk = "org.matrix.rustcomponents:sdk-android:25.7.3"
|
||||
matrix_sdk = "org.matrix.rustcomponents:sdk-android:25.7.7"
|
||||
matrix_richtexteditor = { module = "io.element.android:wysiwyg", version.ref = "wysiwyg" }
|
||||
matrix_richtexteditor_compose = { module = "io.element.android:wysiwyg-compose", version.ref = "wysiwyg" }
|
||||
sqldelight-driver-android = { module = "app.cash.sqldelight:android-driver", version.ref = "sqldelight" }
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
/*
|
||||
* Copyright 2025 New Vector Ltd.
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
|
||||
* Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
package io.element.android.libraries.matrix.api.exception
|
||||
|
||||
/**
|
||||
* Exceptions that can occur while resolving the events associated to push notifications.
|
||||
*/
|
||||
sealed class NotificationResolverException : Exception() {
|
||||
/**
|
||||
* The event was not found by the notification service.
|
||||
*/
|
||||
data object EventNotFound : NotificationResolverException()
|
||||
|
||||
/**
|
||||
* The event was found but it was filtered out by the notification service.
|
||||
*/
|
||||
data object EventFilteredOut : NotificationResolverException()
|
||||
|
||||
/**
|
||||
* An unexpected error occurred while trying to resolve the event.
|
||||
*/
|
||||
data class UnknownError(override val message: String) : NotificationResolverException()
|
||||
}
|
||||
@@ -68,7 +68,6 @@ sealed interface NotificationContent {
|
||||
) : MessageLike
|
||||
|
||||
data object RoomEncrypted : MessageLike
|
||||
data object UnableToResolve : MessageLike
|
||||
data class RoomMessage(
|
||||
val senderId: UserId,
|
||||
val messageType: MessageType
|
||||
|
||||
@@ -10,6 +10,19 @@ package io.element.android.libraries.matrix.api.notification
|
||||
import io.element.android.libraries.matrix.api.core.EventId
|
||||
import io.element.android.libraries.matrix.api.core.RoomId
|
||||
|
||||
/**
|
||||
* Represents the resolution state of an attempt to retrieve notification data for a set of event ids.
|
||||
* The outer [Result] indicates the success or failure of the setup to retrieve notifications.
|
||||
* The inner [Result] for each [EventId] in the map indicates whether the notification data was successfully retrieved or if there was an error.
|
||||
*/
|
||||
typealias GetNotificationDataResult = Result<Map<EventId, Result<NotificationData>>>
|
||||
|
||||
/**
|
||||
* Service to retrieve notifications for a given set of event ids in specific rooms.
|
||||
*/
|
||||
interface NotificationService {
|
||||
suspend fun getNotifications(ids: Map<RoomId, List<EventId>>): Result<Map<EventId, NotificationData>>
|
||||
/**
|
||||
* Fetch notifications for the specified event ids in the given rooms.
|
||||
*/
|
||||
suspend fun getNotifications(ids: Map<RoomId, List<EventId>>): GetNotificationDataResult
|
||||
}
|
||||
|
||||
@@ -12,26 +12,29 @@ import io.element.android.libraries.core.extensions.runCatchingExceptions
|
||||
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
|
||||
import io.element.android.libraries.matrix.api.notification.NotificationContent
|
||||
import io.element.android.libraries.matrix.api.notification.NotificationData
|
||||
import io.element.android.libraries.matrix.api.exception.NotificationResolverException
|
||||
import io.element.android.libraries.matrix.api.notification.GetNotificationDataResult
|
||||
import io.element.android.libraries.matrix.api.notification.NotificationService
|
||||
import io.element.android.services.toolbox.api.systemclock.SystemClock
|
||||
import kotlinx.coroutines.withContext
|
||||
import org.matrix.rustcomponents.sdk.BatchNotificationResult
|
||||
import org.matrix.rustcomponents.sdk.NotificationClient
|
||||
import org.matrix.rustcomponents.sdk.NotificationItemsRequest
|
||||
import org.matrix.rustcomponents.sdk.NotificationStatus
|
||||
import org.matrix.rustcomponents.sdk.use
|
||||
import timber.log.Timber
|
||||
|
||||
class RustNotificationService(
|
||||
private val sessionId: SessionId,
|
||||
private val notificationClient: NotificationClient,
|
||||
private val dispatchers: CoroutineDispatchers,
|
||||
private val clock: SystemClock,
|
||||
clock: SystemClock,
|
||||
) : NotificationService {
|
||||
private val notificationMapper: NotificationMapper = NotificationMapper(clock)
|
||||
|
||||
override suspend fun getNotifications(
|
||||
ids: Map<RoomId, List<EventId>>
|
||||
): Result<Map<EventId, NotificationData>> = withContext(dispatchers.io) {
|
||||
): GetNotificationDataResult = withContext(dispatchers.io) {
|
||||
runCatchingExceptions {
|
||||
val requests = ids.map { (roomId, eventIds) ->
|
||||
NotificationItemsRequest(
|
||||
@@ -42,34 +45,41 @@ class RustNotificationService(
|
||||
val items = notificationClient.getNotifications(requests)
|
||||
buildMap {
|
||||
val eventIds = requests.flatMap { it.eventIds }
|
||||
for (eventId in eventIds) {
|
||||
val item = items[eventId]
|
||||
val roomId = RoomId(requests.find { it.eventIds.contains(eventId) }?.roomId!!)
|
||||
if (item != null) {
|
||||
put(EventId(eventId), notificationMapper.map(sessionId, EventId(eventId), roomId, item))
|
||||
} else {
|
||||
Timber.e("Could not retrieve event for notification with $eventId")
|
||||
put(
|
||||
EventId(eventId),
|
||||
NotificationData(
|
||||
sessionId = sessionId,
|
||||
eventId = EventId(eventId),
|
||||
threadId = null,
|
||||
roomId = roomId,
|
||||
senderAvatarUrl = null,
|
||||
senderDisplayName = null,
|
||||
senderIsNameAmbiguous = false,
|
||||
roomAvatarUrl = null,
|
||||
roomDisplayName = null,
|
||||
isDirect = false,
|
||||
isDm = false,
|
||||
isEncrypted = false,
|
||||
isNoisy = false,
|
||||
timestamp = clock.epochMillis(),
|
||||
content = NotificationContent.MessageLike.UnableToResolve,
|
||||
hasMention = false
|
||||
)
|
||||
)
|
||||
for (rawEventId in eventIds) {
|
||||
val roomId = RoomId(requests.find { it.eventIds.contains(rawEventId) }?.roomId!!)
|
||||
val eventId = EventId(rawEventId)
|
||||
items[rawEventId].use { result ->
|
||||
when (result) {
|
||||
is BatchNotificationResult.Ok -> {
|
||||
when (val status = result.status) {
|
||||
is NotificationStatus.Event -> {
|
||||
put(eventId, Result.success(notificationMapper.map(sessionId, eventId, roomId, status.item)))
|
||||
}
|
||||
is NotificationStatus.EventNotFound -> {
|
||||
Timber.e("Could not retrieve event for notification with $eventId - event not found")
|
||||
put(eventId, Result.failure(NotificationResolverException.EventNotFound))
|
||||
}
|
||||
is NotificationStatus.EventFilteredOut -> {
|
||||
Timber.d("Could not retrieve event for notification with $eventId - event filtered out")
|
||||
put(eventId, Result.failure(NotificationResolverException.EventFilteredOut))
|
||||
}
|
||||
}
|
||||
}
|
||||
is BatchNotificationResult.Error -> {
|
||||
Timber.e("Error while retrieving notification with $rawEventId - ${result.message}")
|
||||
put(
|
||||
eventId,
|
||||
Result.failure(NotificationResolverException.UnknownError(result.message))
|
||||
)
|
||||
}
|
||||
null -> {
|
||||
Timber.e("The notification data for $rawEventId was not in the retrieved results. This is unexpected.")
|
||||
put(
|
||||
eventId,
|
||||
Result.failure(NotificationResolverException.UnknownError("Notification data not found"))
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,6 +16,7 @@ import org.matrix.rustcomponents.sdk.NotificationEvent
|
||||
import org.matrix.rustcomponents.sdk.NotificationItem
|
||||
import org.matrix.rustcomponents.sdk.NotificationRoomInfo
|
||||
import org.matrix.rustcomponents.sdk.NotificationSenderInfo
|
||||
import org.matrix.rustcomponents.sdk.NotificationStatus
|
||||
import org.matrix.rustcomponents.sdk.TimelineEvent
|
||||
|
||||
fun aRustNotificationItem(
|
||||
@@ -34,6 +35,12 @@ fun aRustNotificationItem(
|
||||
threadId = threadId?.value,
|
||||
)
|
||||
|
||||
fun aRustBatchNotificationResult(
|
||||
notificationStatus: NotificationStatus = NotificationStatus.Event(aRustNotificationItem()),
|
||||
) = org.matrix.rustcomponents.sdk.BatchNotificationResult.Ok(
|
||||
status = notificationStatus,
|
||||
)
|
||||
|
||||
fun aRustNotificationSenderInfo(
|
||||
displayName: String? = A_USER_NAME,
|
||||
avatarUrl: String? = null,
|
||||
|
||||
@@ -7,16 +7,16 @@
|
||||
|
||||
package io.element.android.libraries.matrix.impl.fixtures.fakes
|
||||
|
||||
import org.matrix.rustcomponents.sdk.BatchNotificationResult
|
||||
import org.matrix.rustcomponents.sdk.NoPointer
|
||||
import org.matrix.rustcomponents.sdk.NotificationClient
|
||||
import org.matrix.rustcomponents.sdk.NotificationItem
|
||||
import org.matrix.rustcomponents.sdk.NotificationItemsRequest
|
||||
|
||||
class FakeFfiNotificationClient(
|
||||
var notificationItemResult: Map<String, NotificationItem> = emptyMap(),
|
||||
var notificationItemResult: Map<String, BatchNotificationResult> = emptyMap(),
|
||||
val closeResult: () -> Unit = { }
|
||||
) : NotificationClient(NoPointer) {
|
||||
override suspend fun getNotifications(requests: List<NotificationItemsRequest>): Map<String, NotificationItem> {
|
||||
override suspend fun getNotifications(requests: List<NotificationItemsRequest>): Map<String, BatchNotificationResult> {
|
||||
return notificationItemResult
|
||||
}
|
||||
|
||||
|
||||
@@ -8,9 +8,10 @@
|
||||
package io.element.android.libraries.matrix.impl.notification
|
||||
|
||||
import com.google.common.truth.Truth.assertThat
|
||||
import io.element.android.libraries.matrix.api.exception.NotificationResolverException
|
||||
import io.element.android.libraries.matrix.api.notification.NotificationContent
|
||||
import io.element.android.libraries.matrix.api.timeline.item.event.TextMessageType
|
||||
import io.element.android.libraries.matrix.impl.fixtures.factories.aRustNotificationItem
|
||||
import io.element.android.libraries.matrix.impl.fixtures.factories.aRustBatchNotificationResult
|
||||
import io.element.android.libraries.matrix.impl.fixtures.fakes.FakeFfiNotificationClient
|
||||
import io.element.android.libraries.matrix.test.AN_EVENT_ID
|
||||
import io.element.android.libraries.matrix.test.A_MESSAGE
|
||||
@@ -30,12 +31,12 @@ class RustNotificationServiceTest {
|
||||
@Test
|
||||
fun test() = runTest {
|
||||
val notificationClient = FakeFfiNotificationClient(
|
||||
notificationItemResult = mapOf(AN_EVENT_ID.value to aRustNotificationItem()),
|
||||
notificationItemResult = mapOf(AN_EVENT_ID.value to aRustBatchNotificationResult()),
|
||||
)
|
||||
val sut = createRustNotificationService(
|
||||
notificationClient = notificationClient,
|
||||
)
|
||||
val result = sut.getNotifications(mapOf(A_ROOM_ID to listOf(AN_EVENT_ID))).getOrThrow()[AN_EVENT_ID]!!
|
||||
val result = sut.getNotifications(mapOf(A_ROOM_ID to listOf(AN_EVENT_ID))).getOrThrow()[AN_EVENT_ID]!!.getOrThrow()
|
||||
assertThat(result.isEncrypted).isTrue()
|
||||
assertThat(result.content).isEqualTo(
|
||||
NotificationContent.MessageLike.RoomMessage(
|
||||
@@ -56,10 +57,8 @@ class RustNotificationServiceTest {
|
||||
val sut = createRustNotificationService(
|
||||
notificationClient = notificationClient,
|
||||
)
|
||||
val result = sut.getNotifications(mapOf(A_ROOM_ID to listOf(AN_EVENT_ID))).getOrThrow()[AN_EVENT_ID]!!
|
||||
assertThat(result.content).isEqualTo(
|
||||
NotificationContent.MessageLike.UnableToResolve
|
||||
)
|
||||
val exception = sut.getNotifications(mapOf(A_ROOM_ID to listOf(AN_EVENT_ID))).getOrThrow()[AN_EVENT_ID]!!.exceptionOrNull()
|
||||
assertThat(exception).isInstanceOf(NotificationResolverException::class.java)
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
@@ -13,13 +13,13 @@ import io.element.android.libraries.matrix.api.notification.NotificationData
|
||||
import io.element.android.libraries.matrix.api.notification.NotificationService
|
||||
|
||||
class FakeNotificationService : NotificationService {
|
||||
private var getNotificationsResult: Result<Map<EventId, NotificationData>> = Result.success(emptyMap())
|
||||
private var getNotificationsResult: Result<Map<EventId, Result<NotificationData>>> = Result.success(emptyMap())
|
||||
|
||||
fun givenGetNotificationsResult(result: Result<Map<EventId, NotificationData>>) {
|
||||
fun givenGetNotificationsResult(result: Result<Map<EventId, Result<NotificationData>>>) {
|
||||
getNotificationsResult = result
|
||||
}
|
||||
|
||||
override suspend fun getNotifications(ids: Map<RoomId, List<EventId>>): Result<Map<EventId, NotificationData>> {
|
||||
override suspend fun getNotifications(ids: Map<RoomId, List<EventId>>): Result<Map<EventId, Result<NotificationData>>> {
|
||||
return getNotificationsResult
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,6 +12,7 @@ import io.element.android.libraries.core.extensions.runCatchingExceptions
|
||||
import io.element.android.libraries.di.AppScope
|
||||
import io.element.android.libraries.matrix.api.MatrixClientProvider
|
||||
import io.element.android.libraries.matrix.api.core.SessionId
|
||||
import io.element.android.libraries.matrix.api.exception.NotificationResolverException
|
||||
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.NotificationData
|
||||
@@ -57,7 +58,7 @@ class DefaultCallNotificationEventResolver @Inject constructor(
|
||||
forceNotify: Boolean
|
||||
): Result<NotifiableEvent> = runCatchingExceptions {
|
||||
val content = notificationData.content as? NotificationContent.MessageLike.CallNotify
|
||||
?: throw ResolvingException("content is not a call notify")
|
||||
?: throw NotificationResolverException.UnknownError("content is not a call notify")
|
||||
|
||||
val previousRingingCallStatus = appForegroundStateService.hasRingingCall.value
|
||||
// We need the sync service working to get the updated room info
|
||||
@@ -65,8 +66,12 @@ class DefaultCallNotificationEventResolver @Inject constructor(
|
||||
if (content.type == CallNotifyType.RING) {
|
||||
appForegroundStateService.updateHasRingingCall(true)
|
||||
|
||||
val client = clientProvider.getOrRestore(sessionId).getOrNull() ?: throw ResolvingException("Session $sessionId not found")
|
||||
val room = client.getRoom(notificationData.roomId) ?: throw ResolvingException("Room ${notificationData.roomId} not found")
|
||||
val client = clientProvider.getOrRestore(
|
||||
sessionId
|
||||
).getOrNull() ?: throw NotificationResolverException.UnknownError("Session $sessionId not found")
|
||||
val room = client.getRoom(
|
||||
notificationData.roomId
|
||||
) ?: throw NotificationResolverException.UnknownError("Room ${notificationData.roomId} not found")
|
||||
// Give a few seconds for the room info flow to catch up with the sync, if needed - this is usually instant
|
||||
val isActive = withTimeoutOrNull(3.seconds) { room.roomInfoFlow.firstOrNull { it.hasRoomCall } }?.hasRoomCall ?: false
|
||||
|
||||
|
||||
@@ -11,7 +11,7 @@ import android.content.Context
|
||||
import android.net.Uri
|
||||
import androidx.core.content.FileProvider
|
||||
import com.squareup.anvil.annotations.ContributesBinding
|
||||
import io.element.android.libraries.core.extensions.mapCatchingExceptions
|
||||
import io.element.android.libraries.core.extensions.flatMap
|
||||
import io.element.android.libraries.core.extensions.runCatchingExceptions
|
||||
import io.element.android.libraries.core.log.logger.LoggerTag
|
||||
import io.element.android.libraries.di.AppScope
|
||||
@@ -24,6 +24,7 @@ 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.ThreadId
|
||||
import io.element.android.libraries.matrix.api.core.UserId
|
||||
import io.element.android.libraries.matrix.api.exception.NotificationResolverException
|
||||
import io.element.android.libraries.matrix.api.media.MediaPreviewValue
|
||||
import io.element.android.libraries.matrix.api.media.getMediaPreviewValue
|
||||
import io.element.android.libraries.matrix.api.notification.NotificationContent
|
||||
@@ -43,18 +44,24 @@ import io.element.android.libraries.matrix.api.timeline.item.event.VideoMessageT
|
||||
import io.element.android.libraries.matrix.api.timeline.item.event.VoiceMessageType
|
||||
import io.element.android.libraries.matrix.ui.messages.toPlainText
|
||||
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.InviteNotifiableEvent
|
||||
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.services.toolbox.api.strings.StringProvider
|
||||
import io.element.android.services.toolbox.api.systemclock.SystemClock
|
||||
import timber.log.Timber
|
||||
import javax.inject.Inject
|
||||
|
||||
private val loggerTag = LoggerTag("DefaultNotifiableEventResolver", LoggerTag.NotificationLoggerTag)
|
||||
|
||||
/**
|
||||
* Result of resolving a batch of push events.
|
||||
* The outermost [Result] indicates whether the setup to resolve the events was successful.
|
||||
* The results for each push notification will be a map of [NotificationEventRequest] to [Result] of [ResolvedPushEvent].
|
||||
* If the resolution of a specific event fails, the innermost [Result] will contain an exception.
|
||||
*/
|
||||
typealias ResolvePushEventsResult = Result<Map<NotificationEventRequest, Result<ResolvedPushEvent>>>
|
||||
|
||||
/**
|
||||
* The notifiable event resolver is able to create a NotifiableEvent (view model for notifications) from an sdk Event.
|
||||
* It is used as a bridge between the Event Thread and the NotificationDrawerManager.
|
||||
@@ -65,24 +72,24 @@ interface NotifiableEventResolver {
|
||||
suspend fun resolveEvents(
|
||||
sessionId: SessionId,
|
||||
notificationEventRequests: List<NotificationEventRequest>
|
||||
): Result<Map<NotificationEventRequest, Result<ResolvedPushEvent>>>
|
||||
): ResolvePushEventsResult
|
||||
}
|
||||
|
||||
@ContributesBinding(AppScope::class)
|
||||
@SingleIn(AppScope::class)
|
||||
class DefaultNotifiableEventResolver @Inject constructor(
|
||||
private val stringProvider: StringProvider,
|
||||
private val clock: SystemClock,
|
||||
private val matrixClientProvider: MatrixClientProvider,
|
||||
private val notificationMediaRepoFactory: NotificationMediaRepo.Factory,
|
||||
@ApplicationContext private val context: Context,
|
||||
private val permalinkParser: PermalinkParser,
|
||||
private val callNotificationEventResolver: CallNotificationEventResolver,
|
||||
private val fallbackNotificationFactory: FallbackNotificationFactory,
|
||||
) : NotifiableEventResolver {
|
||||
override suspend fun resolveEvents(
|
||||
sessionId: SessionId,
|
||||
notificationEventRequests: List<NotificationEventRequest>
|
||||
): Result<Map<NotificationEventRequest, Result<ResolvedPushEvent>>> {
|
||||
): ResolvePushEventsResult {
|
||||
Timber.d("Queueing notifications: $notificationEventRequests")
|
||||
val client = matrixClientProvider.getOrRestore(sessionId).getOrElse {
|
||||
return Result.failure(IllegalStateException("Couldn't get or restore client for session $sessionId"))
|
||||
@@ -90,20 +97,28 @@ class DefaultNotifiableEventResolver @Inject constructor(
|
||||
val ids = notificationEventRequests.groupBy { it.roomId }.mapValues { (_, value) -> value.map { it.eventId } }
|
||||
|
||||
// TODO this notificationData is not always valid at the moment, sometimes the Rust SDK can't fetch the matching event
|
||||
val notifications = client.notificationService().getNotifications(ids).mapCatchingExceptions { map ->
|
||||
map.mapValues { (_, notificationData) ->
|
||||
notificationData.asNotifiableEvent(client, sessionId)
|
||||
val notificationsResult = client.notificationService().getNotifications(ids)
|
||||
|
||||
if (notificationsResult.isFailure) {
|
||||
val exception = notificationsResult.exceptionOrNull()
|
||||
Timber.tag(loggerTag.value).e(exception, "Failed to get notifications for $ids")
|
||||
return Result.failure(exception ?: NotificationResolverException.UnknownError("Unknown error while fetching notifications"))
|
||||
}
|
||||
|
||||
// The null check is done above
|
||||
val notificationDataMap = notificationsResult.getOrNull()!!.mapValues { (_, notificationData) ->
|
||||
notificationData.flatMap { data ->
|
||||
data.asNotifiableEvent(client, sessionId)
|
||||
}
|
||||
}
|
||||
|
||||
return Result.success(
|
||||
notificationEventRequests.associate {
|
||||
val notificationData = notifications.getOrNull()?.get(it.eventId)
|
||||
if (notificationData != null) {
|
||||
it to notificationData
|
||||
notificationEventRequests.associate { request ->
|
||||
val notificationDataResult = notificationDataMap[request.eventId]
|
||||
if (notificationDataResult == null) {
|
||||
request to Result.failure(NotificationResolverException.UnknownError("No notification data for ${request.roomId} - ${request.eventId}"))
|
||||
} else {
|
||||
// TODO once the SDK can actually return what went wrong, we should return it here instead of this generic error
|
||||
it to Result.failure(ResolvingException("No notification data for ${it.roomId} - ${it.eventId}"))
|
||||
request to notificationDataResult
|
||||
}
|
||||
}
|
||||
)
|
||||
@@ -164,7 +179,7 @@ class DefaultNotifiableEventResolver @Inject constructor(
|
||||
NotificationContent.MessageLike.CallCandidates,
|
||||
NotificationContent.MessageLike.CallHangup -> {
|
||||
Timber.tag(loggerTag.value).d("Ignoring notification for call ${content.javaClass.simpleName}")
|
||||
throw ResolvingException("Ignoring notification for call ${content.javaClass.simpleName}")
|
||||
throw NotificationResolverException.EventFilteredOut
|
||||
}
|
||||
is NotificationContent.MessageLike.CallInvite -> {
|
||||
val notifiableMessageEvent = buildNotifiableMessageEvent(
|
||||
@@ -195,7 +210,7 @@ class DefaultNotifiableEventResolver @Inject constructor(
|
||||
NotificationContent.MessageLike.KeyVerificationReady,
|
||||
NotificationContent.MessageLike.KeyVerificationStart -> {
|
||||
Timber.tag(loggerTag.value).d("Ignoring notification for verification ${content.javaClass.simpleName}")
|
||||
throw ResolvingException("Ignoring notification for verification ${content.javaClass.simpleName}")
|
||||
throw NotificationResolverException.EventFilteredOut
|
||||
}
|
||||
is NotificationContent.MessageLike.Poll -> {
|
||||
val notifiableEventMessage = buildNotifiableMessageEvent(
|
||||
@@ -217,16 +232,11 @@ class DefaultNotifiableEventResolver @Inject constructor(
|
||||
}
|
||||
is NotificationContent.MessageLike.ReactionContent -> {
|
||||
Timber.tag(loggerTag.value).d("Ignoring notification for reaction")
|
||||
throw ResolvingException("Ignoring notification for reaction")
|
||||
throw NotificationResolverException.EventFilteredOut
|
||||
}
|
||||
NotificationContent.MessageLike.RoomEncrypted -> {
|
||||
Timber.tag(loggerTag.value).w("Notification with encrypted content -> fallback")
|
||||
val fallbackNotifiableEvent = fallbackNotifiableEvent(userId, roomId, eventId)
|
||||
ResolvedPushEvent.Event(fallbackNotifiableEvent)
|
||||
}
|
||||
NotificationContent.MessageLike.UnableToResolve -> {
|
||||
Timber.tag(loggerTag.value).w("Unable to resolve notification -> fallback")
|
||||
val fallbackNotifiableEvent = fallbackNotifiableEvent(userId, roomId, eventId)
|
||||
val fallbackNotifiableEvent = fallbackNotificationFactory.create(userId, roomId, eventId)
|
||||
ResolvedPushEvent.Event(fallbackNotifiableEvent)
|
||||
}
|
||||
is NotificationContent.MessageLike.RoomRedaction -> {
|
||||
@@ -234,7 +244,7 @@ class DefaultNotifiableEventResolver @Inject constructor(
|
||||
val redactedEventId = content.redactedEventId
|
||||
if (redactedEventId == null) {
|
||||
Timber.tag(loggerTag.value).d("redactedEventId is null.")
|
||||
throw ResolvingException("redactedEventId is null")
|
||||
throw NotificationResolverException.UnknownError("redactedEventId is null")
|
||||
} else {
|
||||
ResolvedPushEvent.Redaction(
|
||||
sessionId = userId,
|
||||
@@ -246,7 +256,7 @@ class DefaultNotifiableEventResolver @Inject constructor(
|
||||
}
|
||||
NotificationContent.MessageLike.Sticker -> {
|
||||
Timber.tag(loggerTag.value).d("Ignoring notification for sticker")
|
||||
throw ResolvingException("Ignoring notification for reaction")
|
||||
throw NotificationResolverException.EventFilteredOut
|
||||
}
|
||||
is NotificationContent.StateEvent.RoomMemberContent,
|
||||
NotificationContent.StateEvent.PolicyRuleRoom,
|
||||
@@ -270,27 +280,11 @@ class DefaultNotifiableEventResolver @Inject constructor(
|
||||
NotificationContent.StateEvent.SpaceChild,
|
||||
NotificationContent.StateEvent.SpaceParent -> {
|
||||
Timber.tag(loggerTag.value).d("Ignoring notification for state event ${content.javaClass.simpleName}")
|
||||
throw ResolvingException("Ignoring notification for state event ${content.javaClass.simpleName}")
|
||||
throw NotificationResolverException.EventFilteredOut
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun fallbackNotifiableEvent(
|
||||
userId: SessionId,
|
||||
roomId: RoomId,
|
||||
eventId: EventId
|
||||
) = FallbackNotifiableEvent(
|
||||
sessionId = userId,
|
||||
roomId = roomId,
|
||||
eventId = eventId,
|
||||
editedEventId = null,
|
||||
canBeReplaced = true,
|
||||
isRedacted = false,
|
||||
isUpdated = false,
|
||||
timestamp = clock.epochMillis(),
|
||||
description = stringProvider.getString(R.string.notification_fallback_content),
|
||||
)
|
||||
|
||||
private fun descriptionFromMessageContent(
|
||||
content: NotificationContent.MessageLike.RoomMessage,
|
||||
senderDisambiguatedDisplayName: String,
|
||||
|
||||
@@ -33,6 +33,7 @@ class DefaultOnMissedCallNotificationHandler @Inject constructor(
|
||||
?.getNotifications(mapOf(roomId to listOf(eventId)))
|
||||
?.getOrNull()
|
||||
?.get(eventId)
|
||||
?.getOrNull()
|
||||
?: return
|
||||
|
||||
val notifiableEvent = callNotificationEventResolver.resolveEvent(
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
/*
|
||||
* Copyright 2025 New Vector Ltd.
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
|
||||
* Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
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.RoomId
|
||||
import io.element.android.libraries.matrix.api.core.SessionId
|
||||
import io.element.android.libraries.push.impl.R
|
||||
import io.element.android.libraries.push.impl.notifications.model.FallbackNotifiableEvent
|
||||
import io.element.android.services.toolbox.api.strings.StringProvider
|
||||
import io.element.android.services.toolbox.api.systemclock.SystemClock
|
||||
import javax.inject.Inject
|
||||
|
||||
class FallbackNotificationFactory @Inject constructor(
|
||||
private val clock: SystemClock,
|
||||
private val stringProvider: StringProvider,
|
||||
) {
|
||||
fun create(
|
||||
sessionId: SessionId,
|
||||
roomId: RoomId,
|
||||
eventId: EventId,
|
||||
): FallbackNotifiableEvent = FallbackNotifiableEvent(
|
||||
sessionId = sessionId,
|
||||
roomId = roomId,
|
||||
eventId = eventId,
|
||||
editedEventId = null,
|
||||
canBeReplaced = true,
|
||||
isRedacted = false,
|
||||
isUpdated = false,
|
||||
timestamp = clock.epochMillis(),
|
||||
description = stringProvider.getString(R.string.notification_fallback_content),
|
||||
)
|
||||
}
|
||||
@@ -1,10 +0,0 @@
|
||||
/*
|
||||
* Copyright 2025 New Vector Ltd.
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
|
||||
* Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
package io.element.android.libraries.push.impl.notifications
|
||||
|
||||
class ResolvingException(message: String) : Exception(message)
|
||||
@@ -16,16 +16,17 @@ import io.element.android.libraries.di.AppScope
|
||||
import io.element.android.libraries.di.SingleIn
|
||||
import io.element.android.libraries.di.annotations.AppCoroutineScope
|
||||
import io.element.android.libraries.matrix.api.auth.MatrixAuthenticationService
|
||||
import io.element.android.libraries.matrix.api.exception.NotificationResolverException
|
||||
import io.element.android.libraries.push.impl.history.PushHistoryService
|
||||
import io.element.android.libraries.push.impl.history.onDiagnosticPush
|
||||
import io.element.android.libraries.push.impl.history.onInvalidPushReceived
|
||||
import io.element.android.libraries.push.impl.history.onSuccess
|
||||
import io.element.android.libraries.push.impl.history.onUnableToResolveEvent
|
||||
import io.element.android.libraries.push.impl.history.onUnableToRetrieveSession
|
||||
import io.element.android.libraries.push.impl.notifications.FallbackNotificationFactory
|
||||
import io.element.android.libraries.push.impl.notifications.NotificationEventRequest
|
||||
import io.element.android.libraries.push.impl.notifications.NotificationResolverQueue
|
||||
import io.element.android.libraries.push.impl.notifications.channels.NotificationChannels
|
||||
import io.element.android.libraries.push.impl.notifications.model.FallbackNotifiableEvent
|
||||
import io.element.android.libraries.push.impl.notifications.model.NotifiableEvent
|
||||
import io.element.android.libraries.push.impl.notifications.model.NotifiableRingingCallEvent
|
||||
import io.element.android.libraries.push.impl.notifications.model.ResolvedPushEvent
|
||||
@@ -63,6 +64,7 @@ class DefaultPushHandler @Inject constructor(
|
||||
private val resolverQueue: NotificationResolverQueue,
|
||||
@AppCoroutineScope
|
||||
private val appCoroutineScope: CoroutineScope,
|
||||
private val fallbackNotificationFactory: FallbackNotificationFactory,
|
||||
) : PushHandler {
|
||||
init {
|
||||
processPushEventResults()
|
||||
@@ -88,34 +90,37 @@ class DefaultPushHandler @Inject constructor(
|
||||
} else {
|
||||
result.fold(
|
||||
onSuccess = {
|
||||
if (it is ResolvedPushEvent.Event && it.notifiableEvent is FallbackNotifiableEvent) {
|
||||
pushHistoryService.onUnableToResolveEvent(
|
||||
providerInfo = request.providerInfo,
|
||||
eventId = request.eventId,
|
||||
roomId = request.roomId,
|
||||
sessionId = request.sessionId,
|
||||
reason = "Showing fallback notification",
|
||||
)
|
||||
mutableBatteryOptimizationStore.showBatteryOptimizationBanner()
|
||||
} else {
|
||||
pushHistoryService.onSuccess(
|
||||
providerInfo = request.providerInfo,
|
||||
eventId = request.eventId,
|
||||
roomId = request.roomId,
|
||||
sessionId = request.sessionId,
|
||||
comment = "Push handled successfully",
|
||||
)
|
||||
},
|
||||
onFailure = { exception ->
|
||||
if (exception is NotificationResolverException.EventFilteredOut) {
|
||||
pushHistoryService.onSuccess(
|
||||
providerInfo = request.providerInfo,
|
||||
eventId = request.eventId,
|
||||
roomId = request.roomId,
|
||||
sessionId = request.sessionId,
|
||||
comment = "Push handled successfully",
|
||||
comment = "Push handled successfully but notification was filtered out",
|
||||
)
|
||||
} else {
|
||||
val reason = when (exception) {
|
||||
is NotificationResolverException.EventNotFound -> "Event not found"
|
||||
else -> "Unknown error: ${exception.message}"
|
||||
}
|
||||
pushHistoryService.onUnableToResolveEvent(
|
||||
providerInfo = request.providerInfo,
|
||||
eventId = request.eventId,
|
||||
roomId = request.roomId,
|
||||
sessionId = request.sessionId,
|
||||
reason = "$reason - Showing fallback notification",
|
||||
)
|
||||
mutableBatteryOptimizationStore.showBatteryOptimizationBanner()
|
||||
}
|
||||
},
|
||||
onFailure = { exception ->
|
||||
pushHistoryService.onUnableToResolveEvent(
|
||||
providerInfo = request.providerInfo,
|
||||
eventId = request.eventId,
|
||||
roomId = request.roomId,
|
||||
sessionId = request.sessionId,
|
||||
reason = exception.message ?: exception.javaClass.simpleName,
|
||||
)
|
||||
mutableBatteryOptimizationStore.showBatteryOptimizationBanner()
|
||||
}
|
||||
)
|
||||
}
|
||||
@@ -125,8 +130,21 @@ class DefaultPushHandler @Inject constructor(
|
||||
val redactions = mutableListOf<ResolvedPushEvent.Redaction>()
|
||||
|
||||
@Suppress("LoopWithTooManyJumpStatements")
|
||||
for (result in resolvedEvents.values) {
|
||||
val event = result.getOrNull() ?: continue
|
||||
for ((request, result) in resolvedEvents) {
|
||||
val event = result.recover { exception ->
|
||||
// If the event could not be resolved, we create a fallback notification
|
||||
when (exception) {
|
||||
is NotificationResolverException.EventFilteredOut -> {
|
||||
// Do nothing, we don't want to show a notification for filtered out events
|
||||
null
|
||||
}
|
||||
else -> {
|
||||
Timber.tag(loggerTag.value).e(exception, "Failed to resolve push event")
|
||||
ResolvedPushEvent.Event(fallbackNotificationFactory.create(request.sessionId, request.roomId, request.eventId))
|
||||
}
|
||||
}
|
||||
}.getOrNull() ?: continue
|
||||
|
||||
val userPushStore = userPushStoreFactory.getOrCreate(event.sessionId)
|
||||
val areNotificationsEnabled = userPushStore.getNotificationEnabledForDevice().first()
|
||||
// If notifications are disabled for this session and device, we don't want to show the notification
|
||||
|
||||
@@ -10,6 +10,7 @@ package io.element.android.libraries.push.impl.notifications
|
||||
import android.content.Context
|
||||
import com.google.common.truth.Truth.assertThat
|
||||
import io.element.android.libraries.matrix.api.core.EventId
|
||||
import io.element.android.libraries.matrix.api.exception.NotificationResolverException
|
||||
import io.element.android.libraries.matrix.api.media.MediaSource
|
||||
import io.element.android.libraries.matrix.api.notification.CallNotifyType
|
||||
import io.element.android.libraries.matrix.api.notification.NotificationContent
|
||||
@@ -51,6 +52,7 @@ import io.element.android.libraries.push.impl.notifications.model.NotifiableMess
|
||||
import io.element.android.libraries.push.impl.notifications.model.ResolvedPushEvent
|
||||
import io.element.android.libraries.push.test.notifications.FakeCallNotificationEventResolver
|
||||
import io.element.android.services.toolbox.impl.strings.AndroidStringProvider
|
||||
import io.element.android.services.toolbox.test.strings.FakeStringProvider
|
||||
import io.element.android.services.toolbox.test.systemclock.A_FAKE_TIMESTAMP
|
||||
import io.element.android.services.toolbox.test.systemclock.FakeSystemClock
|
||||
import kotlinx.coroutines.test.runTest
|
||||
@@ -71,12 +73,22 @@ class DefaultNotifiableEventResolverTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `resolve event failure`() = runTest {
|
||||
fun `resolve fetching failure`() = runTest {
|
||||
val sut = createDefaultNotifiableEventResolver(
|
||||
notificationResult = Result.failure(AN_EXCEPTION)
|
||||
)
|
||||
val request = NotificationEventRequest(A_SESSION_ID, A_ROOM_ID, AN_EVENT_ID, "firebase")
|
||||
val result = sut.resolveEvents(A_SESSION_ID, listOf(request))
|
||||
assertThat(result.isFailure).isTrue()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `resolve event failure`() = runTest {
|
||||
val sut = createDefaultNotifiableEventResolver(
|
||||
notificationResult = Result.success(mapOf(AN_EVENT_ID to Result.failure(AN_EXCEPTION)))
|
||||
)
|
||||
val request = NotificationEventRequest(A_SESSION_ID, A_ROOM_ID, AN_EVENT_ID, "firebase")
|
||||
val result = sut.resolveEvents(A_SESSION_ID, listOf(request))
|
||||
assertThat(result.getEvent(request)?.isFailure).isTrue()
|
||||
}
|
||||
|
||||
@@ -85,12 +97,12 @@ class DefaultNotifiableEventResolverTest {
|
||||
val sut = createDefaultNotifiableEventResolver(
|
||||
notificationResult = Result.success(
|
||||
mapOf(
|
||||
AN_EVENT_ID to aNotificationData(
|
||||
AN_EVENT_ID to Result.success(aNotificationData(
|
||||
content = NotificationContent.MessageLike.RoomMessage(
|
||||
senderId = A_USER_ID_2,
|
||||
messageType = TextMessageType(body = "Hello world", formatted = null)
|
||||
),
|
||||
)
|
||||
))
|
||||
)
|
||||
)
|
||||
)
|
||||
@@ -108,13 +120,13 @@ class DefaultNotifiableEventResolverTest {
|
||||
val sut = createDefaultNotifiableEventResolver(
|
||||
notificationResult = Result.success(
|
||||
mapOf(
|
||||
AN_EVENT_ID to aNotificationData(
|
||||
AN_EVENT_ID to Result.success(aNotificationData(
|
||||
content = NotificationContent.MessageLike.RoomMessage(
|
||||
senderId = A_USER_ID_2,
|
||||
messageType = TextMessageType(body = "Hello world", formatted = null)
|
||||
),
|
||||
hasMention = true,
|
||||
)
|
||||
))
|
||||
)
|
||||
)
|
||||
)
|
||||
@@ -131,7 +143,7 @@ class DefaultNotifiableEventResolverTest {
|
||||
val sut = createDefaultNotifiableEventResolver(
|
||||
notificationResult = Result.success(
|
||||
mapOf(
|
||||
AN_EVENT_ID to aNotificationData(
|
||||
AN_EVENT_ID to Result.success(aNotificationData(
|
||||
content = NotificationContent.MessageLike.RoomMessage(
|
||||
senderId = A_USER_ID_2,
|
||||
messageType = TextMessageType(
|
||||
@@ -142,7 +154,7 @@ class DefaultNotifiableEventResolverTest {
|
||||
)
|
||||
)
|
||||
),
|
||||
)
|
||||
))
|
||||
)
|
||||
)
|
||||
)
|
||||
@@ -159,7 +171,7 @@ class DefaultNotifiableEventResolverTest {
|
||||
val sut = createDefaultNotifiableEventResolver(
|
||||
notificationResult = Result.success(
|
||||
mapOf(
|
||||
AN_EVENT_ID to aNotificationData(
|
||||
AN_EVENT_ID to Result.success(aNotificationData(
|
||||
content = NotificationContent.MessageLike.RoomMessage(
|
||||
senderId = A_USER_ID_2,
|
||||
messageType = TextMessageType(
|
||||
@@ -170,7 +182,7 @@ class DefaultNotifiableEventResolverTest {
|
||||
)
|
||||
)
|
||||
),
|
||||
)
|
||||
))
|
||||
)
|
||||
)
|
||||
)
|
||||
@@ -187,12 +199,12 @@ class DefaultNotifiableEventResolverTest {
|
||||
val sut = createDefaultNotifiableEventResolver(
|
||||
notificationResult = Result.success(
|
||||
mapOf(
|
||||
AN_EVENT_ID to aNotificationData(
|
||||
AN_EVENT_ID to Result.success(aNotificationData(
|
||||
content = NotificationContent.MessageLike.RoomMessage(
|
||||
senderId = A_USER_ID_2,
|
||||
messageType = AudioMessageType("Audio", null, null, MediaSource("url"), null)
|
||||
),
|
||||
)
|
||||
))
|
||||
)
|
||||
)
|
||||
)
|
||||
@@ -209,12 +221,12 @@ class DefaultNotifiableEventResolverTest {
|
||||
val sut = createDefaultNotifiableEventResolver(
|
||||
notificationResult = Result.success(
|
||||
mapOf(
|
||||
AN_EVENT_ID to aNotificationData(
|
||||
AN_EVENT_ID to Result.success(aNotificationData(
|
||||
content = NotificationContent.MessageLike.RoomMessage(
|
||||
senderId = A_USER_ID_2,
|
||||
messageType = VideoMessageType("Video", null, null, MediaSource("url"), null)
|
||||
),
|
||||
)
|
||||
))
|
||||
)
|
||||
)
|
||||
)
|
||||
@@ -231,12 +243,12 @@ class DefaultNotifiableEventResolverTest {
|
||||
val sut = createDefaultNotifiableEventResolver(
|
||||
notificationResult = Result.success(
|
||||
mapOf(
|
||||
AN_EVENT_ID to aNotificationData(
|
||||
AN_EVENT_ID to Result.success(aNotificationData(
|
||||
content = NotificationContent.MessageLike.RoomMessage(
|
||||
senderId = A_USER_ID_2,
|
||||
messageType = VoiceMessageType("Voice", null, null, MediaSource("url"), null, null)
|
||||
),
|
||||
)
|
||||
))
|
||||
)
|
||||
)
|
||||
)
|
||||
@@ -253,12 +265,12 @@ class DefaultNotifiableEventResolverTest {
|
||||
val sut = createDefaultNotifiableEventResolver(
|
||||
notificationResult = Result.success(
|
||||
mapOf(
|
||||
AN_EVENT_ID to aNotificationData(
|
||||
AN_EVENT_ID to Result.success(aNotificationData(
|
||||
content = NotificationContent.MessageLike.RoomMessage(
|
||||
senderId = A_USER_ID_2,
|
||||
messageType = ImageMessageType("Image", null, null, MediaSource("url"), null),
|
||||
),
|
||||
)
|
||||
))
|
||||
)
|
||||
)
|
||||
)
|
||||
@@ -275,12 +287,12 @@ class DefaultNotifiableEventResolverTest {
|
||||
val sut = createDefaultNotifiableEventResolver(
|
||||
notificationResult = Result.success(
|
||||
mapOf(
|
||||
AN_EVENT_ID to aNotificationData(
|
||||
AN_EVENT_ID to Result.success(aNotificationData(
|
||||
content = NotificationContent.MessageLike.RoomMessage(
|
||||
senderId = A_USER_ID_2,
|
||||
messageType = StickerMessageType("Sticker", null, null, MediaSource("url"), null),
|
||||
),
|
||||
)
|
||||
))
|
||||
)
|
||||
)
|
||||
)
|
||||
@@ -297,12 +309,12 @@ class DefaultNotifiableEventResolverTest {
|
||||
val sut = createDefaultNotifiableEventResolver(
|
||||
notificationResult = Result.success(
|
||||
mapOf(
|
||||
AN_EVENT_ID to aNotificationData(
|
||||
AN_EVENT_ID to Result.success(aNotificationData(
|
||||
content = NotificationContent.MessageLike.RoomMessage(
|
||||
senderId = A_USER_ID_2,
|
||||
messageType = FileMessageType("File", null, null, MediaSource("url"), null),
|
||||
),
|
||||
)
|
||||
))
|
||||
)
|
||||
)
|
||||
)
|
||||
@@ -319,12 +331,12 @@ class DefaultNotifiableEventResolverTest {
|
||||
val sut = createDefaultNotifiableEventResolver(
|
||||
notificationResult = Result.success(
|
||||
mapOf(
|
||||
AN_EVENT_ID to aNotificationData(
|
||||
AN_EVENT_ID to Result.success(aNotificationData(
|
||||
content = NotificationContent.MessageLike.RoomMessage(
|
||||
senderId = A_USER_ID_2,
|
||||
messageType = LocationMessageType("Location", "geo:1,2", null),
|
||||
),
|
||||
)
|
||||
))
|
||||
)
|
||||
)
|
||||
)
|
||||
@@ -341,12 +353,12 @@ class DefaultNotifiableEventResolverTest {
|
||||
val sut = createDefaultNotifiableEventResolver(
|
||||
notificationResult = Result.success(
|
||||
mapOf(
|
||||
AN_EVENT_ID to aNotificationData(
|
||||
AN_EVENT_ID to Result.success(aNotificationData(
|
||||
content = NotificationContent.MessageLike.RoomMessage(
|
||||
senderId = A_USER_ID_2,
|
||||
messageType = NoticeMessageType("Notice", null),
|
||||
),
|
||||
)
|
||||
))
|
||||
)
|
||||
)
|
||||
)
|
||||
@@ -363,12 +375,12 @@ class DefaultNotifiableEventResolverTest {
|
||||
val sut = createDefaultNotifiableEventResolver(
|
||||
notificationResult = Result.success(
|
||||
mapOf(
|
||||
AN_EVENT_ID to aNotificationData(
|
||||
AN_EVENT_ID to Result.success(aNotificationData(
|
||||
content = NotificationContent.MessageLike.RoomMessage(
|
||||
senderId = A_USER_ID_2,
|
||||
messageType = EmoteMessageType("is happy", null),
|
||||
),
|
||||
)
|
||||
))
|
||||
)
|
||||
)
|
||||
)
|
||||
@@ -385,12 +397,12 @@ class DefaultNotifiableEventResolverTest {
|
||||
val sut = createDefaultNotifiableEventResolver(
|
||||
notificationResult = Result.success(
|
||||
mapOf(
|
||||
AN_EVENT_ID to aNotificationData(
|
||||
AN_EVENT_ID to Result.success(aNotificationData(
|
||||
content = NotificationContent.MessageLike.Poll(
|
||||
senderId = A_USER_ID_2,
|
||||
question = "A question"
|
||||
),
|
||||
)
|
||||
))
|
||||
)
|
||||
)
|
||||
)
|
||||
@@ -407,13 +419,13 @@ class DefaultNotifiableEventResolverTest {
|
||||
val sut = createDefaultNotifiableEventResolver(
|
||||
notificationResult = Result.success(
|
||||
mapOf(
|
||||
AN_EVENT_ID to aNotificationData(
|
||||
AN_EVENT_ID to Result.success(aNotificationData(
|
||||
content = NotificationContent.StateEvent.RoomMemberContent(
|
||||
userId = A_USER_ID_2,
|
||||
membershipState = RoomMembershipState.INVITE
|
||||
),
|
||||
isDirect = false,
|
||||
)
|
||||
))
|
||||
)
|
||||
)
|
||||
)
|
||||
@@ -427,12 +439,12 @@ class DefaultNotifiableEventResolverTest {
|
||||
val sut = createDefaultNotifiableEventResolver(
|
||||
notificationResult = Result.success(
|
||||
mapOf(
|
||||
AN_EVENT_ID to aNotificationData(
|
||||
AN_EVENT_ID to Result.success(aNotificationData(
|
||||
content = NotificationContent.Invite(
|
||||
senderId = A_USER_ID_2,
|
||||
),
|
||||
isDirect = false,
|
||||
)
|
||||
))
|
||||
)
|
||||
)
|
||||
)
|
||||
@@ -464,12 +476,12 @@ class DefaultNotifiableEventResolverTest {
|
||||
val sut = createDefaultNotifiableEventResolver(
|
||||
notificationResult = Result.success(
|
||||
mapOf(
|
||||
AN_EVENT_ID to aNotificationData(
|
||||
AN_EVENT_ID to Result.success(aNotificationData(
|
||||
content = NotificationContent.Invite(
|
||||
senderId = A_USER_ID_2,
|
||||
),
|
||||
isDirect = true,
|
||||
)
|
||||
))
|
||||
)
|
||||
)
|
||||
)
|
||||
@@ -501,13 +513,13 @@ class DefaultNotifiableEventResolverTest {
|
||||
val sut = createDefaultNotifiableEventResolver(
|
||||
notificationResult = Result.success(
|
||||
mapOf(
|
||||
AN_EVENT_ID to aNotificationData(
|
||||
AN_EVENT_ID to Result.success(aNotificationData(
|
||||
content = NotificationContent.Invite(
|
||||
senderId = A_USER_ID_2,
|
||||
),
|
||||
isDirect = true,
|
||||
senderDisplayName = null,
|
||||
)
|
||||
))
|
||||
)
|
||||
)
|
||||
)
|
||||
@@ -539,7 +551,8 @@ class DefaultNotifiableEventResolverTest {
|
||||
val sut = createDefaultNotifiableEventResolver(
|
||||
notificationResult = Result.success(
|
||||
mapOf(
|
||||
AN_EVENT_ID to aNotificationData(
|
||||
AN_EVENT_ID to Result.success(
|
||||
aNotificationData(
|
||||
content = NotificationContent.Invite(
|
||||
senderId = A_USER_ID_2,
|
||||
),
|
||||
@@ -547,6 +560,7 @@ class DefaultNotifiableEventResolverTest {
|
||||
senderIsNameAmbiguous = true,
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
val request = NotificationEventRequest(A_SESSION_ID, A_ROOM_ID, AN_EVENT_ID, "firebase")
|
||||
@@ -577,12 +591,12 @@ class DefaultNotifiableEventResolverTest {
|
||||
val sut = createDefaultNotifiableEventResolver(
|
||||
notificationResult = Result.success(
|
||||
mapOf(
|
||||
AN_EVENT_ID to aNotificationData(
|
||||
AN_EVENT_ID to Result.success(aNotificationData(
|
||||
content = NotificationContent.StateEvent.RoomMemberContent(
|
||||
userId = A_USER_ID_2,
|
||||
membershipState = RoomMembershipState.JOIN
|
||||
)
|
||||
)
|
||||
))
|
||||
)
|
||||
)
|
||||
)
|
||||
@@ -595,7 +609,7 @@ class DefaultNotifiableEventResolverTest {
|
||||
fun `resolve RoomEncrypted`() = runTest {
|
||||
val sut = createDefaultNotifiableEventResolver(
|
||||
notificationResult = Result.success(
|
||||
mapOf(AN_EVENT_ID to aNotificationData(content = NotificationContent.MessageLike.RoomEncrypted))
|
||||
mapOf(AN_EVENT_ID to Result.success(aNotificationData(content = NotificationContent.MessageLike.RoomEncrypted)))
|
||||
)
|
||||
)
|
||||
val request = NotificationEventRequest(A_SESSION_ID, A_ROOM_ID, AN_EVENT_ID, "firebase")
|
||||
@@ -620,25 +634,12 @@ class DefaultNotifiableEventResolverTest {
|
||||
fun `resolve UnableToResolve`() = runTest {
|
||||
val sut = createDefaultNotifiableEventResolver(
|
||||
notificationResult = Result.success(
|
||||
mapOf(AN_EVENT_ID to aNotificationData(content = NotificationContent.MessageLike.UnableToResolve))
|
||||
mapOf(AN_EVENT_ID to Result.failure(NotificationResolverException.EventNotFound))
|
||||
)
|
||||
)
|
||||
val request = NotificationEventRequest(A_SESSION_ID, A_ROOM_ID, AN_EVENT_ID, "firebase")
|
||||
val result = sut.resolveEvents(A_SESSION_ID, listOf(request))
|
||||
val expectedResult = ResolvedPushEvent.Event(
|
||||
FallbackNotifiableEvent(
|
||||
sessionId = A_SESSION_ID,
|
||||
roomId = A_ROOM_ID,
|
||||
eventId = AN_EVENT_ID,
|
||||
editedEventId = null,
|
||||
description = "You have new messages.",
|
||||
canBeReplaced = true,
|
||||
isRedacted = false,
|
||||
isUpdated = false,
|
||||
timestamp = A_FAKE_TIMESTAMP,
|
||||
)
|
||||
)
|
||||
assertThat(result.getEvent(request)).isEqualTo(Result.success(expectedResult))
|
||||
assertThat(result.getEvent(request)).isEqualTo(Result.failure<ResolvedPushEvent?>(NotificationResolverException.EventNotFound))
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -646,10 +647,12 @@ class DefaultNotifiableEventResolverTest {
|
||||
val sut = createDefaultNotifiableEventResolver(
|
||||
notificationResult = Result.success(
|
||||
mapOf(
|
||||
AN_EVENT_ID to aNotificationData(
|
||||
AN_EVENT_ID to Result.success(
|
||||
aNotificationData(
|
||||
content = NotificationContent.MessageLike.CallInvite(A_USER_ID_2),
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
val request = NotificationEventRequest(A_SESSION_ID, A_ROOM_ID, AN_EVENT_ID, "firebase")
|
||||
@@ -688,12 +691,12 @@ class DefaultNotifiableEventResolverTest {
|
||||
val sut = createDefaultNotifiableEventResolver(
|
||||
notificationResult = Result.success(
|
||||
mapOf(
|
||||
AN_EVENT_ID to aNotificationData(
|
||||
AN_EVENT_ID to Result.success(aNotificationData(
|
||||
content = NotificationContent.MessageLike.CallNotify(
|
||||
A_USER_ID_2,
|
||||
CallNotifyType.NOTIFY
|
||||
),
|
||||
)
|
||||
))
|
||||
)
|
||||
),
|
||||
callNotificationEventResolver = callNotificationEventResolver,
|
||||
@@ -729,12 +732,12 @@ class DefaultNotifiableEventResolverTest {
|
||||
val sut = createDefaultNotifiableEventResolver(
|
||||
notificationResult = Result.success(
|
||||
mapOf(
|
||||
AN_EVENT_ID to aNotificationData(
|
||||
AN_EVENT_ID to Result.success(aNotificationData(
|
||||
content = NotificationContent.MessageLike.RoomRedaction(
|
||||
AN_EVENT_ID_2,
|
||||
A_REDACTION_REASON,
|
||||
)
|
||||
)
|
||||
))
|
||||
)
|
||||
)
|
||||
)
|
||||
@@ -754,12 +757,12 @@ class DefaultNotifiableEventResolverTest {
|
||||
val sut = createDefaultNotifiableEventResolver(
|
||||
notificationResult = Result.success(
|
||||
mapOf(
|
||||
AN_EVENT_ID to aNotificationData(
|
||||
AN_EVENT_ID to Result.success(aNotificationData(
|
||||
content = NotificationContent.MessageLike.RoomRedaction(
|
||||
null,
|
||||
A_REDACTION_REASON,
|
||||
)
|
||||
)
|
||||
))
|
||||
)
|
||||
)
|
||||
)
|
||||
@@ -807,7 +810,7 @@ class DefaultNotifiableEventResolverTest {
|
||||
private fun testNoResults(content: NotificationContent) = runTest {
|
||||
val sut = createDefaultNotifiableEventResolver(
|
||||
notificationResult = Result.success(
|
||||
mapOf(AN_EVENT_ID to aNotificationData(content = content))
|
||||
mapOf(AN_EVENT_ID to Result.success(aNotificationData(content = content)))
|
||||
)
|
||||
)
|
||||
val request = NotificationEventRequest(A_SESSION_ID, A_ROOM_ID, AN_EVENT_ID, "firebase")
|
||||
@@ -823,7 +826,7 @@ class DefaultNotifiableEventResolverTest {
|
||||
|
||||
private fun createDefaultNotifiableEventResolver(
|
||||
notificationService: FakeNotificationService? = FakeNotificationService(),
|
||||
notificationResult: Result<Map<EventId, NotificationData>> = Result.success(emptyMap()),
|
||||
notificationResult: Result<Map<EventId, Result<NotificationData>>> = Result.success(emptyMap()),
|
||||
callNotificationEventResolver: FakeCallNotificationEventResolver = FakeCallNotificationEventResolver(),
|
||||
): DefaultNotifiableEventResolver {
|
||||
val context = RuntimeEnvironment.getApplication() as Context
|
||||
@@ -840,12 +843,15 @@ class DefaultNotifiableEventResolverTest {
|
||||
}
|
||||
return DefaultNotifiableEventResolver(
|
||||
stringProvider = AndroidStringProvider(context.resources),
|
||||
clock = FakeSystemClock(),
|
||||
matrixClientProvider = matrixClientProvider,
|
||||
notificationMediaRepoFactory = notificationMediaRepoFactory,
|
||||
context = context,
|
||||
permalinkParser = FakePermalinkParser(),
|
||||
callNotificationEventResolver = callNotificationEventResolver,
|
||||
fallbackNotificationFactory = FallbackNotificationFactory(
|
||||
clock = FakeSystemClock(),
|
||||
stringProvider = FakeStringProvider(defaultResult = "You have new messages.")
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -43,7 +43,7 @@ class DefaultOnMissedCallNotificationHandlerTest {
|
||||
val matrixClientProvider = FakeMatrixClientProvider(getClient = {
|
||||
val notificationService = FakeNotificationService().apply {
|
||||
givenGetNotificationsResult(
|
||||
Result.success(mapOf(AN_EVENT_ID to aNotificationData(senderDisplayName = A_USER_NAME, senderIsNameAmbiguous = false)))
|
||||
Result.success(mapOf(AN_EVENT_ID to Result.success(aNotificationData(senderDisplayName = A_USER_NAME, senderIsNameAmbiguous = false))))
|
||||
)
|
||||
}
|
||||
Result.success(FakeMatrixClient(notificationService = notificationService))
|
||||
|
||||
@@ -18,6 +18,7 @@ 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
|
||||
import io.element.android.libraries.matrix.api.core.UserId
|
||||
import io.element.android.libraries.matrix.api.exception.NotificationResolverException
|
||||
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.test.AN_EVENT_ID
|
||||
@@ -31,9 +32,9 @@ import io.element.android.libraries.matrix.test.core.aBuildMeta
|
||||
import io.element.android.libraries.push.impl.history.FakePushHistoryService
|
||||
import io.element.android.libraries.push.impl.history.PushHistoryService
|
||||
import io.element.android.libraries.push.impl.notifications.FakeNotifiableEventResolver
|
||||
import io.element.android.libraries.push.impl.notifications.FallbackNotificationFactory
|
||||
import io.element.android.libraries.push.impl.notifications.NotificationEventRequest
|
||||
import io.element.android.libraries.push.impl.notifications.NotificationResolverQueue
|
||||
import io.element.android.libraries.push.impl.notifications.ResolvingException
|
||||
import io.element.android.libraries.push.impl.notifications.channels.FakeNotificationChannels
|
||||
import io.element.android.libraries.push.impl.notifications.fixtures.aNotifiableCallEvent
|
||||
import io.element.android.libraries.push.impl.notifications.fixtures.aNotifiableMessageEvent
|
||||
@@ -47,6 +48,8 @@ import io.element.android.libraries.pushstore.api.clientsecret.PushClientSecret
|
||||
import io.element.android.libraries.pushstore.test.userpushstore.FakeUserPushStore
|
||||
import io.element.android.libraries.pushstore.test.userpushstore.FakeUserPushStoreFactory
|
||||
import io.element.android.libraries.pushstore.test.userpushstore.clientsecret.FakePushClientSecret
|
||||
import io.element.android.services.toolbox.test.strings.FakeStringProvider
|
||||
import io.element.android.services.toolbox.test.systemclock.FakeSystemClock
|
||||
import io.element.android.tests.testutils.lambda.any
|
||||
import io.element.android.tests.testutils.lambda.lambdaError
|
||||
import io.element.android.tests.testutils.lambda.lambdaRecorder
|
||||
@@ -271,7 +274,7 @@ class DefaultPushHandlerTest {
|
||||
fun `when classical PushData is received, but a failure occurs (session not found), nothing happen`() {
|
||||
`test notification resolver failure`(
|
||||
notificationResolveResult = { _ ->
|
||||
Result.failure(ResolvingException("Unable to restore session"))
|
||||
Result.failure(NotificationResolverException.UnknownError("Unable to restore session"))
|
||||
},
|
||||
shouldSetOptimizationBatteryBanner = false,
|
||||
)
|
||||
@@ -282,7 +285,7 @@ class DefaultPushHandlerTest {
|
||||
`test notification resolver failure`(
|
||||
notificationResolveResult = { requests: List<NotificationEventRequest> ->
|
||||
Result.success(
|
||||
requests.associateWith { Result.failure(ResolvingException("Unable to resolve event")) }
|
||||
requests.associateWith { Result.failure(NotificationResolverException.UnknownError("Unable to resolve event")) }
|
||||
)
|
||||
},
|
||||
shouldSetOptimizationBatteryBanner = true,
|
||||
@@ -336,8 +339,6 @@ class DefaultPushHandlerTest {
|
||||
notifiableEventResult.assertions()
|
||||
.isCalledOnce()
|
||||
.with(value(A_USER_ID), any())
|
||||
onNotifiableEventsReceived.assertions()
|
||||
.isNeverCalled()
|
||||
onPushReceivedResult.assertions()
|
||||
.isCalledOnce()
|
||||
.with(any(), value(AN_EVENT_ID), value(A_ROOM_ID), value(A_USER_ID), value(false), value(true), any())
|
||||
@@ -662,6 +663,10 @@ class DefaultPushHandlerTest {
|
||||
pushHistoryService = pushHistoryService,
|
||||
resolverQueue = NotificationResolverQueue(notifiableEventResolver = FakeNotifiableEventResolver(notifiableEventsResult), backgroundScope),
|
||||
appCoroutineScope = backgroundScope,
|
||||
fallbackNotificationFactory = FallbackNotificationFactory(
|
||||
clock = FakeSystemClock(),
|
||||
stringProvider = FakeStringProvider(),
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user