Merge branch 'develop' into feature/fga/join_space

This commit is contained in:
ganfra 2025-09-19 16:35:55 +02:00
commit c4308e9810
446 changed files with 5669 additions and 2617 deletions

View file

@ -14,9 +14,9 @@ import io.element.android.libraries.core.extensions.runCatchingExceptions
import io.element.android.libraries.matrix.api.MatrixClientProvider
import io.element.android.libraries.matrix.api.core.SessionId
import io.element.android.libraries.matrix.api.exception.NotificationResolverException
import io.element.android.libraries.matrix.api.notification.CallNotifyType
import io.element.android.libraries.matrix.api.notification.NotificationContent
import io.element.android.libraries.matrix.api.notification.NotificationData
import io.element.android.libraries.matrix.api.notification.RtcNotificationType
import io.element.android.libraries.matrix.api.timeline.item.event.EventType
import io.element.android.libraries.push.impl.R
import io.element.android.libraries.push.impl.notifications.model.NotifiableEvent
@ -58,13 +58,13 @@ class DefaultCallNotificationEventResolver(
notificationData: NotificationData,
forceNotify: Boolean
): Result<NotifiableEvent> = runCatchingExceptions {
val content = notificationData.content as? NotificationContent.MessageLike.CallNotify
val content = notificationData.content as? NotificationContent.MessageLike.RtcNotification
?: throw NotificationResolverException.UnknownError("content is not a call notify")
val previousRingingCallStatus = appForegroundStateService.hasRingingCall.value
// We need the sync service working to get the updated room info
val isRoomCallActive = runCatchingExceptions {
if (content.type == CallNotifyType.RING) {
if (content.type == RtcNotificationType.RING) {
appForegroundStateService.updateHasRingingCall(true)
val client = clientProvider.getOrRestore(
@ -90,7 +90,7 @@ class DefaultCallNotificationEventResolver(
}.getOrDefault(false)
notificationData.run {
if (content.type == CallNotifyType.RING && isRoomCallActive && !forceNotify) {
if (content.type == RtcNotificationType.RING && isRoomCallActive && !forceNotify) {
NotifiableRingingCallEvent(
sessionId = sessionId,
roomId = roomId,
@ -104,9 +104,10 @@ class DefaultCallNotificationEventResolver(
description = stringProvider.getString(R.string.notification_incoming_call),
senderDisambiguatedDisplayName = getDisambiguatedDisplayName(content.senderId),
roomAvatarUrl = roomAvatarUrl,
callNotifyType = content.type,
rtcNotificationType = content.type,
senderId = content.senderId,
senderAvatarUrl = senderAvatarUrl,
expirationTimestamp = content.expirationTimestampMillis,
)
} else {
Timber.d("Event $eventId is call notify but should not ring: $isRoomCallActive, notify: ${content.type}")
@ -124,7 +125,7 @@ class DefaultCallNotificationEventResolver(
roomIsDm = isDm,
roomAvatarPath = roomAvatarUrl,
senderAvatarPath = senderAvatarUrl,
type = EventType.CALL_NOTIFY,
type = EventType.RTC_NOTIFICATION,
)
}
}

View file

@ -199,7 +199,7 @@ class DefaultNotifiableEventResolver(
)
ResolvedPushEvent.Event(notifiableMessageEvent)
}
is NotificationContent.MessageLike.CallNotify -> {
is NotificationContent.MessageLike.RtcNotification -> {
val notifiableEvent = callNotificationEventResolver.resolveEvent(userId, this).getOrThrow()
ResolvedPushEvent.Event(notifiableEvent)
}

View file

@ -123,7 +123,7 @@ class DefaultNotificationCreator(
val smallIcon = CommonDrawables.ic_notification
val containsMissedCall = events.any { it.type == EventType.CALL_NOTIFY }
val containsMissedCall = events.any { it.type == EventType.RTC_NOTIFICATION }
val channelId = if (containsMissedCall) {
notificationChannels.getChannelForIncomingCall(false)
} else {
@ -213,8 +213,8 @@ class DefaultNotificationCreator(
}
setDeleteIntent(pendingIntentFactory.createDismissRoomPendingIntent(roomInfo.sessionId, roomInfo.roomId))
// If any of the events are of call notify type it means a missed call, set the category to the right value
if (events.any { it.type == EventType.CALL_NOTIFY }) {
// If any of the events are of rtc notification type it means a missed call, set the category to the right value
if (events.any { it.type == EventType.RTC_NOTIFICATION }) {
setCategory(NotificationCompat.CATEGORY_MISSED_CALL)
}
}

View file

@ -11,7 +11,7 @@ import io.element.android.libraries.matrix.api.core.EventId
import io.element.android.libraries.matrix.api.core.RoomId
import io.element.android.libraries.matrix.api.core.SessionId
import io.element.android.libraries.matrix.api.core.UserId
import io.element.android.libraries.matrix.api.notification.CallNotifyType
import io.element.android.libraries.matrix.api.notification.RtcNotificationType
data class NotifiableRingingCallEvent(
override val sessionId: SessionId,
@ -27,6 +27,7 @@ data class NotifiableRingingCallEvent(
val senderDisambiguatedDisplayName: String?,
val senderAvatarUrl: String?,
val roomAvatarUrl: String? = null,
val callNotifyType: CallNotifyType,
val rtcNotificationType: RtcNotificationType,
val timestamp: Long,
val expirationTimestamp: Long,
) : NotifiableEvent

View file

@ -16,7 +16,6 @@ import io.element.android.features.call.api.ElementCallEntryPoint
import io.element.android.libraries.core.log.logger.LoggerTag
import io.element.android.libraries.core.meta.BuildMeta
import io.element.android.libraries.di.annotations.AppCoroutineScope
import io.element.android.libraries.matrix.api.auth.MatrixAuthenticationService
import io.element.android.libraries.matrix.api.exception.NotificationResolverException
import io.element.android.libraries.push.impl.history.PushHistoryService
import io.element.android.libraries.push.impl.history.onDiagnosticPush
@ -58,7 +57,6 @@ class DefaultPushHandler(
private val userPushStoreFactory: UserPushStoreFactory,
private val pushClientSecret: PushClientSecret,
private val buildMeta: BuildMeta,
private val matrixAuthenticationService: MatrixAuthenticationService,
private val diagnosticPushHandler: DiagnosticPushHandler,
private val elementCallEntryPoint: ElementCallEntryPoint,
private val notificationChannels: NotificationChannels,
@ -241,32 +239,15 @@ class DefaultPushHandler(
} else {
Timber.tag(loggerTag.value).d("## handleInternal()")
}
val clientSecret = pushData.clientSecret
// clientSecret should not be null. If this happens, restore default session
var reason = if (clientSecret == null) "No client secret" else ""
val userId = clientSecret?.let {
// Get userId from client secret
pushClientSecret.getUserIdFromSecret(clientSecret).also {
if (it == null) {
reason = "Unable to get userId from client secret"
}
}
}
?: run {
matrixAuthenticationService.getLatestSessionId().also {
if (it == null) {
if (reason.isNotEmpty()) reason += " - "
reason += "Unable to get latest sessionId"
}
}
}
// Get userId from client secret
val userId = pushClientSecret.getUserIdFromSecret(pushData.clientSecret)
if (userId == null) {
Timber.w("Unable to get a session")
Timber.w("Unable to get userId from client secret")
pushHistoryService.onUnableToRetrieveSession(
providerInfo = providerInfo,
eventId = pushData.eventId,
roomId = pushData.roomId,
reason = reason,
reason = "Unable to get userId from client secret",
)
return
}
@ -296,6 +277,7 @@ class DefaultPushHandler(
senderName = notifiableEvent.senderDisambiguatedDisplayName,
avatarUrl = notifiableEvent.roomAvatarUrl,
timestamp = notifiableEvent.timestamp,
expirationTimestamp = notifiableEvent.expirationTimestamp,
notificationChannelId = notificationChannels.getChannelForIncomingCall(ring = true),
textContent = notifiableEvent.description,
)

View file

@ -23,7 +23,7 @@
<item quantity="other">"%d convites"</item>
</plurals>
<string name="notification_invite_body">"Convidou-te para conversar"</string>
<string name="notification_invite_body_with_sender">"%1$s convidou-o para conversar"</string>
<string name="notification_invite_body_with_sender">"%1$s convidou-te para conversar"</string>
<string name="notification_mentioned_you_body">"Mencionou-te: %1$s"</string>
<string name="notification_new_messages">"Mensagens novas"</string>
<plurals name="notification_new_messages_for_room">
@ -34,7 +34,7 @@
<string name="notification_room_action_mark_as_read">"Marcar como lida"</string>
<string name="notification_room_action_quick_reply">"Resposta rápida"</string>
<string name="notification_room_invite_body">"Convidou-te a entrar na sala"</string>
<string name="notification_room_invite_body_with_sender">"%1$s convidou-o a juntar-se à sala"</string>
<string name="notification_room_invite_body_with_sender">"%1$s convidou-te a entrares na sala"</string>
<string name="notification_sender_me">"Eu"</string>
<string name="notification_sender_mention_reply">"%1$s mencionou ou respondeu"</string>
<string name="notification_test_push_notification_content">"Estás a ver a notificação! Clica em mim!"</string>

View file

@ -8,8 +8,8 @@
package io.element.android.libraries.push.impl.notifications
import com.google.common.truth.Truth.assertThat
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.RtcNotificationType
import io.element.android.libraries.matrix.test.AN_EVENT_ID
import io.element.android.libraries.matrix.test.A_ROOM_ID
import io.element.android.libraries.matrix.test.A_ROOM_NAME
@ -61,11 +61,12 @@ class DefaultCallNotificationEventResolverTest {
isUpdated = false,
senderDisambiguatedDisplayName = A_USER_NAME_2,
senderAvatarUrl = null,
callNotifyType = CallNotifyType.RING,
expirationTimestamp = 1567L,
rtcNotificationType = RtcNotificationType.RING,
)
val notificationData = aNotificationData(
content = NotificationContent.MessageLike.CallNotify(A_USER_ID_2, CallNotifyType.RING)
content = NotificationContent.MessageLike.RtcNotification(A_USER_ID_2, RtcNotificationType.RING, 1567)
)
val result = resolver.resolveEvent(A_SESSION_ID, notificationData)
assertThat(result.getOrNull()).isEqualTo(expectedResult)
@ -105,11 +106,11 @@ class DefaultCallNotificationEventResolverTest {
imageUriString = null,
imageMimeType = null,
threadId = null,
type = "m.call.notify",
type = "org.matrix.msc4075.rtc.notification",
)
val notificationData = aNotificationData(
content = NotificationContent.MessageLike.CallNotify(A_USER_ID_2, CallNotifyType.NOTIFY)
content = NotificationContent.MessageLike.RtcNotification(A_USER_ID_2, RtcNotificationType.NOTIFY, 0)
)
val result = resolver.resolveEvent(A_SESSION_ID, notificationData)
assertThat(result.getOrNull()).isEqualTo(expectedResult)
@ -149,11 +150,11 @@ class DefaultCallNotificationEventResolverTest {
imageUriString = null,
imageMimeType = null,
threadId = null,
type = "m.call.notify",
type = "org.matrix.msc4075.rtc.notification",
)
val notificationData = aNotificationData(
content = NotificationContent.MessageLike.CallNotify(A_USER_ID_2, CallNotifyType.RING)
content = NotificationContent.MessageLike.RtcNotification(A_USER_ID_2, RtcNotificationType.RING, 0)
)
val result = resolver.resolveEvent(A_SESSION_ID, notificationData)
assertThat(result.getOrNull()).isEqualTo(expectedResult)

View file

@ -12,9 +12,9 @@ import com.google.common.truth.Truth.assertThat
import io.element.android.libraries.matrix.api.core.EventId
import io.element.android.libraries.matrix.api.exception.NotificationResolverException
import io.element.android.libraries.matrix.api.media.MediaSource
import io.element.android.libraries.matrix.api.notification.CallNotifyType
import io.element.android.libraries.matrix.api.notification.NotificationContent
import io.element.android.libraries.matrix.api.notification.NotificationData
import io.element.android.libraries.matrix.api.notification.RtcNotificationType
import io.element.android.libraries.matrix.api.room.RoomMembershipState
import io.element.android.libraries.matrix.api.timeline.item.event.AudioMessageType
import io.element.android.libraries.matrix.api.timeline.item.event.EmoteMessageType
@ -693,9 +693,10 @@ class DefaultNotifiableEventResolverTest {
notificationResult = Result.success(
mapOf(
AN_EVENT_ID to Result.success(aNotificationData(
content = NotificationContent.MessageLike.CallNotify(
content = NotificationContent.MessageLike.RtcNotification(
A_USER_ID_2,
CallNotifyType.NOTIFY
RtcNotificationType.NOTIFY,
0
),
))
)
@ -719,7 +720,7 @@ class DefaultNotifiableEventResolverTest {
isRedacted = false,
imageUriString = null,
imageMimeType = null,
type = EventType.CALL_NOTIFY,
type = EventType.RTC_NOTIFICATION,
)
)
callNotificationEventResolver.resolveEventLambda = { _, _, _ -> Result.success(expectedResult.notifiableEvent) }

View file

@ -12,7 +12,7 @@ import io.element.android.libraries.matrix.api.core.RoomId
import io.element.android.libraries.matrix.api.core.SessionId
import io.element.android.libraries.matrix.api.core.ThreadId
import io.element.android.libraries.matrix.api.core.UserId
import io.element.android.libraries.matrix.api.notification.CallNotifyType
import io.element.android.libraries.matrix.api.notification.RtcNotificationType
import io.element.android.libraries.matrix.api.timeline.item.event.EventType
import io.element.android.libraries.matrix.test.AN_AVATAR_URL
import io.element.android.libraries.matrix.test.AN_EVENT_ID
@ -119,8 +119,9 @@ fun aNotifiableCallEvent(
senderName: String? = null,
roomAvatarUrl: String? = AN_AVATAR_URL,
senderAvatarUrl: String? = AN_AVATAR_URL,
callNotifyType: CallNotifyType = CallNotifyType.NOTIFY,
rtcNotificationType: RtcNotificationType = RtcNotificationType.NOTIFY,
timestamp: Long = 0L,
expirationTimestamp: Long = 0L,
) = NotifiableRingingCallEvent(
sessionId = sessionId,
eventId = eventId,
@ -129,6 +130,7 @@ fun aNotifiableCallEvent(
editedEventId = null,
description = "description",
timestamp = timestamp,
expirationTimestamp = expirationTimestamp,
canBeReplaced = false,
isRedacted = false,
isUpdated = false,
@ -136,5 +138,5 @@ fun aNotifiableCallEvent(
senderId = senderId,
roomAvatarUrl = roomAvatarUrl,
senderAvatarUrl = senderAvatarUrl,
callNotifyType = callNotifyType,
rtcNotificationType = rtcNotificationType,
)

View file

@ -14,13 +14,12 @@ 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
import io.element.android.libraries.matrix.api.auth.MatrixAuthenticationService
import io.element.android.libraries.matrix.api.core.EventId
import io.element.android.libraries.matrix.api.core.RoomId
import io.element.android.libraries.matrix.api.core.SessionId
import io.element.android.libraries.matrix.api.core.UserId
import io.element.android.libraries.matrix.api.exception.NotificationResolverException
import io.element.android.libraries.matrix.api.notification.CallNotifyType
import io.element.android.libraries.matrix.api.notification.RtcNotificationType
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_2
@ -28,7 +27,6 @@ import io.element.android.libraries.matrix.test.A_ROOM_ID
import io.element.android.libraries.matrix.test.A_SECRET
import io.element.android.libraries.matrix.test.A_SESSION_ID
import io.element.android.libraries.matrix.test.A_USER_ID
import io.element.android.libraries.matrix.test.auth.FakeMatrixAuthenticationService
import io.element.android.libraries.matrix.test.core.aBuildMeta
import io.element.android.libraries.push.impl.history.FakePushHistoryService
import io.element.android.libraries.push.impl.history.PushHistoryService
@ -181,7 +179,7 @@ class DefaultPushHandlerTest {
}
@Test
fun `when PushData is received, but client secret is not known, fallback the latest session`() =
fun `when PushData is received, but client secret is not known, nothing happen`() =
runTest {
val aNotifiableMessageEvent = aNotifiableMessageEvent()
val notifiableEventResult =
@ -207,58 +205,6 @@ class DefaultPushHandlerTest {
pushClientSecret = FakePushClientSecret(
getUserIdFromSecretResult = { null }
),
matrixAuthenticationService = FakeMatrixAuthenticationService().apply {
getLatestSessionIdLambda = { A_USER_ID }
},
incrementPushCounterResult = incrementPushCounterResult,
pushHistoryService = pushHistoryService,
)
defaultPushHandler.handle(aPushData, A_PUSHER_INFO)
advanceTimeBy(300.milliseconds)
incrementPushCounterResult.assertions()
.isCalledOnce()
notifiableEventResult.assertions()
.isCalledOnce()
.with(value(A_USER_ID), any())
onNotifiableEventsReceived.assertions()
.isCalledOnce()
.with(value(listOf(aNotifiableMessageEvent)))
onPushReceivedResult.assertions()
.isCalledOnce()
}
@Test
fun `when PushData is received, but client secret is not known, and there is no latest session, nothing happen`() =
runTest {
val aNotifiableMessageEvent = aNotifiableMessageEvent()
val notifiableEventResult =
lambdaRecorder<SessionId, List<NotificationEventRequest>, Result<Map<NotificationEventRequest, Result<ResolvedPushEvent>>>> { _, _ ->
val request = NotificationEventRequest(A_SESSION_ID, A_ROOM_ID, AN_EVENT_ID, A_PUSHER_INFO)
Result.success(mapOf(request to Result.success(ResolvedPushEvent.Event(aNotifiableMessageEvent))))
}
val onNotifiableEventsReceived = lambdaRecorder<List<NotifiableEvent>, Unit> {}
val incrementPushCounterResult = lambdaRecorder<Unit> {}
val aPushData = PushData(
eventId = AN_EVENT_ID,
roomId = A_ROOM_ID,
unread = 0,
clientSecret = A_SECRET,
)
val onPushReceivedResult = lambdaRecorder<String, EventId?, RoomId?, SessionId?, Boolean, Boolean, String?, Unit> { _, _, _, _, _, _, _ -> }
val pushHistoryService = FakePushHistoryService(
onPushReceivedResult = onPushReceivedResult,
)
val defaultPushHandler = createDefaultPushHandler(
onNotifiableEventsReceived = onNotifiableEventsReceived,
notifiableEventsResult = notifiableEventResult,
pushClientSecret = FakePushClientSecret(
getUserIdFromSecretResult = { null }
),
matrixAuthenticationService = FakeMatrixAuthenticationService().apply {
getLatestSessionIdLambda = { null }
},
incrementPushCounterResult = incrementPushCounterResult,
pushHistoryService = pushHistoryService,
)
@ -388,7 +334,7 @@ class DefaultPushHandlerTest {
mapOf(
request to Result.success(
ResolvedPushEvent.Event(
aNotifiableCallEvent(callNotifyType = CallNotifyType.RING, timestamp = Instant.now().toEpochMilli())
aNotifiableCallEvent(rtcNotificationType = RtcNotificationType.RING, timestamp = Instant.now().toEpochMilli())
)
)
)
@ -440,7 +386,7 @@ class DefaultPushHandlerTest {
onNotifiableEventsReceived = onNotifiableEventsReceived,
notifiableEventsResult = { _, _ ->
val request = NotificationEventRequest(A_SESSION_ID, A_ROOM_ID, AN_EVENT_ID, A_PUSHER_INFO)
Result.success(mapOf(request to Result.success(ResolvedPushEvent.Event(aNotifiableMessageEvent(type = EventType.CALL_NOTIFY)))))
Result.success(mapOf(request to Result.success(ResolvedPushEvent.Event(aNotifiableMessageEvent(type = EventType.RTC_NOTIFICATION)))))
},
incrementPushCounterResult = {},
pushClientSecret = FakePushClientSecret(
@ -655,8 +601,8 @@ class DefaultPushHandlerTest {
var receivedFallbackEvent = false
val onPushReceivedResult =
lambdaRecorder<String, EventId?, RoomId?, SessionId?, Boolean, Boolean, String?, Unit> { _, _, _, _, isResolved, _, comment ->
receivedFallbackEvent = !isResolved && comment == "Unable to resolve event: ${aNotifiableFallbackEvent.cause}"
}
receivedFallbackEvent = !isResolved && comment == "Unable to resolve event: ${aNotifiableFallbackEvent.cause}"
}
val pushHistoryService = FakePushHistoryService(
onPushReceivedResult = onPushReceivedResult,
)
@ -694,7 +640,6 @@ class DefaultPushHandlerTest {
userPushStore: UserPushStore = FakeUserPushStore(),
pushClientSecret: PushClientSecret = FakePushClientSecret(),
buildMeta: BuildMeta = aBuildMeta(),
matrixAuthenticationService: MatrixAuthenticationService = FakeMatrixAuthenticationService(),
diagnosticPushHandler: DiagnosticPushHandler = DiagnosticPushHandler(),
elementCallEntryPoint: FakeElementCallEntryPoint = FakeElementCallEntryPoint(),
notificationChannels: FakeNotificationChannels = FakeNotificationChannels(),
@ -712,7 +657,6 @@ class DefaultPushHandlerTest {
userPushStoreFactory = FakeUserPushStoreFactory { userPushStore },
pushClientSecret = pushClientSecret,
buildMeta = buildMeta,
matrixAuthenticationService = matrixAuthenticationService,
diagnosticPushHandler = diagnosticPushHandler,
elementCallEntryPoint = elementCallEntryPoint,
notificationChannels = notificationChannels,