diff --git a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/DefaultNotifiableEventResolver.kt b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/DefaultNotifiableEventResolver.kt index c0fca17d81..00af4881e9 100644 --- a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/DefaultNotifiableEventResolver.kt +++ b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/DefaultNotifiableEventResolver.kt @@ -236,7 +236,12 @@ class DefaultNotifiableEventResolver @Inject constructor( } NotificationContent.MessageLike.RoomEncrypted -> { Timber.tag(loggerTag.value).w("Notification with encrypted content -> fallback") - val fallbackNotifiableEvent = fallbackNotificationFactory.create(userId, roomId, eventId) + val fallbackNotifiableEvent = fallbackNotificationFactory.create( + sessionId = userId, + roomId = roomId, + eventId = eventId, + cause = "Unable to decrypt event content", + ) ResolvedPushEvent.Event(fallbackNotifiableEvent) } is NotificationContent.MessageLike.RoomRedaction -> { diff --git a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/FallbackNotificationFactory.kt b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/FallbackNotificationFactory.kt index 947e62bf75..8e206335b9 100644 --- a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/FallbackNotificationFactory.kt +++ b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/FallbackNotificationFactory.kt @@ -24,6 +24,7 @@ class FallbackNotificationFactory @Inject constructor( sessionId: SessionId, roomId: RoomId, eventId: EventId, + cause: String?, ): FallbackNotifiableEvent = FallbackNotifiableEvent( sessionId = sessionId, roomId = roomId, @@ -34,5 +35,6 @@ class FallbackNotificationFactory @Inject constructor( isUpdated = false, timestamp = clock.epochMillis(), description = stringProvider.getString(R.string.notification_fallback_content), + cause = cause, ) } diff --git a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/model/FallbackNotifiableEvent.kt b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/model/FallbackNotifiableEvent.kt index 91ec1e078c..f9a08de195 100644 --- a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/model/FallbackNotifiableEvent.kt +++ b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/model/FallbackNotifiableEvent.kt @@ -25,4 +25,5 @@ data class FallbackNotifiableEvent( override val isRedacted: Boolean, override val isUpdated: Boolean, val timestamp: Long, + val cause: String?, ) : NotifiableEvent diff --git a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/push/DefaultPushHandler.kt b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/push/DefaultPushHandler.kt index dc2e7bab0a..6967692a46 100644 --- a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/push/DefaultPushHandler.kt +++ b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/push/DefaultPushHandler.kt @@ -27,6 +27,7 @@ import io.element.android.libraries.push.impl.notifications.FallbackNotification 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 @@ -90,13 +91,23 @@ class DefaultPushHandler @Inject constructor( } else { result.fold( onSuccess = { - pushHistoryService.onSuccess( - providerInfo = request.providerInfo, - eventId = request.eventId, - roomId = request.roomId, - sessionId = request.sessionId, - comment = "Push handled successfully", - ) + if (it is ResolvedPushEvent.Event && it.notifiableEvent is FallbackNotifiableEvent) { + pushHistoryService.onUnableToResolveEvent( + providerInfo = request.providerInfo, + eventId = request.eventId, + roomId = request.roomId, + sessionId = request.sessionId, + reason = it.notifiableEvent.cause.orEmpty(), + ) + } 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) { @@ -140,7 +151,14 @@ class DefaultPushHandler @Inject constructor( } else -> { Timber.tag(loggerTag.value).e(exception, "Failed to resolve push event") - ResolvedPushEvent.Event(fallbackNotificationFactory.create(request.sessionId, request.roomId, request.eventId)) + ResolvedPushEvent.Event( + fallbackNotificationFactory.create( + sessionId = request.sessionId, + roomId = request.roomId, + eventId = request.eventId, + cause = exception.message, + ) + ) } } }.getOrNull() ?: continue diff --git a/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/DefaultNotifiableEventResolverTest.kt b/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/DefaultNotifiableEventResolverTest.kt index a6e443b403..2d99e31d74 100644 --- a/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/DefaultNotifiableEventResolverTest.kt +++ b/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/DefaultNotifiableEventResolverTest.kt @@ -625,6 +625,7 @@ class DefaultNotifiableEventResolverTest { isRedacted = false, isUpdated = false, timestamp = A_FAKE_TIMESTAMP, + cause = "Unable to decrypt event content", ) ) assertThat(result.getEvent(request)).isEqualTo(Result.success(expectedResult)) diff --git a/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/factories/DefaultNotificationCreatorTest.kt b/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/factories/DefaultNotificationCreatorTest.kt index 155fcd108f..5fd1b6386e 100644 --- a/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/factories/DefaultNotificationCreatorTest.kt +++ b/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/factories/DefaultNotificationCreatorTest.kt @@ -71,6 +71,7 @@ class DefaultNotificationCreatorTest { isRedacted = false, isUpdated = false, timestamp = A_FAKE_TIMESTAMP, + cause = null, ) ) result.commonAssertions( diff --git a/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/push/DefaultPushHandlerTest.kt b/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/push/DefaultPushHandlerTest.kt index 6ec5accd02..a070156d05 100644 --- a/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/push/DefaultPushHandlerTest.kt +++ b/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/push/DefaultPushHandlerTest.kt @@ -10,6 +10,7 @@ package io.element.android.libraries.push.impl.push import app.cash.turbine.test +import com.google.common.truth.Truth.assertThat import io.element.android.features.call.api.CallType import io.element.android.features.call.test.FakeElementCallEntryPoint import io.element.android.libraries.core.meta.BuildMeta @@ -38,6 +39,7 @@ import io.element.android.libraries.push.impl.notifications.NotificationResolver 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 +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.ResolvedPushEvent import io.element.android.libraries.push.impl.test.DefaultTestPush @@ -65,6 +67,7 @@ import kotlin.time.Duration.Companion.milliseconds private const val A_PUSHER_INFO = "info" +@Suppress("LargeClass") class DefaultPushHandlerTest { @Test fun `check handleInvalid behavior`() = runTest { @@ -628,6 +631,59 @@ class DefaultPushHandlerTest { .isCalledExactly(2) } + @Test + fun `when receiving a fallback event, we notify the push history service about it not being resolved`() = runTest { + val aNotifiableFallbackEvent = FallbackNotifiableEvent( + sessionId = A_SESSION_ID, + roomId = A_ROOM_ID, + eventId = AN_EVENT_ID, + editedEventId = null, + description = "A fallback notification", + canBeReplaced = false, + isRedacted = false, + isUpdated = false, + timestamp = 0L, + cause = "Unable to decrypt event", + ) + val notifiableEventResult = + lambdaRecorder, Result>>> { _, _ -> + val request = NotificationEventRequest(A_SESSION_ID, A_ROOM_ID, AN_EVENT_ID, A_PUSHER_INFO) + Result.success(mapOf(request to Result.success(ResolvedPushEvent.Event(aNotifiableFallbackEvent)))) + } + val onNotifiableEventsReceived = lambdaRecorder, Unit> {} + val incrementPushCounterResult = lambdaRecorder {} + var receivedFallbackEvent = false + val onPushReceivedResult = + lambdaRecorder { _, _, _, _, isResolved, _, comment -> + receivedFallbackEvent = !isResolved && comment == "Unable to resolve event: ${aNotifiableFallbackEvent.cause}" + } + val pushHistoryService = FakePushHistoryService( + onPushReceivedResult = onPushReceivedResult, + ) + val aPushData = PushData( + eventId = AN_EVENT_ID, + roomId = A_ROOM_ID, + unread = 0, + clientSecret = A_SECRET, + ) + val defaultPushHandler = createDefaultPushHandler( + onNotifiableEventsReceived = onNotifiableEventsReceived, + notifiableEventsResult = notifiableEventResult, + pushClientSecret = FakePushClientSecret( + getUserIdFromSecretResult = { A_USER_ID } + ), + incrementPushCounterResult = incrementPushCounterResult, + pushHistoryService = pushHistoryService, + ) + defaultPushHandler.handle(aPushData, A_PUSHER_INFO) + + advanceTimeBy(300.milliseconds) + + onNotifiableEventsReceived.assertions().isCalledOnce() + + assertThat(receivedFallbackEvent).isTrue() + } + private fun TestScope.createDefaultPushHandler( onNotifiableEventsReceived: (List) -> Unit = { lambdaError() }, onRedactedEventsReceived: (List) -> Unit = { lambdaError() },