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:
parent
ca206617c4
commit
04a1c00b94
18 changed files with 313 additions and 200 deletions
|
|
@ -173,7 +173,7 @@ jsoup = "org.jsoup:jsoup:1.21.1"
|
||||||
appyx_core = { module = "com.bumble.appyx:core", version.ref = "appyx" }
|
appyx_core = { module = "com.bumble.appyx:core", version.ref = "appyx" }
|
||||||
molecule-runtime = "app.cash.molecule:molecule-runtime:2.1.0"
|
molecule-runtime = "app.cash.molecule:molecule-runtime:2.1.0"
|
||||||
timber = "com.jakewharton.timber:timber:5.0.1"
|
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 = { module = "io.element.android:wysiwyg", version.ref = "wysiwyg" }
|
||||||
matrix_richtexteditor_compose = { module = "io.element.android:wysiwyg-compose", 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" }
|
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
|
) : MessageLike
|
||||||
|
|
||||||
data object RoomEncrypted : MessageLike
|
data object RoomEncrypted : MessageLike
|
||||||
data object UnableToResolve : MessageLike
|
|
||||||
data class RoomMessage(
|
data class RoomMessage(
|
||||||
val senderId: UserId,
|
val senderId: UserId,
|
||||||
val messageType: MessageType
|
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.EventId
|
||||||
import io.element.android.libraries.matrix.api.core.RoomId
|
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 {
|
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.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.matrix.api.notification.NotificationContent
|
import io.element.android.libraries.matrix.api.exception.NotificationResolverException
|
||||||
import io.element.android.libraries.matrix.api.notification.NotificationData
|
import io.element.android.libraries.matrix.api.notification.GetNotificationDataResult
|
||||||
import io.element.android.libraries.matrix.api.notification.NotificationService
|
import io.element.android.libraries.matrix.api.notification.NotificationService
|
||||||
import io.element.android.services.toolbox.api.systemclock.SystemClock
|
import io.element.android.services.toolbox.api.systemclock.SystemClock
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
|
import org.matrix.rustcomponents.sdk.BatchNotificationResult
|
||||||
import org.matrix.rustcomponents.sdk.NotificationClient
|
import org.matrix.rustcomponents.sdk.NotificationClient
|
||||||
import org.matrix.rustcomponents.sdk.NotificationItemsRequest
|
import org.matrix.rustcomponents.sdk.NotificationItemsRequest
|
||||||
|
import org.matrix.rustcomponents.sdk.NotificationStatus
|
||||||
|
import org.matrix.rustcomponents.sdk.use
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
|
|
||||||
class RustNotificationService(
|
class RustNotificationService(
|
||||||
private val sessionId: SessionId,
|
private val sessionId: SessionId,
|
||||||
private val notificationClient: NotificationClient,
|
private val notificationClient: NotificationClient,
|
||||||
private val dispatchers: CoroutineDispatchers,
|
private val dispatchers: CoroutineDispatchers,
|
||||||
private val clock: SystemClock,
|
clock: SystemClock,
|
||||||
) : NotificationService {
|
) : NotificationService {
|
||||||
private val notificationMapper: NotificationMapper = NotificationMapper(clock)
|
private val notificationMapper: NotificationMapper = NotificationMapper(clock)
|
||||||
|
|
||||||
override suspend fun getNotifications(
|
override suspend fun getNotifications(
|
||||||
ids: Map<RoomId, List<EventId>>
|
ids: Map<RoomId, List<EventId>>
|
||||||
): Result<Map<EventId, NotificationData>> = withContext(dispatchers.io) {
|
): GetNotificationDataResult = withContext(dispatchers.io) {
|
||||||
runCatchingExceptions {
|
runCatchingExceptions {
|
||||||
val requests = ids.map { (roomId, eventIds) ->
|
val requests = ids.map { (roomId, eventIds) ->
|
||||||
NotificationItemsRequest(
|
NotificationItemsRequest(
|
||||||
|
|
@ -42,34 +45,41 @@ class RustNotificationService(
|
||||||
val items = notificationClient.getNotifications(requests)
|
val items = notificationClient.getNotifications(requests)
|
||||||
buildMap {
|
buildMap {
|
||||||
val eventIds = requests.flatMap { it.eventIds }
|
val eventIds = requests.flatMap { it.eventIds }
|
||||||
for (eventId in eventIds) {
|
for (rawEventId in eventIds) {
|
||||||
val item = items[eventId]
|
val roomId = RoomId(requests.find { it.eventIds.contains(rawEventId) }?.roomId!!)
|
||||||
val roomId = RoomId(requests.find { it.eventIds.contains(eventId) }?.roomId!!)
|
val eventId = EventId(rawEventId)
|
||||||
if (item != null) {
|
items[rawEventId].use { result ->
|
||||||
put(EventId(eventId), notificationMapper.map(sessionId, EventId(eventId), roomId, item))
|
when (result) {
|
||||||
} else {
|
is BatchNotificationResult.Ok -> {
|
||||||
Timber.e("Could not retrieve event for notification with $eventId")
|
when (val status = result.status) {
|
||||||
put(
|
is NotificationStatus.Event -> {
|
||||||
EventId(eventId),
|
put(eventId, Result.success(notificationMapper.map(sessionId, eventId, roomId, status.item)))
|
||||||
NotificationData(
|
}
|
||||||
sessionId = sessionId,
|
is NotificationStatus.EventNotFound -> {
|
||||||
eventId = EventId(eventId),
|
Timber.e("Could not retrieve event for notification with $eventId - event not found")
|
||||||
threadId = null,
|
put(eventId, Result.failure(NotificationResolverException.EventNotFound))
|
||||||
roomId = roomId,
|
}
|
||||||
senderAvatarUrl = null,
|
is NotificationStatus.EventFilteredOut -> {
|
||||||
senderDisplayName = null,
|
Timber.d("Could not retrieve event for notification with $eventId - event filtered out")
|
||||||
senderIsNameAmbiguous = false,
|
put(eventId, Result.failure(NotificationResolverException.EventFilteredOut))
|
||||||
roomAvatarUrl = null,
|
}
|
||||||
roomDisplayName = null,
|
}
|
||||||
isDirect = false,
|
}
|
||||||
isDm = false,
|
is BatchNotificationResult.Error -> {
|
||||||
isEncrypted = false,
|
Timber.e("Error while retrieving notification with $rawEventId - ${result.message}")
|
||||||
isNoisy = false,
|
put(
|
||||||
timestamp = clock.epochMillis(),
|
eventId,
|
||||||
content = NotificationContent.MessageLike.UnableToResolve,
|
Result.failure(NotificationResolverException.UnknownError(result.message))
|
||||||
hasMention = false
|
)
|
||||||
)
|
}
|
||||||
)
|
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.NotificationItem
|
||||||
import org.matrix.rustcomponents.sdk.NotificationRoomInfo
|
import org.matrix.rustcomponents.sdk.NotificationRoomInfo
|
||||||
import org.matrix.rustcomponents.sdk.NotificationSenderInfo
|
import org.matrix.rustcomponents.sdk.NotificationSenderInfo
|
||||||
|
import org.matrix.rustcomponents.sdk.NotificationStatus
|
||||||
import org.matrix.rustcomponents.sdk.TimelineEvent
|
import org.matrix.rustcomponents.sdk.TimelineEvent
|
||||||
|
|
||||||
fun aRustNotificationItem(
|
fun aRustNotificationItem(
|
||||||
|
|
@ -34,6 +35,12 @@ fun aRustNotificationItem(
|
||||||
threadId = threadId?.value,
|
threadId = threadId?.value,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
fun aRustBatchNotificationResult(
|
||||||
|
notificationStatus: NotificationStatus = NotificationStatus.Event(aRustNotificationItem()),
|
||||||
|
) = org.matrix.rustcomponents.sdk.BatchNotificationResult.Ok(
|
||||||
|
status = notificationStatus,
|
||||||
|
)
|
||||||
|
|
||||||
fun aRustNotificationSenderInfo(
|
fun aRustNotificationSenderInfo(
|
||||||
displayName: String? = A_USER_NAME,
|
displayName: String? = A_USER_NAME,
|
||||||
avatarUrl: String? = null,
|
avatarUrl: String? = null,
|
||||||
|
|
|
||||||
|
|
@ -7,16 +7,16 @@
|
||||||
|
|
||||||
package io.element.android.libraries.matrix.impl.fixtures.fakes
|
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.NoPointer
|
||||||
import org.matrix.rustcomponents.sdk.NotificationClient
|
import org.matrix.rustcomponents.sdk.NotificationClient
|
||||||
import org.matrix.rustcomponents.sdk.NotificationItem
|
|
||||||
import org.matrix.rustcomponents.sdk.NotificationItemsRequest
|
import org.matrix.rustcomponents.sdk.NotificationItemsRequest
|
||||||
|
|
||||||
class FakeFfiNotificationClient(
|
class FakeFfiNotificationClient(
|
||||||
var notificationItemResult: Map<String, NotificationItem> = emptyMap(),
|
var notificationItemResult: Map<String, BatchNotificationResult> = emptyMap(),
|
||||||
val closeResult: () -> Unit = { }
|
val closeResult: () -> Unit = { }
|
||||||
) : NotificationClient(NoPointer) {
|
) : NotificationClient(NoPointer) {
|
||||||
override suspend fun getNotifications(requests: List<NotificationItemsRequest>): Map<String, NotificationItem> {
|
override suspend fun getNotifications(requests: List<NotificationItemsRequest>): Map<String, BatchNotificationResult> {
|
||||||
return notificationItemResult
|
return notificationItemResult
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -8,9 +8,10 @@
|
||||||
package io.element.android.libraries.matrix.impl.notification
|
package io.element.android.libraries.matrix.impl.notification
|
||||||
|
|
||||||
import com.google.common.truth.Truth.assertThat
|
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.notification.NotificationContent
|
||||||
import io.element.android.libraries.matrix.api.timeline.item.event.TextMessageType
|
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.impl.fixtures.fakes.FakeFfiNotificationClient
|
||||||
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.A_MESSAGE
|
import io.element.android.libraries.matrix.test.A_MESSAGE
|
||||||
|
|
@ -30,12 +31,12 @@ class RustNotificationServiceTest {
|
||||||
@Test
|
@Test
|
||||||
fun test() = runTest {
|
fun test() = runTest {
|
||||||
val notificationClient = FakeFfiNotificationClient(
|
val notificationClient = FakeFfiNotificationClient(
|
||||||
notificationItemResult = mapOf(AN_EVENT_ID.value to aRustNotificationItem()),
|
notificationItemResult = mapOf(AN_EVENT_ID.value to aRustBatchNotificationResult()),
|
||||||
)
|
)
|
||||||
val sut = createRustNotificationService(
|
val sut = createRustNotificationService(
|
||||||
notificationClient = notificationClient,
|
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.isEncrypted).isTrue()
|
||||||
assertThat(result.content).isEqualTo(
|
assertThat(result.content).isEqualTo(
|
||||||
NotificationContent.MessageLike.RoomMessage(
|
NotificationContent.MessageLike.RoomMessage(
|
||||||
|
|
@ -56,10 +57,8 @@ class RustNotificationServiceTest {
|
||||||
val sut = createRustNotificationService(
|
val sut = createRustNotificationService(
|
||||||
notificationClient = notificationClient,
|
notificationClient = notificationClient,
|
||||||
)
|
)
|
||||||
val result = sut.getNotifications(mapOf(A_ROOM_ID to listOf(AN_EVENT_ID))).getOrThrow()[AN_EVENT_ID]!!
|
val exception = sut.getNotifications(mapOf(A_ROOM_ID to listOf(AN_EVENT_ID))).getOrThrow()[AN_EVENT_ID]!!.exceptionOrNull()
|
||||||
assertThat(result.content).isEqualTo(
|
assertThat(exception).isInstanceOf(NotificationResolverException::class.java)
|
||||||
NotificationContent.MessageLike.UnableToResolve
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|
|
||||||
|
|
@ -13,13 +13,13 @@ import io.element.android.libraries.matrix.api.notification.NotificationData
|
||||||
import io.element.android.libraries.matrix.api.notification.NotificationService
|
import io.element.android.libraries.matrix.api.notification.NotificationService
|
||||||
|
|
||||||
class FakeNotificationService : 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
|
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
|
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.di.AppScope
|
||||||
import io.element.android.libraries.matrix.api.MatrixClientProvider
|
import io.element.android.libraries.matrix.api.MatrixClientProvider
|
||||||
import io.element.android.libraries.matrix.api.core.SessionId
|
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.CallNotifyType
|
||||||
import io.element.android.libraries.matrix.api.notification.NotificationContent
|
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.notification.NotificationData
|
||||||
|
|
@ -57,7 +58,7 @@ class DefaultCallNotificationEventResolver @Inject constructor(
|
||||||
forceNotify: Boolean
|
forceNotify: Boolean
|
||||||
): Result<NotifiableEvent> = runCatchingExceptions {
|
): Result<NotifiableEvent> = runCatchingExceptions {
|
||||||
val content = notificationData.content as? NotificationContent.MessageLike.CallNotify
|
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
|
val previousRingingCallStatus = appForegroundStateService.hasRingingCall.value
|
||||||
// We need the sync service working to get the updated room info
|
// 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) {
|
if (content.type == CallNotifyType.RING) {
|
||||||
appForegroundStateService.updateHasRingingCall(true)
|
appForegroundStateService.updateHasRingingCall(true)
|
||||||
|
|
||||||
val client = clientProvider.getOrRestore(sessionId).getOrNull() ?: throw ResolvingException("Session $sessionId not found")
|
val client = clientProvider.getOrRestore(
|
||||||
val room = client.getRoom(notificationData.roomId) ?: throw ResolvingException("Room ${notificationData.roomId} not found")
|
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
|
// 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
|
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 android.net.Uri
|
||||||
import androidx.core.content.FileProvider
|
import androidx.core.content.FileProvider
|
||||||
import com.squareup.anvil.annotations.ContributesBinding
|
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.extensions.runCatchingExceptions
|
||||||
import io.element.android.libraries.core.log.logger.LoggerTag
|
import io.element.android.libraries.core.log.logger.LoggerTag
|
||||||
import io.element.android.libraries.di.AppScope
|
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.SessionId
|
||||||
import io.element.android.libraries.matrix.api.core.ThreadId
|
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.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.MediaPreviewValue
|
||||||
import io.element.android.libraries.matrix.api.media.getMediaPreviewValue
|
import io.element.android.libraries.matrix.api.media.getMediaPreviewValue
|
||||||
import io.element.android.libraries.matrix.api.notification.NotificationContent
|
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.api.timeline.item.event.VoiceMessageType
|
||||||
import io.element.android.libraries.matrix.ui.messages.toPlainText
|
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.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.ResolvedPushEvent
|
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 timber.log.Timber
|
import timber.log.Timber
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
private val loggerTag = LoggerTag("DefaultNotifiableEventResolver", LoggerTag.NotificationLoggerTag)
|
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.
|
* 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.
|
* It is used as a bridge between the Event Thread and the NotificationDrawerManager.
|
||||||
|
|
@ -65,24 +72,24 @@ interface NotifiableEventResolver {
|
||||||
suspend fun resolveEvents(
|
suspend fun resolveEvents(
|
||||||
sessionId: SessionId,
|
sessionId: SessionId,
|
||||||
notificationEventRequests: List<NotificationEventRequest>
|
notificationEventRequests: List<NotificationEventRequest>
|
||||||
): Result<Map<NotificationEventRequest, Result<ResolvedPushEvent>>>
|
): ResolvePushEventsResult
|
||||||
}
|
}
|
||||||
|
|
||||||
@ContributesBinding(AppScope::class)
|
@ContributesBinding(AppScope::class)
|
||||||
@SingleIn(AppScope::class)
|
@SingleIn(AppScope::class)
|
||||||
class DefaultNotifiableEventResolver @Inject constructor(
|
class DefaultNotifiableEventResolver @Inject constructor(
|
||||||
private val stringProvider: StringProvider,
|
private val stringProvider: StringProvider,
|
||||||
private val clock: SystemClock,
|
|
||||||
private val matrixClientProvider: MatrixClientProvider,
|
private val matrixClientProvider: MatrixClientProvider,
|
||||||
private val notificationMediaRepoFactory: NotificationMediaRepo.Factory,
|
private val notificationMediaRepoFactory: NotificationMediaRepo.Factory,
|
||||||
@ApplicationContext private val context: Context,
|
@ApplicationContext private val context: Context,
|
||||||
private val permalinkParser: PermalinkParser,
|
private val permalinkParser: PermalinkParser,
|
||||||
private val callNotificationEventResolver: CallNotificationEventResolver,
|
private val callNotificationEventResolver: CallNotificationEventResolver,
|
||||||
|
private val fallbackNotificationFactory: FallbackNotificationFactory,
|
||||||
) : NotifiableEventResolver {
|
) : NotifiableEventResolver {
|
||||||
override suspend fun resolveEvents(
|
override suspend fun resolveEvents(
|
||||||
sessionId: SessionId,
|
sessionId: SessionId,
|
||||||
notificationEventRequests: List<NotificationEventRequest>
|
notificationEventRequests: List<NotificationEventRequest>
|
||||||
): Result<Map<NotificationEventRequest, Result<ResolvedPushEvent>>> {
|
): ResolvePushEventsResult {
|
||||||
Timber.d("Queueing notifications: $notificationEventRequests")
|
Timber.d("Queueing notifications: $notificationEventRequests")
|
||||||
val client = matrixClientProvider.getOrRestore(sessionId).getOrElse {
|
val client = matrixClientProvider.getOrRestore(sessionId).getOrElse {
|
||||||
return Result.failure(IllegalStateException("Couldn't get or restore client for session $sessionId"))
|
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 } }
|
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
|
// 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 ->
|
val notificationsResult = client.notificationService().getNotifications(ids)
|
||||||
map.mapValues { (_, notificationData) ->
|
|
||||||
notificationData.asNotifiableEvent(client, sessionId)
|
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(
|
return Result.success(
|
||||||
notificationEventRequests.associate {
|
notificationEventRequests.associate { request ->
|
||||||
val notificationData = notifications.getOrNull()?.get(it.eventId)
|
val notificationDataResult = notificationDataMap[request.eventId]
|
||||||
if (notificationData != null) {
|
if (notificationDataResult == null) {
|
||||||
it to notificationData
|
request to Result.failure(NotificationResolverException.UnknownError("No notification data for ${request.roomId} - ${request.eventId}"))
|
||||||
} else {
|
} else {
|
||||||
// TODO once the SDK can actually return what went wrong, we should return it here instead of this generic error
|
request to notificationDataResult
|
||||||
it to Result.failure(ResolvingException("No notification data for ${it.roomId} - ${it.eventId}"))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
@ -164,7 +179,7 @@ class DefaultNotifiableEventResolver @Inject constructor(
|
||||||
NotificationContent.MessageLike.CallCandidates,
|
NotificationContent.MessageLike.CallCandidates,
|
||||||
NotificationContent.MessageLike.CallHangup -> {
|
NotificationContent.MessageLike.CallHangup -> {
|
||||||
Timber.tag(loggerTag.value).d("Ignoring notification for call ${content.javaClass.simpleName}")
|
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 -> {
|
is NotificationContent.MessageLike.CallInvite -> {
|
||||||
val notifiableMessageEvent = buildNotifiableMessageEvent(
|
val notifiableMessageEvent = buildNotifiableMessageEvent(
|
||||||
|
|
@ -195,7 +210,7 @@ class DefaultNotifiableEventResolver @Inject constructor(
|
||||||
NotificationContent.MessageLike.KeyVerificationReady,
|
NotificationContent.MessageLike.KeyVerificationReady,
|
||||||
NotificationContent.MessageLike.KeyVerificationStart -> {
|
NotificationContent.MessageLike.KeyVerificationStart -> {
|
||||||
Timber.tag(loggerTag.value).d("Ignoring notification for verification ${content.javaClass.simpleName}")
|
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 -> {
|
is NotificationContent.MessageLike.Poll -> {
|
||||||
val notifiableEventMessage = buildNotifiableMessageEvent(
|
val notifiableEventMessage = buildNotifiableMessageEvent(
|
||||||
|
|
@ -217,16 +232,11 @@ class DefaultNotifiableEventResolver @Inject constructor(
|
||||||
}
|
}
|
||||||
is NotificationContent.MessageLike.ReactionContent -> {
|
is NotificationContent.MessageLike.ReactionContent -> {
|
||||||
Timber.tag(loggerTag.value).d("Ignoring notification for reaction")
|
Timber.tag(loggerTag.value).d("Ignoring notification for reaction")
|
||||||
throw ResolvingException("Ignoring notification for reaction")
|
throw NotificationResolverException.EventFilteredOut
|
||||||
}
|
}
|
||||||
NotificationContent.MessageLike.RoomEncrypted -> {
|
NotificationContent.MessageLike.RoomEncrypted -> {
|
||||||
Timber.tag(loggerTag.value).w("Notification with encrypted content -> fallback")
|
Timber.tag(loggerTag.value).w("Notification with encrypted content -> fallback")
|
||||||
val fallbackNotifiableEvent = fallbackNotifiableEvent(userId, roomId, eventId)
|
val fallbackNotifiableEvent = fallbackNotificationFactory.create(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)
|
|
||||||
ResolvedPushEvent.Event(fallbackNotifiableEvent)
|
ResolvedPushEvent.Event(fallbackNotifiableEvent)
|
||||||
}
|
}
|
||||||
is NotificationContent.MessageLike.RoomRedaction -> {
|
is NotificationContent.MessageLike.RoomRedaction -> {
|
||||||
|
|
@ -234,7 +244,7 @@ class DefaultNotifiableEventResolver @Inject constructor(
|
||||||
val redactedEventId = content.redactedEventId
|
val redactedEventId = content.redactedEventId
|
||||||
if (redactedEventId == null) {
|
if (redactedEventId == null) {
|
||||||
Timber.tag(loggerTag.value).d("redactedEventId is null.")
|
Timber.tag(loggerTag.value).d("redactedEventId is null.")
|
||||||
throw ResolvingException("redactedEventId is null")
|
throw NotificationResolverException.UnknownError("redactedEventId is null")
|
||||||
} else {
|
} else {
|
||||||
ResolvedPushEvent.Redaction(
|
ResolvedPushEvent.Redaction(
|
||||||
sessionId = userId,
|
sessionId = userId,
|
||||||
|
|
@ -246,7 +256,7 @@ class DefaultNotifiableEventResolver @Inject constructor(
|
||||||
}
|
}
|
||||||
NotificationContent.MessageLike.Sticker -> {
|
NotificationContent.MessageLike.Sticker -> {
|
||||||
Timber.tag(loggerTag.value).d("Ignoring notification for sticker")
|
Timber.tag(loggerTag.value).d("Ignoring notification for sticker")
|
||||||
throw ResolvingException("Ignoring notification for reaction")
|
throw NotificationResolverException.EventFilteredOut
|
||||||
}
|
}
|
||||||
is NotificationContent.StateEvent.RoomMemberContent,
|
is NotificationContent.StateEvent.RoomMemberContent,
|
||||||
NotificationContent.StateEvent.PolicyRuleRoom,
|
NotificationContent.StateEvent.PolicyRuleRoom,
|
||||||
|
|
@ -270,27 +280,11 @@ class DefaultNotifiableEventResolver @Inject constructor(
|
||||||
NotificationContent.StateEvent.SpaceChild,
|
NotificationContent.StateEvent.SpaceChild,
|
||||||
NotificationContent.StateEvent.SpaceParent -> {
|
NotificationContent.StateEvent.SpaceParent -> {
|
||||||
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}")
|
||||||
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(
|
private fun descriptionFromMessageContent(
|
||||||
content: NotificationContent.MessageLike.RoomMessage,
|
content: NotificationContent.MessageLike.RoomMessage,
|
||||||
senderDisambiguatedDisplayName: String,
|
senderDisambiguatedDisplayName: String,
|
||||||
|
|
|
||||||
|
|
@ -33,6 +33,7 @@ class DefaultOnMissedCallNotificationHandler @Inject constructor(
|
||||||
?.getNotifications(mapOf(roomId to listOf(eventId)))
|
?.getNotifications(mapOf(roomId to listOf(eventId)))
|
||||||
?.getOrNull()
|
?.getOrNull()
|
||||||
?.get(eventId)
|
?.get(eventId)
|
||||||
|
?.getOrNull()
|
||||||
?: return
|
?: return
|
||||||
|
|
||||||
val notifiableEvent = callNotificationEventResolver.resolveEvent(
|
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.SingleIn
|
||||||
import io.element.android.libraries.di.annotations.AppCoroutineScope
|
import io.element.android.libraries.di.annotations.AppCoroutineScope
|
||||||
import io.element.android.libraries.matrix.api.auth.MatrixAuthenticationService
|
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.PushHistoryService
|
||||||
import io.element.android.libraries.push.impl.history.onDiagnosticPush
|
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.onInvalidPushReceived
|
||||||
import io.element.android.libraries.push.impl.history.onSuccess
|
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.onUnableToResolveEvent
|
||||||
import io.element.android.libraries.push.impl.history.onUnableToRetrieveSession
|
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.NotificationEventRequest
|
||||||
import io.element.android.libraries.push.impl.notifications.NotificationResolverQueue
|
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.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.NotifiableEvent
|
||||||
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.notifications.model.ResolvedPushEvent
|
||||||
|
|
@ -63,6 +64,7 @@ class DefaultPushHandler @Inject constructor(
|
||||||
private val resolverQueue: NotificationResolverQueue,
|
private val resolverQueue: NotificationResolverQueue,
|
||||||
@AppCoroutineScope
|
@AppCoroutineScope
|
||||||
private val appCoroutineScope: CoroutineScope,
|
private val appCoroutineScope: CoroutineScope,
|
||||||
|
private val fallbackNotificationFactory: FallbackNotificationFactory,
|
||||||
) : PushHandler {
|
) : PushHandler {
|
||||||
init {
|
init {
|
||||||
processPushEventResults()
|
processPushEventResults()
|
||||||
|
|
@ -88,34 +90,37 @@ class DefaultPushHandler @Inject constructor(
|
||||||
} else {
|
} else {
|
||||||
result.fold(
|
result.fold(
|
||||||
onSuccess = {
|
onSuccess = {
|
||||||
if (it is ResolvedPushEvent.Event && it.notifiableEvent is FallbackNotifiableEvent) {
|
pushHistoryService.onSuccess(
|
||||||
pushHistoryService.onUnableToResolveEvent(
|
providerInfo = request.providerInfo,
|
||||||
providerInfo = request.providerInfo,
|
eventId = request.eventId,
|
||||||
eventId = request.eventId,
|
roomId = request.roomId,
|
||||||
roomId = request.roomId,
|
sessionId = request.sessionId,
|
||||||
sessionId = request.sessionId,
|
comment = "Push handled successfully",
|
||||||
reason = "Showing fallback notification",
|
)
|
||||||
)
|
},
|
||||||
mutableBatteryOptimizationStore.showBatteryOptimizationBanner()
|
onFailure = { exception ->
|
||||||
} else {
|
if (exception is NotificationResolverException.EventFilteredOut) {
|
||||||
pushHistoryService.onSuccess(
|
pushHistoryService.onSuccess(
|
||||||
providerInfo = request.providerInfo,
|
providerInfo = request.providerInfo,
|
||||||
eventId = request.eventId,
|
eventId = request.eventId,
|
||||||
roomId = request.roomId,
|
roomId = request.roomId,
|
||||||
sessionId = request.sessionId,
|
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>()
|
val redactions = mutableListOf<ResolvedPushEvent.Redaction>()
|
||||||
|
|
||||||
@Suppress("LoopWithTooManyJumpStatements")
|
@Suppress("LoopWithTooManyJumpStatements")
|
||||||
for (result in resolvedEvents.values) {
|
for ((request, result) in resolvedEvents) {
|
||||||
val event = result.getOrNull() ?: continue
|
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 userPushStore = userPushStoreFactory.getOrCreate(event.sessionId)
|
||||||
val areNotificationsEnabled = userPushStore.getNotificationEnabledForDevice().first()
|
val areNotificationsEnabled = userPushStore.getNotificationEnabledForDevice().first()
|
||||||
// If notifications are disabled for this session and device, we don't want to show the notification
|
// 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 android.content.Context
|
||||||
import com.google.common.truth.Truth.assertThat
|
import com.google.common.truth.Truth.assertThat
|
||||||
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.exception.NotificationResolverException
|
||||||
import io.element.android.libraries.matrix.api.media.MediaSource
|
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.CallNotifyType
|
||||||
import io.element.android.libraries.matrix.api.notification.NotificationContent
|
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.impl.notifications.model.ResolvedPushEvent
|
||||||
import io.element.android.libraries.push.test.notifications.FakeCallNotificationEventResolver
|
import io.element.android.libraries.push.test.notifications.FakeCallNotificationEventResolver
|
||||||
import io.element.android.services.toolbox.impl.strings.AndroidStringProvider
|
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.A_FAKE_TIMESTAMP
|
||||||
import io.element.android.services.toolbox.test.systemclock.FakeSystemClock
|
import io.element.android.services.toolbox.test.systemclock.FakeSystemClock
|
||||||
import kotlinx.coroutines.test.runTest
|
import kotlinx.coroutines.test.runTest
|
||||||
|
|
@ -71,12 +73,22 @@ class DefaultNotifiableEventResolverTest {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `resolve event failure`() = runTest {
|
fun `resolve fetching failure`() = runTest {
|
||||||
val sut = createDefaultNotifiableEventResolver(
|
val sut = createDefaultNotifiableEventResolver(
|
||||||
notificationResult = Result.failure(AN_EXCEPTION)
|
notificationResult = Result.failure(AN_EXCEPTION)
|
||||||
)
|
)
|
||||||
val request = NotificationEventRequest(A_SESSION_ID, A_ROOM_ID, AN_EVENT_ID, "firebase")
|
val request = NotificationEventRequest(A_SESSION_ID, A_ROOM_ID, AN_EVENT_ID, "firebase")
|
||||||
val result = sut.resolveEvents(A_SESSION_ID, listOf(request))
|
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()
|
assertThat(result.getEvent(request)?.isFailure).isTrue()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -85,12 +97,12 @@ class DefaultNotifiableEventResolverTest {
|
||||||
val sut = createDefaultNotifiableEventResolver(
|
val sut = createDefaultNotifiableEventResolver(
|
||||||
notificationResult = Result.success(
|
notificationResult = Result.success(
|
||||||
mapOf(
|
mapOf(
|
||||||
AN_EVENT_ID to aNotificationData(
|
AN_EVENT_ID to Result.success(aNotificationData(
|
||||||
content = NotificationContent.MessageLike.RoomMessage(
|
content = NotificationContent.MessageLike.RoomMessage(
|
||||||
senderId = A_USER_ID_2,
|
senderId = A_USER_ID_2,
|
||||||
messageType = TextMessageType(body = "Hello world", formatted = null)
|
messageType = TextMessageType(body = "Hello world", formatted = null)
|
||||||
),
|
),
|
||||||
)
|
))
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
@ -108,13 +120,13 @@ class DefaultNotifiableEventResolverTest {
|
||||||
val sut = createDefaultNotifiableEventResolver(
|
val sut = createDefaultNotifiableEventResolver(
|
||||||
notificationResult = Result.success(
|
notificationResult = Result.success(
|
||||||
mapOf(
|
mapOf(
|
||||||
AN_EVENT_ID to aNotificationData(
|
AN_EVENT_ID to Result.success(aNotificationData(
|
||||||
content = NotificationContent.MessageLike.RoomMessage(
|
content = NotificationContent.MessageLike.RoomMessage(
|
||||||
senderId = A_USER_ID_2,
|
senderId = A_USER_ID_2,
|
||||||
messageType = TextMessageType(body = "Hello world", formatted = null)
|
messageType = TextMessageType(body = "Hello world", formatted = null)
|
||||||
),
|
),
|
||||||
hasMention = true,
|
hasMention = true,
|
||||||
)
|
))
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
@ -131,7 +143,7 @@ class DefaultNotifiableEventResolverTest {
|
||||||
val sut = createDefaultNotifiableEventResolver(
|
val sut = createDefaultNotifiableEventResolver(
|
||||||
notificationResult = Result.success(
|
notificationResult = Result.success(
|
||||||
mapOf(
|
mapOf(
|
||||||
AN_EVENT_ID to aNotificationData(
|
AN_EVENT_ID to Result.success(aNotificationData(
|
||||||
content = NotificationContent.MessageLike.RoomMessage(
|
content = NotificationContent.MessageLike.RoomMessage(
|
||||||
senderId = A_USER_ID_2,
|
senderId = A_USER_ID_2,
|
||||||
messageType = TextMessageType(
|
messageType = TextMessageType(
|
||||||
|
|
@ -142,7 +154,7 @@ class DefaultNotifiableEventResolverTest {
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
)
|
))
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
@ -159,7 +171,7 @@ class DefaultNotifiableEventResolverTest {
|
||||||
val sut = createDefaultNotifiableEventResolver(
|
val sut = createDefaultNotifiableEventResolver(
|
||||||
notificationResult = Result.success(
|
notificationResult = Result.success(
|
||||||
mapOf(
|
mapOf(
|
||||||
AN_EVENT_ID to aNotificationData(
|
AN_EVENT_ID to Result.success(aNotificationData(
|
||||||
content = NotificationContent.MessageLike.RoomMessage(
|
content = NotificationContent.MessageLike.RoomMessage(
|
||||||
senderId = A_USER_ID_2,
|
senderId = A_USER_ID_2,
|
||||||
messageType = TextMessageType(
|
messageType = TextMessageType(
|
||||||
|
|
@ -170,7 +182,7 @@ class DefaultNotifiableEventResolverTest {
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
)
|
))
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
@ -187,12 +199,12 @@ class DefaultNotifiableEventResolverTest {
|
||||||
val sut = createDefaultNotifiableEventResolver(
|
val sut = createDefaultNotifiableEventResolver(
|
||||||
notificationResult = Result.success(
|
notificationResult = Result.success(
|
||||||
mapOf(
|
mapOf(
|
||||||
AN_EVENT_ID to aNotificationData(
|
AN_EVENT_ID to Result.success(aNotificationData(
|
||||||
content = NotificationContent.MessageLike.RoomMessage(
|
content = NotificationContent.MessageLike.RoomMessage(
|
||||||
senderId = A_USER_ID_2,
|
senderId = A_USER_ID_2,
|
||||||
messageType = AudioMessageType("Audio", null, null, MediaSource("url"), null)
|
messageType = AudioMessageType("Audio", null, null, MediaSource("url"), null)
|
||||||
),
|
),
|
||||||
)
|
))
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
@ -209,12 +221,12 @@ class DefaultNotifiableEventResolverTest {
|
||||||
val sut = createDefaultNotifiableEventResolver(
|
val sut = createDefaultNotifiableEventResolver(
|
||||||
notificationResult = Result.success(
|
notificationResult = Result.success(
|
||||||
mapOf(
|
mapOf(
|
||||||
AN_EVENT_ID to aNotificationData(
|
AN_EVENT_ID to Result.success(aNotificationData(
|
||||||
content = NotificationContent.MessageLike.RoomMessage(
|
content = NotificationContent.MessageLike.RoomMessage(
|
||||||
senderId = A_USER_ID_2,
|
senderId = A_USER_ID_2,
|
||||||
messageType = VideoMessageType("Video", null, null, MediaSource("url"), null)
|
messageType = VideoMessageType("Video", null, null, MediaSource("url"), null)
|
||||||
),
|
),
|
||||||
)
|
))
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
@ -231,12 +243,12 @@ class DefaultNotifiableEventResolverTest {
|
||||||
val sut = createDefaultNotifiableEventResolver(
|
val sut = createDefaultNotifiableEventResolver(
|
||||||
notificationResult = Result.success(
|
notificationResult = Result.success(
|
||||||
mapOf(
|
mapOf(
|
||||||
AN_EVENT_ID to aNotificationData(
|
AN_EVENT_ID to Result.success(aNotificationData(
|
||||||
content = NotificationContent.MessageLike.RoomMessage(
|
content = NotificationContent.MessageLike.RoomMessage(
|
||||||
senderId = A_USER_ID_2,
|
senderId = A_USER_ID_2,
|
||||||
messageType = VoiceMessageType("Voice", null, null, MediaSource("url"), null, null)
|
messageType = VoiceMessageType("Voice", null, null, MediaSource("url"), null, null)
|
||||||
),
|
),
|
||||||
)
|
))
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
@ -253,12 +265,12 @@ class DefaultNotifiableEventResolverTest {
|
||||||
val sut = createDefaultNotifiableEventResolver(
|
val sut = createDefaultNotifiableEventResolver(
|
||||||
notificationResult = Result.success(
|
notificationResult = Result.success(
|
||||||
mapOf(
|
mapOf(
|
||||||
AN_EVENT_ID to aNotificationData(
|
AN_EVENT_ID to Result.success(aNotificationData(
|
||||||
content = NotificationContent.MessageLike.RoomMessage(
|
content = NotificationContent.MessageLike.RoomMessage(
|
||||||
senderId = A_USER_ID_2,
|
senderId = A_USER_ID_2,
|
||||||
messageType = ImageMessageType("Image", null, null, MediaSource("url"), null),
|
messageType = ImageMessageType("Image", null, null, MediaSource("url"), null),
|
||||||
),
|
),
|
||||||
)
|
))
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
@ -275,12 +287,12 @@ class DefaultNotifiableEventResolverTest {
|
||||||
val sut = createDefaultNotifiableEventResolver(
|
val sut = createDefaultNotifiableEventResolver(
|
||||||
notificationResult = Result.success(
|
notificationResult = Result.success(
|
||||||
mapOf(
|
mapOf(
|
||||||
AN_EVENT_ID to aNotificationData(
|
AN_EVENT_ID to Result.success(aNotificationData(
|
||||||
content = NotificationContent.MessageLike.RoomMessage(
|
content = NotificationContent.MessageLike.RoomMessage(
|
||||||
senderId = A_USER_ID_2,
|
senderId = A_USER_ID_2,
|
||||||
messageType = StickerMessageType("Sticker", null, null, MediaSource("url"), null),
|
messageType = StickerMessageType("Sticker", null, null, MediaSource("url"), null),
|
||||||
),
|
),
|
||||||
)
|
))
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
@ -297,12 +309,12 @@ class DefaultNotifiableEventResolverTest {
|
||||||
val sut = createDefaultNotifiableEventResolver(
|
val sut = createDefaultNotifiableEventResolver(
|
||||||
notificationResult = Result.success(
|
notificationResult = Result.success(
|
||||||
mapOf(
|
mapOf(
|
||||||
AN_EVENT_ID to aNotificationData(
|
AN_EVENT_ID to Result.success(aNotificationData(
|
||||||
content = NotificationContent.MessageLike.RoomMessage(
|
content = NotificationContent.MessageLike.RoomMessage(
|
||||||
senderId = A_USER_ID_2,
|
senderId = A_USER_ID_2,
|
||||||
messageType = FileMessageType("File", null, null, MediaSource("url"), null),
|
messageType = FileMessageType("File", null, null, MediaSource("url"), null),
|
||||||
),
|
),
|
||||||
)
|
))
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
@ -319,12 +331,12 @@ class DefaultNotifiableEventResolverTest {
|
||||||
val sut = createDefaultNotifiableEventResolver(
|
val sut = createDefaultNotifiableEventResolver(
|
||||||
notificationResult = Result.success(
|
notificationResult = Result.success(
|
||||||
mapOf(
|
mapOf(
|
||||||
AN_EVENT_ID to aNotificationData(
|
AN_EVENT_ID to Result.success(aNotificationData(
|
||||||
content = NotificationContent.MessageLike.RoomMessage(
|
content = NotificationContent.MessageLike.RoomMessage(
|
||||||
senderId = A_USER_ID_2,
|
senderId = A_USER_ID_2,
|
||||||
messageType = LocationMessageType("Location", "geo:1,2", null),
|
messageType = LocationMessageType("Location", "geo:1,2", null),
|
||||||
),
|
),
|
||||||
)
|
))
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
@ -341,12 +353,12 @@ class DefaultNotifiableEventResolverTest {
|
||||||
val sut = createDefaultNotifiableEventResolver(
|
val sut = createDefaultNotifiableEventResolver(
|
||||||
notificationResult = Result.success(
|
notificationResult = Result.success(
|
||||||
mapOf(
|
mapOf(
|
||||||
AN_EVENT_ID to aNotificationData(
|
AN_EVENT_ID to Result.success(aNotificationData(
|
||||||
content = NotificationContent.MessageLike.RoomMessage(
|
content = NotificationContent.MessageLike.RoomMessage(
|
||||||
senderId = A_USER_ID_2,
|
senderId = A_USER_ID_2,
|
||||||
messageType = NoticeMessageType("Notice", null),
|
messageType = NoticeMessageType("Notice", null),
|
||||||
),
|
),
|
||||||
)
|
))
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
@ -363,12 +375,12 @@ class DefaultNotifiableEventResolverTest {
|
||||||
val sut = createDefaultNotifiableEventResolver(
|
val sut = createDefaultNotifiableEventResolver(
|
||||||
notificationResult = Result.success(
|
notificationResult = Result.success(
|
||||||
mapOf(
|
mapOf(
|
||||||
AN_EVENT_ID to aNotificationData(
|
AN_EVENT_ID to Result.success(aNotificationData(
|
||||||
content = NotificationContent.MessageLike.RoomMessage(
|
content = NotificationContent.MessageLike.RoomMessage(
|
||||||
senderId = A_USER_ID_2,
|
senderId = A_USER_ID_2,
|
||||||
messageType = EmoteMessageType("is happy", null),
|
messageType = EmoteMessageType("is happy", null),
|
||||||
),
|
),
|
||||||
)
|
))
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
@ -385,12 +397,12 @@ class DefaultNotifiableEventResolverTest {
|
||||||
val sut = createDefaultNotifiableEventResolver(
|
val sut = createDefaultNotifiableEventResolver(
|
||||||
notificationResult = Result.success(
|
notificationResult = Result.success(
|
||||||
mapOf(
|
mapOf(
|
||||||
AN_EVENT_ID to aNotificationData(
|
AN_EVENT_ID to Result.success(aNotificationData(
|
||||||
content = NotificationContent.MessageLike.Poll(
|
content = NotificationContent.MessageLike.Poll(
|
||||||
senderId = A_USER_ID_2,
|
senderId = A_USER_ID_2,
|
||||||
question = "A question"
|
question = "A question"
|
||||||
),
|
),
|
||||||
)
|
))
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
@ -407,13 +419,13 @@ class DefaultNotifiableEventResolverTest {
|
||||||
val sut = createDefaultNotifiableEventResolver(
|
val sut = createDefaultNotifiableEventResolver(
|
||||||
notificationResult = Result.success(
|
notificationResult = Result.success(
|
||||||
mapOf(
|
mapOf(
|
||||||
AN_EVENT_ID to aNotificationData(
|
AN_EVENT_ID to Result.success(aNotificationData(
|
||||||
content = NotificationContent.StateEvent.RoomMemberContent(
|
content = NotificationContent.StateEvent.RoomMemberContent(
|
||||||
userId = A_USER_ID_2,
|
userId = A_USER_ID_2,
|
||||||
membershipState = RoomMembershipState.INVITE
|
membershipState = RoomMembershipState.INVITE
|
||||||
),
|
),
|
||||||
isDirect = false,
|
isDirect = false,
|
||||||
)
|
))
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
@ -427,12 +439,12 @@ class DefaultNotifiableEventResolverTest {
|
||||||
val sut = createDefaultNotifiableEventResolver(
|
val sut = createDefaultNotifiableEventResolver(
|
||||||
notificationResult = Result.success(
|
notificationResult = Result.success(
|
||||||
mapOf(
|
mapOf(
|
||||||
AN_EVENT_ID to aNotificationData(
|
AN_EVENT_ID to Result.success(aNotificationData(
|
||||||
content = NotificationContent.Invite(
|
content = NotificationContent.Invite(
|
||||||
senderId = A_USER_ID_2,
|
senderId = A_USER_ID_2,
|
||||||
),
|
),
|
||||||
isDirect = false,
|
isDirect = false,
|
||||||
)
|
))
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
@ -464,12 +476,12 @@ class DefaultNotifiableEventResolverTest {
|
||||||
val sut = createDefaultNotifiableEventResolver(
|
val sut = createDefaultNotifiableEventResolver(
|
||||||
notificationResult = Result.success(
|
notificationResult = Result.success(
|
||||||
mapOf(
|
mapOf(
|
||||||
AN_EVENT_ID to aNotificationData(
|
AN_EVENT_ID to Result.success(aNotificationData(
|
||||||
content = NotificationContent.Invite(
|
content = NotificationContent.Invite(
|
||||||
senderId = A_USER_ID_2,
|
senderId = A_USER_ID_2,
|
||||||
),
|
),
|
||||||
isDirect = true,
|
isDirect = true,
|
||||||
)
|
))
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
@ -501,13 +513,13 @@ class DefaultNotifiableEventResolverTest {
|
||||||
val sut = createDefaultNotifiableEventResolver(
|
val sut = createDefaultNotifiableEventResolver(
|
||||||
notificationResult = Result.success(
|
notificationResult = Result.success(
|
||||||
mapOf(
|
mapOf(
|
||||||
AN_EVENT_ID to aNotificationData(
|
AN_EVENT_ID to Result.success(aNotificationData(
|
||||||
content = NotificationContent.Invite(
|
content = NotificationContent.Invite(
|
||||||
senderId = A_USER_ID_2,
|
senderId = A_USER_ID_2,
|
||||||
),
|
),
|
||||||
isDirect = true,
|
isDirect = true,
|
||||||
senderDisplayName = null,
|
senderDisplayName = null,
|
||||||
)
|
))
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
@ -539,7 +551,8 @@ class DefaultNotifiableEventResolverTest {
|
||||||
val sut = createDefaultNotifiableEventResolver(
|
val sut = createDefaultNotifiableEventResolver(
|
||||||
notificationResult = Result.success(
|
notificationResult = Result.success(
|
||||||
mapOf(
|
mapOf(
|
||||||
AN_EVENT_ID to aNotificationData(
|
AN_EVENT_ID to Result.success(
|
||||||
|
aNotificationData(
|
||||||
content = NotificationContent.Invite(
|
content = NotificationContent.Invite(
|
||||||
senderId = A_USER_ID_2,
|
senderId = A_USER_ID_2,
|
||||||
),
|
),
|
||||||
|
|
@ -547,6 +560,7 @@ class DefaultNotifiableEventResolverTest {
|
||||||
senderIsNameAmbiguous = true,
|
senderIsNameAmbiguous = true,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
val request = NotificationEventRequest(A_SESSION_ID, A_ROOM_ID, AN_EVENT_ID, "firebase")
|
val request = NotificationEventRequest(A_SESSION_ID, A_ROOM_ID, AN_EVENT_ID, "firebase")
|
||||||
|
|
@ -577,12 +591,12 @@ class DefaultNotifiableEventResolverTest {
|
||||||
val sut = createDefaultNotifiableEventResolver(
|
val sut = createDefaultNotifiableEventResolver(
|
||||||
notificationResult = Result.success(
|
notificationResult = Result.success(
|
||||||
mapOf(
|
mapOf(
|
||||||
AN_EVENT_ID to aNotificationData(
|
AN_EVENT_ID to Result.success(aNotificationData(
|
||||||
content = NotificationContent.StateEvent.RoomMemberContent(
|
content = NotificationContent.StateEvent.RoomMemberContent(
|
||||||
userId = A_USER_ID_2,
|
userId = A_USER_ID_2,
|
||||||
membershipState = RoomMembershipState.JOIN
|
membershipState = RoomMembershipState.JOIN
|
||||||
)
|
)
|
||||||
)
|
))
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
@ -595,7 +609,7 @@ class DefaultNotifiableEventResolverTest {
|
||||||
fun `resolve RoomEncrypted`() = runTest {
|
fun `resolve RoomEncrypted`() = runTest {
|
||||||
val sut = createDefaultNotifiableEventResolver(
|
val sut = createDefaultNotifiableEventResolver(
|
||||||
notificationResult = Result.success(
|
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")
|
val request = NotificationEventRequest(A_SESSION_ID, A_ROOM_ID, AN_EVENT_ID, "firebase")
|
||||||
|
|
@ -620,25 +634,12 @@ class DefaultNotifiableEventResolverTest {
|
||||||
fun `resolve UnableToResolve`() = runTest {
|
fun `resolve UnableToResolve`() = runTest {
|
||||||
val sut = createDefaultNotifiableEventResolver(
|
val sut = createDefaultNotifiableEventResolver(
|
||||||
notificationResult = Result.success(
|
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 request = NotificationEventRequest(A_SESSION_ID, A_ROOM_ID, AN_EVENT_ID, "firebase")
|
||||||
val result = sut.resolveEvents(A_SESSION_ID, listOf(request))
|
val result = sut.resolveEvents(A_SESSION_ID, listOf(request))
|
||||||
val expectedResult = ResolvedPushEvent.Event(
|
assertThat(result.getEvent(request)).isEqualTo(Result.failure<ResolvedPushEvent?>(NotificationResolverException.EventNotFound))
|
||||||
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))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|
@ -646,10 +647,12 @@ class DefaultNotifiableEventResolverTest {
|
||||||
val sut = createDefaultNotifiableEventResolver(
|
val sut = createDefaultNotifiableEventResolver(
|
||||||
notificationResult = Result.success(
|
notificationResult = Result.success(
|
||||||
mapOf(
|
mapOf(
|
||||||
AN_EVENT_ID to aNotificationData(
|
AN_EVENT_ID to Result.success(
|
||||||
|
aNotificationData(
|
||||||
content = NotificationContent.MessageLike.CallInvite(A_USER_ID_2),
|
content = NotificationContent.MessageLike.CallInvite(A_USER_ID_2),
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
val request = NotificationEventRequest(A_SESSION_ID, A_ROOM_ID, AN_EVENT_ID, "firebase")
|
val request = NotificationEventRequest(A_SESSION_ID, A_ROOM_ID, AN_EVENT_ID, "firebase")
|
||||||
|
|
@ -688,12 +691,12 @@ class DefaultNotifiableEventResolverTest {
|
||||||
val sut = createDefaultNotifiableEventResolver(
|
val sut = createDefaultNotifiableEventResolver(
|
||||||
notificationResult = Result.success(
|
notificationResult = Result.success(
|
||||||
mapOf(
|
mapOf(
|
||||||
AN_EVENT_ID to aNotificationData(
|
AN_EVENT_ID to Result.success(aNotificationData(
|
||||||
content = NotificationContent.MessageLike.CallNotify(
|
content = NotificationContent.MessageLike.CallNotify(
|
||||||
A_USER_ID_2,
|
A_USER_ID_2,
|
||||||
CallNotifyType.NOTIFY
|
CallNotifyType.NOTIFY
|
||||||
),
|
),
|
||||||
)
|
))
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
callNotificationEventResolver = callNotificationEventResolver,
|
callNotificationEventResolver = callNotificationEventResolver,
|
||||||
|
|
@ -729,12 +732,12 @@ class DefaultNotifiableEventResolverTest {
|
||||||
val sut = createDefaultNotifiableEventResolver(
|
val sut = createDefaultNotifiableEventResolver(
|
||||||
notificationResult = Result.success(
|
notificationResult = Result.success(
|
||||||
mapOf(
|
mapOf(
|
||||||
AN_EVENT_ID to aNotificationData(
|
AN_EVENT_ID to Result.success(aNotificationData(
|
||||||
content = NotificationContent.MessageLike.RoomRedaction(
|
content = NotificationContent.MessageLike.RoomRedaction(
|
||||||
AN_EVENT_ID_2,
|
AN_EVENT_ID_2,
|
||||||
A_REDACTION_REASON,
|
A_REDACTION_REASON,
|
||||||
)
|
)
|
||||||
)
|
))
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
@ -754,12 +757,12 @@ class DefaultNotifiableEventResolverTest {
|
||||||
val sut = createDefaultNotifiableEventResolver(
|
val sut = createDefaultNotifiableEventResolver(
|
||||||
notificationResult = Result.success(
|
notificationResult = Result.success(
|
||||||
mapOf(
|
mapOf(
|
||||||
AN_EVENT_ID to aNotificationData(
|
AN_EVENT_ID to Result.success(aNotificationData(
|
||||||
content = NotificationContent.MessageLike.RoomRedaction(
|
content = NotificationContent.MessageLike.RoomRedaction(
|
||||||
null,
|
null,
|
||||||
A_REDACTION_REASON,
|
A_REDACTION_REASON,
|
||||||
)
|
)
|
||||||
)
|
))
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
@ -807,7 +810,7 @@ class DefaultNotifiableEventResolverTest {
|
||||||
private fun testNoResults(content: NotificationContent) = runTest {
|
private fun testNoResults(content: NotificationContent) = runTest {
|
||||||
val sut = createDefaultNotifiableEventResolver(
|
val sut = createDefaultNotifiableEventResolver(
|
||||||
notificationResult = Result.success(
|
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")
|
val request = NotificationEventRequest(A_SESSION_ID, A_ROOM_ID, AN_EVENT_ID, "firebase")
|
||||||
|
|
@ -823,7 +826,7 @@ class DefaultNotifiableEventResolverTest {
|
||||||
|
|
||||||
private fun createDefaultNotifiableEventResolver(
|
private fun createDefaultNotifiableEventResolver(
|
||||||
notificationService: FakeNotificationService? = FakeNotificationService(),
|
notificationService: FakeNotificationService? = FakeNotificationService(),
|
||||||
notificationResult: Result<Map<EventId, NotificationData>> = Result.success(emptyMap()),
|
notificationResult: Result<Map<EventId, Result<NotificationData>>> = Result.success(emptyMap()),
|
||||||
callNotificationEventResolver: FakeCallNotificationEventResolver = FakeCallNotificationEventResolver(),
|
callNotificationEventResolver: FakeCallNotificationEventResolver = FakeCallNotificationEventResolver(),
|
||||||
): DefaultNotifiableEventResolver {
|
): DefaultNotifiableEventResolver {
|
||||||
val context = RuntimeEnvironment.getApplication() as Context
|
val context = RuntimeEnvironment.getApplication() as Context
|
||||||
|
|
@ -840,12 +843,15 @@ class DefaultNotifiableEventResolverTest {
|
||||||
}
|
}
|
||||||
return DefaultNotifiableEventResolver(
|
return DefaultNotifiableEventResolver(
|
||||||
stringProvider = AndroidStringProvider(context.resources),
|
stringProvider = AndroidStringProvider(context.resources),
|
||||||
clock = FakeSystemClock(),
|
|
||||||
matrixClientProvider = matrixClientProvider,
|
matrixClientProvider = matrixClientProvider,
|
||||||
notificationMediaRepoFactory = notificationMediaRepoFactory,
|
notificationMediaRepoFactory = notificationMediaRepoFactory,
|
||||||
context = context,
|
context = context,
|
||||||
permalinkParser = FakePermalinkParser(),
|
permalinkParser = FakePermalinkParser(),
|
||||||
callNotificationEventResolver = callNotificationEventResolver,
|
callNotificationEventResolver = callNotificationEventResolver,
|
||||||
|
fallbackNotificationFactory = FallbackNotificationFactory(
|
||||||
|
clock = FakeSystemClock(),
|
||||||
|
stringProvider = FakeStringProvider(defaultResult = "You have new messages.")
|
||||||
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -43,7 +43,7 @@ class DefaultOnMissedCallNotificationHandlerTest {
|
||||||
val matrixClientProvider = FakeMatrixClientProvider(getClient = {
|
val matrixClientProvider = FakeMatrixClientProvider(getClient = {
|
||||||
val notificationService = FakeNotificationService().apply {
|
val notificationService = FakeNotificationService().apply {
|
||||||
givenGetNotificationsResult(
|
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))
|
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.RoomId
|
||||||
import io.element.android.libraries.matrix.api.core.SessionId
|
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.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.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
|
||||||
|
|
@ -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.FakePushHistoryService
|
||||||
import io.element.android.libraries.push.impl.history.PushHistoryService
|
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.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.NotificationEventRequest
|
||||||
import io.element.android.libraries.push.impl.notifications.NotificationResolverQueue
|
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.channels.FakeNotificationChannels
|
||||||
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
|
||||||
|
|
@ -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.FakeUserPushStore
|
||||||
import io.element.android.libraries.pushstore.test.userpushstore.FakeUserPushStoreFactory
|
import io.element.android.libraries.pushstore.test.userpushstore.FakeUserPushStoreFactory
|
||||||
import io.element.android.libraries.pushstore.test.userpushstore.clientsecret.FakePushClientSecret
|
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.any
|
||||||
import io.element.android.tests.testutils.lambda.lambdaError
|
import io.element.android.tests.testutils.lambda.lambdaError
|
||||||
import io.element.android.tests.testutils.lambda.lambdaRecorder
|
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`() {
|
fun `when classical PushData is received, but a failure occurs (session not found), nothing happen`() {
|
||||||
`test notification resolver failure`(
|
`test notification resolver failure`(
|
||||||
notificationResolveResult = { _ ->
|
notificationResolveResult = { _ ->
|
||||||
Result.failure(ResolvingException("Unable to restore session"))
|
Result.failure(NotificationResolverException.UnknownError("Unable to restore session"))
|
||||||
},
|
},
|
||||||
shouldSetOptimizationBatteryBanner = false,
|
shouldSetOptimizationBatteryBanner = false,
|
||||||
)
|
)
|
||||||
|
|
@ -282,7 +285,7 @@ class DefaultPushHandlerTest {
|
||||||
`test notification resolver failure`(
|
`test notification resolver failure`(
|
||||||
notificationResolveResult = { requests: List<NotificationEventRequest> ->
|
notificationResolveResult = { requests: List<NotificationEventRequest> ->
|
||||||
Result.success(
|
Result.success(
|
||||||
requests.associateWith { Result.failure(ResolvingException("Unable to resolve event")) }
|
requests.associateWith { Result.failure(NotificationResolverException.UnknownError("Unable to resolve event")) }
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
shouldSetOptimizationBatteryBanner = true,
|
shouldSetOptimizationBatteryBanner = true,
|
||||||
|
|
@ -336,8 +339,6 @@ class DefaultPushHandlerTest {
|
||||||
notifiableEventResult.assertions()
|
notifiableEventResult.assertions()
|
||||||
.isCalledOnce()
|
.isCalledOnce()
|
||||||
.with(value(A_USER_ID), any())
|
.with(value(A_USER_ID), any())
|
||||||
onNotifiableEventsReceived.assertions()
|
|
||||||
.isNeverCalled()
|
|
||||||
onPushReceivedResult.assertions()
|
onPushReceivedResult.assertions()
|
||||||
.isCalledOnce()
|
.isCalledOnce()
|
||||||
.with(any(), value(AN_EVENT_ID), value(A_ROOM_ID), value(A_USER_ID), value(false), value(true), any())
|
.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,
|
pushHistoryService = pushHistoryService,
|
||||||
resolverQueue = NotificationResolverQueue(notifiableEventResolver = FakeNotifiableEventResolver(notifiableEventsResult), backgroundScope),
|
resolverQueue = NotificationResolverQueue(notifiableEventResolver = FakeNotifiableEventResolver(notifiableEventsResult), backgroundScope),
|
||||||
appCoroutineScope = backgroundScope,
|
appCoroutineScope = backgroundScope,
|
||||||
|
fallbackNotificationFactory = FallbackNotificationFactory(
|
||||||
|
clock = FakeSystemClock(),
|
||||||
|
stringProvider = FakeStringProvider(),
|
||||||
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue