Use the new RtcNotification instead of deprecated CallNotify

This commit is contained in:
Valere 2025-09-16 09:00:17 +02:00
parent 0d7d33ca92
commit 508aef98ff
27 changed files with 69 additions and 38 deletions

View file

@ -29,6 +29,7 @@ interface ElementCallEntryPoint {
* @param senderName The name of the sender of the event that started the call. * @param senderName The name of the sender of the event that started the call.
* @param avatarUrl The avatar url of the room or DM. * @param avatarUrl The avatar url of the room or DM.
* @param timestamp The timestamp of the event that started the call. * @param timestamp The timestamp of the event that started the call.
* @param expirationTimestamp The timestamp at which the call should stop ringing.
* @param notificationChannelId The id of the notification channel to use for the call notification. * @param notificationChannelId The id of the notification channel to use for the call notification.
* @param textContent The text content of the notification. If null the default content from the system will be used. * @param textContent The text content of the notification. If null the default content from the system will be used.
*/ */
@ -40,6 +41,7 @@ interface ElementCallEntryPoint {
senderName: String?, senderName: String?,
avatarUrl: String?, avatarUrl: String?,
timestamp: Long, timestamp: Long,
expirationTimestamp: Long,
notificationChannelId: String, notificationChannelId: String,
textContent: String?, textContent: String?,
) )

View file

@ -43,6 +43,7 @@ class DefaultElementCallEntryPoint(
senderName: String?, senderName: String?,
avatarUrl: String?, avatarUrl: String?,
timestamp: Long, timestamp: Long,
expirationTimestamp: Long,
notificationChannelId: String, notificationChannelId: String,
textContent: String?, textContent: String?,
) { ) {
@ -55,6 +56,7 @@ class DefaultElementCallEntryPoint(
senderName = senderName, senderName = senderName,
avatarUrl = avatarUrl, avatarUrl = avatarUrl,
timestamp = timestamp, timestamp = timestamp,
expirationTimestamp = expirationTimestamp,
notificationChannelId = notificationChannelId, notificationChannelId = notificationChannelId,
textContent = textContent, textContent = textContent,
) )

View file

@ -26,4 +26,5 @@ data class CallNotificationData(
val notificationChannelId: String, val notificationChannelId: String,
val timestamp: Long, val timestamp: Long,
val textContent: String?, val textContent: String?,
val expirationTimestamp: Long,
) : Parcelable ) : Parcelable

View file

@ -64,6 +64,7 @@ class RingingCallNotificationCreator(
roomAvatarUrl: String?, roomAvatarUrl: String?,
notificationChannelId: String, notificationChannelId: String,
timestamp: Long, timestamp: Long,
expirationTimestamp: Long,
textContent: String?, textContent: String?,
): Notification? { ): Notification? {
val matrixClient = matrixClientProvider.getOrRestore(sessionId).getOrNull() ?: return null val matrixClient = matrixClientProvider.getOrRestore(sessionId).getOrNull() ?: return null
@ -88,6 +89,7 @@ class RingingCallNotificationCreator(
notificationChannelId = notificationChannelId, notificationChannelId = notificationChannelId,
timestamp = timestamp, timestamp = timestamp,
textContent = textContent, textContent = textContent,
expirationTimestamp = expirationTimestamp,
) )
val declineIntent = PendingIntentCompat.getBroadcast( val declineIntent = PendingIntentCompat.getBroadcast(

View file

@ -176,6 +176,7 @@ internal fun IncomingCallScreenPreview() = ElementPreview {
notificationChannelId = "incoming_call", notificationChannelId = "incoming_call",
timestamp = 0L, timestamp = 0L,
textContent = null, textContent = null,
expirationTimestamp = 1000L,
), ),
onAnswer = {}, onAnswer = {},
onCancel = {}, onCancel = {},

View file

@ -53,7 +53,7 @@ import kotlinx.coroutines.launch
import kotlinx.coroutines.sync.Mutex import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock import kotlinx.coroutines.sync.withLock
import timber.log.Timber import timber.log.Timber
import kotlin.time.Duration.Companion.seconds import kotlin.math.min
/** /**
* Manages the active call state. * Manages the active call state.
@ -118,8 +118,20 @@ class DefaultActiveCallManager(
override suspend fun registerIncomingCall(notificationData: CallNotificationData) { override suspend fun registerIncomingCall(notificationData: CallNotificationData) {
mutex.withLock { mutex.withLock {
val ringDuration =
min(
notificationData.expirationTimestamp - System.currentTimeMillis(),
ElementCallConfig.RINGING_CALL_DURATION_SECONDS * 1000L
)
if (ringDuration < 0) {
// Should already have stopped ringing, ignore.
Timber.tag(tag).d("Received timed-out incoming ringing call for room id: ${notificationData.roomId}, cancel ringing")
return
}
appForegroundStateService.updateHasRingingCall(true) appForegroundStateService.updateHasRingingCall(true)
Timber.tag(tag).d("Received incoming call for room id: ${notificationData.roomId}") Timber.tag(tag).d("Received incoming call for room id: ${notificationData.roomId}, ringDuration: $ringDuration")
if (activeCall.value != null) { if (activeCall.value != null) {
displayMissedCallNotification(notificationData) displayMissedCallNotification(notificationData)
Timber.tag(tag).w("Already have an active call, ignoring incoming call: $notificationData") Timber.tag(tag).w("Already have an active call, ignoring incoming call: $notificationData")
@ -138,14 +150,14 @@ class DefaultActiveCallManager(
showIncomingCallNotification(notificationData) showIncomingCallNotification(notificationData)
// Wait for the ringing call to time out // Wait for the ringing call to time out
delay(ElementCallConfig.RINGING_CALL_DURATION_SECONDS.seconds) delay(timeMillis = ringDuration)
incomingCallTimedOut(displayMissedCallNotification = true) incomingCallTimedOut(displayMissedCallNotification = true)
} }
// Acquire a wake lock to keep the device awake during the incoming call, so we can process the room info data // Acquire a wake lock to keep the device awake during the incoming call, so we can process the room info data
if (activeWakeLock?.isHeld == false) { if (activeWakeLock?.isHeld == false) {
Timber.tag(tag).d("Acquiring partial wakelock") Timber.tag(tag).d("Acquiring partial wakelock")
activeWakeLock.acquire(ElementCallConfig.RINGING_CALL_DURATION_SECONDS * 1000L) activeWakeLock.acquire(ringDuration)
} }
} }
} }
@ -236,6 +248,7 @@ class DefaultActiveCallManager(
notificationChannelId = notificationData.notificationChannelId, notificationChannelId = notificationData.notificationChannelId,
timestamp = notificationData.timestamp, timestamp = notificationData.timestamp,
textContent = notificationData.textContent, textContent = notificationData.textContent,
expirationTimestamp = notificationData.expirationTimestamp,
) ?: return ) ?: return
runCatchingExceptions { runCatchingExceptions {
notificationManagerCompat.notify( notificationManagerCompat.notify(

View file

@ -25,12 +25,12 @@ import io.element.android.features.messages.impl.actionlist.model.TimelineItemAc
import io.element.android.features.messages.impl.crypto.sendfailure.VerifiedUserSendFailure import io.element.android.features.messages.impl.crypto.sendfailure.VerifiedUserSendFailure
import io.element.android.features.messages.impl.crypto.sendfailure.VerifiedUserSendFailureFactory import io.element.android.features.messages.impl.crypto.sendfailure.VerifiedUserSendFailureFactory
import io.element.android.features.messages.impl.timeline.model.TimelineItem import io.element.android.features.messages.impl.timeline.model.TimelineItem
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemCallNotifyContent
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemEventContent import io.element.android.features.messages.impl.timeline.model.event.TimelineItemEventContent
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemEventContentWithAttachment import io.element.android.features.messages.impl.timeline.model.event.TimelineItemEventContentWithAttachment
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemLegacyCallInviteContent import io.element.android.features.messages.impl.timeline.model.event.TimelineItemLegacyCallInviteContent
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemPollContent import io.element.android.features.messages.impl.timeline.model.event.TimelineItemPollContent
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemRedactedContent import io.element.android.features.messages.impl.timeline.model.event.TimelineItemRedactedContent
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemRtcNotificationContent
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemStateContent import io.element.android.features.messages.impl.timeline.model.event.TimelineItemStateContent
import io.element.android.features.messages.impl.timeline.model.event.canBeCopied import io.element.android.features.messages.impl.timeline.model.event.canBeCopied
import io.element.android.features.messages.impl.timeline.model.event.canBeForwarded import io.element.android.features.messages.impl.timeline.model.event.canBeForwarded
@ -242,7 +242,7 @@ class DefaultActionListPresenter(
private fun Iterable<TimelineItemAction>.postFilter(content: TimelineItemEventContent): Iterable<TimelineItemAction> { private fun Iterable<TimelineItemAction>.postFilter(content: TimelineItemEventContent): Iterable<TimelineItemAction> {
return filter { action -> return filter { action ->
when (content) { when (content) {
is TimelineItemCallNotifyContent, is TimelineItemRtcNotificationContent,
is TimelineItemLegacyCallInviteContent, is TimelineItemLegacyCallInviteContent,
is TimelineItemStateContent -> action == TimelineItemAction.ViewSource is TimelineItemStateContent -> action == TimelineItemAction.ViewSource
is TimelineItemRedactedContent -> { is TimelineItemRedactedContent -> {

View file

@ -56,7 +56,6 @@ import io.element.android.features.messages.impl.timeline.a11y.a11yReactionActio
import io.element.android.features.messages.impl.timeline.components.MessageShieldView import io.element.android.features.messages.impl.timeline.components.MessageShieldView
import io.element.android.features.messages.impl.timeline.model.TimelineItem import io.element.android.features.messages.impl.timeline.model.TimelineItem
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemAudioContent import io.element.android.features.messages.impl.timeline.model.event.TimelineItemAudioContent
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemCallNotifyContent
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemEncryptedContent import io.element.android.features.messages.impl.timeline.model.event.TimelineItemEncryptedContent
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemFileContent import io.element.android.features.messages.impl.timeline.model.event.TimelineItemFileContent
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemImageContent import io.element.android.features.messages.impl.timeline.model.event.TimelineItemImageContent
@ -64,6 +63,7 @@ import io.element.android.features.messages.impl.timeline.model.event.TimelineIt
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemLocationContent import io.element.android.features.messages.impl.timeline.model.event.TimelineItemLocationContent
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemPollContent import io.element.android.features.messages.impl.timeline.model.event.TimelineItemPollContent
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemRedactedContent import io.element.android.features.messages.impl.timeline.model.event.TimelineItemRedactedContent
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemRtcNotificationContent
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemStateContent import io.element.android.features.messages.impl.timeline.model.event.TimelineItemStateContent
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemStickerContent import io.element.android.features.messages.impl.timeline.model.event.TimelineItemStickerContent
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemTextBasedContent import io.element.android.features.messages.impl.timeline.model.event.TimelineItemTextBasedContent
@ -306,7 +306,7 @@ private fun MessageSummary(
is TimelineItemLegacyCallInviteContent -> { is TimelineItemLegacyCallInviteContent -> {
content = { ContentForBody(textContent) } content = { ContentForBody(textContent) }
} }
is TimelineItemCallNotifyContent -> { is TimelineItemRtcNotificationContent -> {
content = { ContentForBody(stringResource(CommonStrings.common_call_started)) } content = { ContentForBody(stringResource(CommonStrings.common_call_started)) }
} }
} }

View file

@ -29,7 +29,7 @@ import io.element.android.compound.theme.ElementTheme
import io.element.android.compound.tokens.generated.CompoundIcons import io.element.android.compound.tokens.generated.CompoundIcons
import io.element.android.features.messages.impl.timeline.aTimelineItemEvent import io.element.android.features.messages.impl.timeline.aTimelineItemEvent
import io.element.android.features.messages.impl.timeline.model.TimelineItem import io.element.android.features.messages.impl.timeline.model.TimelineItem
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemCallNotifyContent import io.element.android.features.messages.impl.timeline.model.event.TimelineItemRtcNotificationContent
import io.element.android.features.roomcall.api.RoomCallState import io.element.android.features.roomcall.api.RoomCallState
import io.element.android.features.roomcall.api.RoomCallStateProvider import io.element.android.features.roomcall.api.RoomCallStateProvider
import io.element.android.libraries.designsystem.components.avatar.Avatar import io.element.android.libraries.designsystem.components.avatar.Avatar
@ -119,7 +119,7 @@ internal fun TimelineItemCallNotifyViewPreview() = ElementPreview {
.filter { it !is RoomCallState.Unavailable } .filter { it !is RoomCallState.Unavailable }
.forEach { roomCallState -> .forEach { roomCallState ->
TimelineItemCallNotifyView( TimelineItemCallNotifyView(
event = aTimelineItemEvent(content = TimelineItemCallNotifyContent()), event = aTimelineItemEvent(content = TimelineItemRtcNotificationContent()),
roomCallState = roomCallState, roomCallState = roomCallState,
onLongClick = {}, onLongClick = {},
onJoinCallClick = {}, onJoinCallClick = {},

View file

@ -30,9 +30,9 @@ import io.element.android.features.messages.impl.timeline.TimelineRoomInfo
import io.element.android.features.messages.impl.timeline.components.event.TimelineItemEventContentView import io.element.android.features.messages.impl.timeline.components.event.TimelineItemEventContentView
import io.element.android.features.messages.impl.timeline.components.layout.ContentAvoidingLayoutData import io.element.android.features.messages.impl.timeline.components.layout.ContentAvoidingLayoutData
import io.element.android.features.messages.impl.timeline.model.TimelineItem import io.element.android.features.messages.impl.timeline.model.TimelineItem
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemCallNotifyContent
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemLegacyCallInviteContent import io.element.android.features.messages.impl.timeline.model.event.TimelineItemLegacyCallInviteContent
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemPollContent import io.element.android.features.messages.impl.timeline.model.event.TimelineItemPollContent
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemRtcNotificationContent
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemStateContent import io.element.android.features.messages.impl.timeline.model.event.TimelineItemStateContent
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemVoiceContent import io.element.android.features.messages.impl.timeline.model.event.TimelineItemVoiceContent
import io.element.android.features.messages.impl.timeline.protection.TimelineProtectionEvent import io.element.android.features.messages.impl.timeline.protection.TimelineProtectionEvent
@ -123,7 +123,7 @@ internal fun TimelineItemRow(
eventSink = eventSink, eventSink = eventSink,
) )
} }
is TimelineItemCallNotifyContent -> { is TimelineItemRtcNotificationContent -> {
TimelineItemCallNotifyView( TimelineItemCallNotifyView(
modifier = Modifier.padding(start = 16.dp, end = 16.dp, top = 16.dp), modifier = Modifier.padding(start = 16.dp, end = 16.dp, top = 16.dp),
event = timelineItem, event = timelineItem,

View file

@ -14,7 +14,6 @@ import io.element.android.features.messages.impl.timeline.components.layout.Cont
import io.element.android.features.messages.impl.timeline.di.LocalTimelineItemPresenterFactories import io.element.android.features.messages.impl.timeline.di.LocalTimelineItemPresenterFactories
import io.element.android.features.messages.impl.timeline.di.rememberPresenter import io.element.android.features.messages.impl.timeline.di.rememberPresenter
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemAudioContent import io.element.android.features.messages.impl.timeline.model.event.TimelineItemAudioContent
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemCallNotifyContent
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemEncryptedContent import io.element.android.features.messages.impl.timeline.model.event.TimelineItemEncryptedContent
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemEventContent import io.element.android.features.messages.impl.timeline.model.event.TimelineItemEventContent
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemFileContent import io.element.android.features.messages.impl.timeline.model.event.TimelineItemFileContent
@ -23,6 +22,7 @@ import io.element.android.features.messages.impl.timeline.model.event.TimelineIt
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemLocationContent import io.element.android.features.messages.impl.timeline.model.event.TimelineItemLocationContent
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemPollContent import io.element.android.features.messages.impl.timeline.model.event.TimelineItemPollContent
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemRedactedContent import io.element.android.features.messages.impl.timeline.model.event.TimelineItemRedactedContent
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemRtcNotificationContent
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemStateContent import io.element.android.features.messages.impl.timeline.model.event.TimelineItemStateContent
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemStickerContent import io.element.android.features.messages.impl.timeline.model.event.TimelineItemStickerContent
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemTextBasedContent import io.element.android.features.messages.impl.timeline.model.event.TimelineItemTextBasedContent
@ -133,6 +133,6 @@ fun TimelineItemEventContentView(
modifier = modifier modifier = modifier
) )
} }
is TimelineItemCallNotifyContent -> error("This shouldn't be rendered as the content of a bubble") is TimelineItemRtcNotificationContent -> error("This shouldn't be rendered as the content of a bubble")
} }
} }

View file

@ -8,9 +8,9 @@
package io.element.android.features.messages.impl.timeline.factories.event package io.element.android.features.messages.impl.timeline.factories.event
import dev.zacsweers.metro.Inject import dev.zacsweers.metro.Inject
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemCallNotifyContent
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemEventContent import io.element.android.features.messages.impl.timeline.model.event.TimelineItemEventContent
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemLegacyCallInviteContent import io.element.android.features.messages.impl.timeline.model.event.TimelineItemLegacyCallInviteContent
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemRtcNotificationContent
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemUnknownContent import io.element.android.features.messages.impl.timeline.model.event.TimelineItemUnknownContent
import io.element.android.libraries.matrix.api.timeline.item.event.CallNotifyContent import io.element.android.libraries.matrix.api.timeline.item.event.CallNotifyContent
import io.element.android.libraries.matrix.api.timeline.item.event.EventTimelineItem import io.element.android.libraries.matrix.api.timeline.item.event.EventTimelineItem
@ -61,7 +61,7 @@ class TimelineItemContentFactory(
is StickerContent -> stickerFactory.create(itemContent) is StickerContent -> stickerFactory.create(itemContent)
is PollContent -> pollFactory.create(eventTimelineItem, itemContent) is PollContent -> pollFactory.create(eventTimelineItem, itemContent)
is UnableToDecryptContent -> utdFactory.create(itemContent) is UnableToDecryptContent -> utdFactory.create(itemContent)
is CallNotifyContent -> TimelineItemCallNotifyContent() is CallNotifyContent -> TimelineItemRtcNotificationContent()
is UnknownContent -> TimelineItemUnknownContent is UnknownContent -> TimelineItemUnknownContent
} }
} }

View file

@ -9,7 +9,6 @@ package io.element.android.features.messages.impl.timeline.groups
import io.element.android.features.messages.impl.timeline.model.TimelineItem import io.element.android.features.messages.impl.timeline.model.TimelineItem
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemAudioContent import io.element.android.features.messages.impl.timeline.model.event.TimelineItemAudioContent
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemCallNotifyContent
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemEncryptedContent import io.element.android.features.messages.impl.timeline.model.event.TimelineItemEncryptedContent
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemFileContent import io.element.android.features.messages.impl.timeline.model.event.TimelineItemFileContent
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemImageContent import io.element.android.features.messages.impl.timeline.model.event.TimelineItemImageContent
@ -19,6 +18,7 @@ import io.element.android.features.messages.impl.timeline.model.event.TimelineIt
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemProfileChangeContent import io.element.android.features.messages.impl.timeline.model.event.TimelineItemProfileChangeContent
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemRedactedContent import io.element.android.features.messages.impl.timeline.model.event.TimelineItemRedactedContent
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemRoomMembershipContent import io.element.android.features.messages.impl.timeline.model.event.TimelineItemRoomMembershipContent
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemRtcNotificationContent
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemStateEventContent import io.element.android.features.messages.impl.timeline.model.event.TimelineItemStateEventContent
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemStickerContent import io.element.android.features.messages.impl.timeline.model.event.TimelineItemStickerContent
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemTextBasedContent import io.element.android.features.messages.impl.timeline.model.event.TimelineItemTextBasedContent
@ -60,7 +60,7 @@ internal fun TimelineItem.Event.canBeGrouped(): Boolean {
TimelineItemRedactedContent, TimelineItemRedactedContent,
TimelineItemUnknownContent, TimelineItemUnknownContent,
is TimelineItemLegacyCallInviteContent, is TimelineItemLegacyCallInviteContent,
is TimelineItemCallNotifyContent -> false is TimelineItemRtcNotificationContent -> false
is TimelineItemProfileChangeContent, is TimelineItemProfileChangeContent,
is TimelineItemRoomMembershipContent, is TimelineItemRoomMembershipContent,
is TimelineItemStateEventContent -> true is TimelineItemStateEventContent -> true

View file

@ -81,7 +81,7 @@ fun TimelineItemEventContent.canReact(): Boolean =
is TimelineItemStateContent, is TimelineItemStateContent,
is TimelineItemRedactedContent, is TimelineItemRedactedContent,
is TimelineItemLegacyCallInviteContent, is TimelineItemLegacyCallInviteContent,
is TimelineItemCallNotifyContent, is TimelineItemRtcNotificationContent,
TimelineItemUnknownContent -> false TimelineItemUnknownContent -> false
} }

View file

@ -7,6 +7,6 @@
package io.element.android.features.messages.impl.timeline.model.event package io.element.android.features.messages.impl.timeline.model.event
class TimelineItemCallNotifyContent : TimelineItemEventContent { class TimelineItemRtcNotificationContent : TimelineItemEventContent {
override val type: String = "m.call.notify" override val type: String = "org.matrix.msc4075.rtc.notification"
} }

View file

@ -9,7 +9,6 @@ package io.element.android.features.messages.impl.timeline.protection
import io.element.android.features.messages.impl.timeline.model.TimelineItem import io.element.android.features.messages.impl.timeline.model.TimelineItem
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemAudioContent import io.element.android.features.messages.impl.timeline.model.event.TimelineItemAudioContent
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemCallNotifyContent
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemEmoteContent import io.element.android.features.messages.impl.timeline.model.event.TimelineItemEmoteContent
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemEncryptedContent import io.element.android.features.messages.impl.timeline.model.event.TimelineItemEncryptedContent
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemFileContent import io.element.android.features.messages.impl.timeline.model.event.TimelineItemFileContent
@ -21,6 +20,7 @@ import io.element.android.features.messages.impl.timeline.model.event.TimelineIt
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemProfileChangeContent import io.element.android.features.messages.impl.timeline.model.event.TimelineItemProfileChangeContent
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemRedactedContent import io.element.android.features.messages.impl.timeline.model.event.TimelineItemRedactedContent
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemRoomMembershipContent import io.element.android.features.messages.impl.timeline.model.event.TimelineItemRoomMembershipContent
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemRtcNotificationContent
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemStateEventContent import io.element.android.features.messages.impl.timeline.model.event.TimelineItemStateEventContent
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemStickerContent import io.element.android.features.messages.impl.timeline.model.event.TimelineItemStickerContent
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemTextContent import io.element.android.features.messages.impl.timeline.model.event.TimelineItemTextContent
@ -38,7 +38,7 @@ fun TimelineItem.mustBeProtected(): Boolean {
is TimelineItemVideoContent, is TimelineItemVideoContent,
is TimelineItemStickerContent -> true is TimelineItemStickerContent -> true
is TimelineItemAudioContent, is TimelineItemAudioContent,
is TimelineItemCallNotifyContent, is TimelineItemRtcNotificationContent,
is TimelineItemEncryptedContent, is TimelineItemEncryptedContent,
is TimelineItemFileContent, is TimelineItemFileContent,
TimelineItemLegacyCallInviteContent, TimelineItemLegacyCallInviteContent,

View file

@ -12,7 +12,6 @@ import dev.zacsweers.metro.ContributesBinding
import dev.zacsweers.metro.Inject import dev.zacsweers.metro.Inject
import io.element.android.features.messages.impl.timeline.model.TimelineItem import io.element.android.features.messages.impl.timeline.model.TimelineItem
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemAudioContent import io.element.android.features.messages.impl.timeline.model.event.TimelineItemAudioContent
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemCallNotifyContent
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemEncryptedContent import io.element.android.features.messages.impl.timeline.model.event.TimelineItemEncryptedContent
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemFileContent import io.element.android.features.messages.impl.timeline.model.event.TimelineItemFileContent
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemImageContent import io.element.android.features.messages.impl.timeline.model.event.TimelineItemImageContent
@ -21,6 +20,7 @@ import io.element.android.features.messages.impl.timeline.model.event.TimelineIt
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemPollContent import io.element.android.features.messages.impl.timeline.model.event.TimelineItemPollContent
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemProfileChangeContent import io.element.android.features.messages.impl.timeline.model.event.TimelineItemProfileChangeContent
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemRedactedContent import io.element.android.features.messages.impl.timeline.model.event.TimelineItemRedactedContent
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemRtcNotificationContent
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemStateContent import io.element.android.features.messages.impl.timeline.model.event.TimelineItemStateContent
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemStickerContent import io.element.android.features.messages.impl.timeline.model.event.TimelineItemStickerContent
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemTextBasedContent import io.element.android.features.messages.impl.timeline.model.event.TimelineItemTextBasedContent
@ -54,7 +54,7 @@ class DefaultMessageSummaryFormatter(
is TimelineItemFileContent -> context.getString(CommonStrings.common_file) is TimelineItemFileContent -> context.getString(CommonStrings.common_file)
is TimelineItemAudioContent -> context.getString(CommonStrings.common_audio) is TimelineItemAudioContent -> context.getString(CommonStrings.common_audio)
is TimelineItemLegacyCallInviteContent -> context.getString(CommonStrings.common_unsupported_call) is TimelineItemLegacyCallInviteContent -> context.getString(CommonStrings.common_unsupported_call)
is TimelineItemCallNotifyContent -> context.getString(CommonStrings.common_call_started) is TimelineItemRtcNotificationContent -> context.getString(CommonStrings.common_call_started)
} }
// Truncate the message to a safe length to avoid crashes in Compose // Truncate the message to a safe length to avoid crashes in Compose
.toSafeLength() .toSafeLength()

View file

@ -52,6 +52,7 @@ sealed interface NotificationContent {
data class CallNotify( data class CallNotify(
val senderId: UserId, val senderId: UserId,
val type: CallNotifyType, val type: CallNotifyType,
val expirationTimestampMillis: Long
) : MessageLike ) : MessageLike
data object CallHangup : MessageLike data object CallHangup : MessageLike

View file

@ -15,5 +15,6 @@ object EventType {
// Call Events // Call Events
const val CALL_INVITE = "m.call.invite" const val CALL_INVITE = "m.call.invite"
const val CALL_NOTIFY = "m.call.notify"
const val RTC_NOTIFICATION = "org.matrix.msc4075.rtc.notification"
} }

View file

@ -15,7 +15,7 @@ import io.element.android.libraries.matrix.api.notification.NotificationContent
import io.element.android.libraries.matrix.impl.room.member.RoomMemberMapper import io.element.android.libraries.matrix.impl.room.member.RoomMemberMapper
import io.element.android.libraries.matrix.impl.timeline.item.event.EventMessageMapper import io.element.android.libraries.matrix.impl.timeline.item.event.EventMessageMapper
import org.matrix.rustcomponents.sdk.MessageLikeEventContent import org.matrix.rustcomponents.sdk.MessageLikeEventContent
import org.matrix.rustcomponents.sdk.NotifyType import org.matrix.rustcomponents.sdk.RtcNotificationType
import org.matrix.rustcomponents.sdk.StateEventContent import org.matrix.rustcomponents.sdk.StateEventContent
import org.matrix.rustcomponents.sdk.TimelineEvent import org.matrix.rustcomponents.sdk.TimelineEvent
import org.matrix.rustcomponents.sdk.TimelineEventType import org.matrix.rustcomponents.sdk.TimelineEventType
@ -78,7 +78,11 @@ private fun MessageLikeEventContent.toContent(senderId: UserId): NotificationCon
MessageLikeEventContent.CallCandidates -> NotificationContent.MessageLike.CallCandidates MessageLikeEventContent.CallCandidates -> NotificationContent.MessageLike.CallCandidates
MessageLikeEventContent.CallHangup -> NotificationContent.MessageLike.CallHangup MessageLikeEventContent.CallHangup -> NotificationContent.MessageLike.CallHangup
MessageLikeEventContent.CallInvite -> NotificationContent.MessageLike.CallInvite(senderId) MessageLikeEventContent.CallInvite -> NotificationContent.MessageLike.CallInvite(senderId)
is MessageLikeEventContent.CallNotify -> NotificationContent.MessageLike.CallNotify(senderId, notifyType.map()) is MessageLikeEventContent.RtcNotification -> NotificationContent.MessageLike.CallNotify(
senderId = senderId,
type = notificationType.map(),
expirationTimestampMillis = expirationTs.toLong()
)
MessageLikeEventContent.KeyVerificationAccept -> NotificationContent.MessageLike.KeyVerificationAccept MessageLikeEventContent.KeyVerificationAccept -> NotificationContent.MessageLike.KeyVerificationAccept
MessageLikeEventContent.KeyVerificationCancel -> NotificationContent.MessageLike.KeyVerificationCancel MessageLikeEventContent.KeyVerificationCancel -> NotificationContent.MessageLike.KeyVerificationCancel
MessageLikeEventContent.KeyVerificationDone -> NotificationContent.MessageLike.KeyVerificationDone MessageLikeEventContent.KeyVerificationDone -> NotificationContent.MessageLike.KeyVerificationDone
@ -101,7 +105,7 @@ private fun MessageLikeEventContent.toContent(senderId: UserId): NotificationCon
} }
} }
private fun NotifyType.map(): CallNotifyType = when (this) { private fun RtcNotificationType.map(): CallNotifyType = when (this) {
NotifyType.NOTIFY -> CallNotifyType.NOTIFY RtcNotificationType.NOTIFY -> CallNotifyType.NOTIFY
NotifyType.RING -> CallNotifyType.RING RtcNotificationType.RING -> CallNotifyType.RING
} }

View file

@ -15,7 +15,7 @@ fun MessageEventType.map(): MessageLikeEventType = when (this) {
MessageEventType.CALL_INVITE -> MessageLikeEventType.CALL_INVITE MessageEventType.CALL_INVITE -> MessageLikeEventType.CALL_INVITE
MessageEventType.CALL_HANGUP -> MessageLikeEventType.CALL_HANGUP MessageEventType.CALL_HANGUP -> MessageLikeEventType.CALL_HANGUP
MessageEventType.CALL_CANDIDATES -> MessageLikeEventType.CALL_CANDIDATES MessageEventType.CALL_CANDIDATES -> MessageLikeEventType.CALL_CANDIDATES
MessageEventType.CALL_NOTIFY -> MessageLikeEventType.CALL_NOTIFY MessageEventType.CALL_NOTIFY -> MessageLikeEventType.RTC_NOTIFICATION
MessageEventType.KEY_VERIFICATION_READY -> MessageLikeEventType.KEY_VERIFICATION_READY MessageEventType.KEY_VERIFICATION_READY -> MessageLikeEventType.KEY_VERIFICATION_READY
MessageEventType.KEY_VERIFICATION_START -> MessageLikeEventType.KEY_VERIFICATION_START MessageEventType.KEY_VERIFICATION_START -> MessageLikeEventType.KEY_VERIFICATION_START
MessageEventType.KEY_VERIFICATION_CANCEL -> MessageLikeEventType.KEY_VERIFICATION_CANCEL MessageEventType.KEY_VERIFICATION_CANCEL -> MessageLikeEventType.KEY_VERIFICATION_CANCEL
@ -41,7 +41,7 @@ fun MessageLikeEventType.map(): MessageEventType = when (this) {
MessageLikeEventType.CALL_INVITE -> MessageEventType.CALL_INVITE MessageLikeEventType.CALL_INVITE -> MessageEventType.CALL_INVITE
MessageLikeEventType.CALL_HANGUP -> MessageEventType.CALL_HANGUP MessageLikeEventType.CALL_HANGUP -> MessageEventType.CALL_HANGUP
MessageLikeEventType.CALL_CANDIDATES -> MessageEventType.CALL_CANDIDATES MessageLikeEventType.CALL_CANDIDATES -> MessageEventType.CALL_CANDIDATES
MessageLikeEventType.CALL_NOTIFY -> MessageEventType.CALL_NOTIFY MessageLikeEventType.RTC_NOTIFICATION -> MessageEventType.CALL_NOTIFY
MessageLikeEventType.KEY_VERIFICATION_READY -> MessageEventType.KEY_VERIFICATION_READY MessageLikeEventType.KEY_VERIFICATION_READY -> MessageEventType.KEY_VERIFICATION_READY
MessageLikeEventType.KEY_VERIFICATION_START -> MessageEventType.KEY_VERIFICATION_START MessageLikeEventType.KEY_VERIFICATION_START -> MessageEventType.KEY_VERIFICATION_START
MessageLikeEventType.KEY_VERIFICATION_CANCEL -> MessageEventType.KEY_VERIFICATION_CANCEL MessageLikeEventType.KEY_VERIFICATION_CANCEL -> MessageEventType.KEY_VERIFICATION_CANCEL

View file

@ -437,6 +437,7 @@ class RustTimeline(
key = emoji, key = emoji,
itemId = eventOrTransactionId.toRustEventOrTransactionId(), itemId = eventOrTransactionId.toRustEventOrTransactionId(),
) )
return@runCatchingExceptions Unit
} }
} }

View file

@ -149,7 +149,7 @@ class TimelineEventContentMapper(
) )
} }
is TimelineItemContent.CallInvite -> LegacyCallInviteContent is TimelineItemContent.CallInvite -> LegacyCallInviteContent
is TimelineItemContent.CallNotify -> CallNotifyContent is TimelineItemContent.RtcNotification -> CallNotifyContent
} }
} }
} }

View file

@ -107,6 +107,7 @@ class DefaultCallNotificationEventResolver(
callNotifyType = content.type, callNotifyType = content.type,
senderId = content.senderId, senderId = content.senderId,
senderAvatarUrl = senderAvatarUrl, senderAvatarUrl = senderAvatarUrl,
expirationTimestamp = content.expirationTimestampMillis,
) )
} else { } else {
Timber.d("Event $eventId is call notify but should not ring: $isRoomCallActive, notify: ${content.type}") Timber.d("Event $eventId is call notify but should not ring: $isRoomCallActive, notify: ${content.type}")
@ -124,7 +125,7 @@ class DefaultCallNotificationEventResolver(
roomIsDm = isDm, roomIsDm = isDm,
roomAvatarPath = roomAvatarUrl, roomAvatarPath = roomAvatarUrl,
senderAvatarPath = senderAvatarUrl, senderAvatarPath = senderAvatarUrl,
type = EventType.CALL_NOTIFY, type = EventType.RTC_NOTIFICATION,
) )
} }
} }

View file

@ -123,7 +123,7 @@ class DefaultNotificationCreator(
val smallIcon = CommonDrawables.ic_notification 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) { val channelId = if (containsMissedCall) {
notificationChannels.getChannelForIncomingCall(false) notificationChannels.getChannelForIncomingCall(false)
} else { } else {
@ -213,8 +213,8 @@ class DefaultNotificationCreator(
} }
setDeleteIntent(pendingIntentFactory.createDismissRoomPendingIntent(roomInfo.sessionId, roomInfo.roomId)) 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 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.CALL_NOTIFY }) { if (events.any { it.type == EventType.RTC_NOTIFICATION }) {
setCategory(NotificationCompat.CATEGORY_MISSED_CALL) setCategory(NotificationCompat.CATEGORY_MISSED_CALL)
} }
} }

View file

@ -29,4 +29,5 @@ data class NotifiableRingingCallEvent(
val roomAvatarUrl: String? = null, val roomAvatarUrl: String? = null,
val callNotifyType: CallNotifyType, val callNotifyType: CallNotifyType,
val timestamp: Long, val timestamp: Long,
val expirationTimestamp: Long,
) : NotifiableEvent ) : NotifiableEvent

View file

@ -296,6 +296,7 @@ class DefaultPushHandler(
senderName = notifiableEvent.senderDisambiguatedDisplayName, senderName = notifiableEvent.senderDisambiguatedDisplayName,
avatarUrl = notifiableEvent.roomAvatarUrl, avatarUrl = notifiableEvent.roomAvatarUrl,
timestamp = notifiableEvent.timestamp, timestamp = notifiableEvent.timestamp,
expirationTimestamp = notifiableEvent.expirationTimestamp,
notificationChannelId = notificationChannels.getChannelForIncomingCall(ring = true), notificationChannelId = notificationChannels.getChannelForIncomingCall(ring = true),
textContent = notifiableEvent.description, textContent = notifiableEvent.description,
) )