Merge branch 'develop' into feature/bma/addCommentOnSdkDep

This commit is contained in:
Benoit Marty 2025-09-18 18:10:04 +02:00 committed by GitHub
commit 3422b7d90a
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
43 changed files with 307 additions and 80 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

@ -100,6 +100,7 @@ dependencies {
testImplementation(projects.libraries.push.test) testImplementation(projects.libraries.push.test)
testImplementation(projects.services.analytics.test) testImplementation(projects.services.analytics.test)
testImplementation(projects.services.appnavstate.test) testImplementation(projects.services.appnavstate.test)
testImplementation(projects.services.toolbox.test)
testImplementation(projects.tests.testutils) testImplementation(projects.tests.testutils)
testImplementation(libs.androidx.compose.ui.test.junit) testImplementation(libs.androidx.compose.ui.test.junit)
testReleaseImplementation(libs.androidx.compose.ui.test.manifest) testReleaseImplementation(libs.androidx.compose.ui.test.manifest)

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,6 @@ data class CallNotificationData(
val notificationChannelId: String, val notificationChannelId: String,
val timestamp: Long, val timestamp: Long,
val textContent: String?, val textContent: String?,
// Expiration timestamp in millis since epoch
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

@ -34,6 +34,7 @@ import io.element.android.libraries.push.api.notifications.ForegroundServiceType
import io.element.android.libraries.push.api.notifications.NotificationIdProvider import io.element.android.libraries.push.api.notifications.NotificationIdProvider
import io.element.android.libraries.push.api.notifications.OnMissedCallNotificationHandler import io.element.android.libraries.push.api.notifications.OnMissedCallNotificationHandler
import io.element.android.services.appnavstate.api.AppForegroundStateService import io.element.android.services.appnavstate.api.AppForegroundStateService
import io.element.android.services.toolbox.api.systemclock.SystemClock
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.Job import kotlinx.coroutines.Job
@ -53,7 +54,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.
@ -98,6 +99,7 @@ class DefaultActiveCallManager(
private val defaultCurrentCallService: DefaultCurrentCallService, private val defaultCurrentCallService: DefaultCurrentCallService,
private val appForegroundStateService: AppForegroundStateService, private val appForegroundStateService: AppForegroundStateService,
private val imageLoaderHolder: ImageLoaderHolder, private val imageLoaderHolder: ImageLoaderHolder,
private val systemClock: SystemClock,
) : ActiveCallManager { ) : ActiveCallManager {
private val tag = "DefaultActiveCallManager" private val tag = "DefaultActiveCallManager"
private var timedOutCallJob: Job? = null private var timedOutCallJob: Job? = null
@ -118,8 +120,20 @@ class DefaultActiveCallManager(
override suspend fun registerIncomingCall(notificationData: CallNotificationData) { override suspend fun registerIncomingCall(notificationData: CallNotificationData) {
mutex.withLock { mutex.withLock {
val ringDuration =
min(
notificationData.expirationTimestamp - systemClock.epochMillis(),
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(ms): $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 +152,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 +250,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

@ -59,6 +59,7 @@ class DefaultElementCallEntryPointTest {
senderName = "senderName", senderName = "senderName",
avatarUrl = "avatarUrl", avatarUrl = "avatarUrl",
timestamp = 0, timestamp = 0,
expirationTimestamp = 0,
notificationChannelId = "notificationChannelId", notificationChannelId = "notificationChannelId",
textContent = "textContent", textContent = "textContent",
) )

View file

@ -73,6 +73,7 @@ class RingingCallNotificationCreatorTest {
roomAvatarUrl = "https://example.com/avatar.jpg", roomAvatarUrl = "https://example.com/avatar.jpg",
notificationChannelId = "channelId", notificationChannelId = "channelId",
timestamp = 0L, timestamp = 0L,
expirationTimestamp = 20L,
textContent = "textContent", textContent = "textContent",
) )

View file

@ -39,6 +39,8 @@ import io.element.android.libraries.push.test.notifications.FakeImageLoaderHolde
import io.element.android.libraries.push.test.notifications.FakeOnMissedCallNotificationHandler import io.element.android.libraries.push.test.notifications.FakeOnMissedCallNotificationHandler
import io.element.android.libraries.push.test.notifications.push.FakeNotificationBitmapLoader import io.element.android.libraries.push.test.notifications.push.FakeNotificationBitmapLoader
import io.element.android.services.appnavstate.test.FakeAppForegroundStateService import io.element.android.services.appnavstate.test.FakeAppForegroundStateService
import io.element.android.services.toolbox.test.systemclock.A_FAKE_TIMESTAMP
import io.element.android.services.toolbox.test.systemclock.FakeSystemClock
import io.element.android.tests.testutils.lambda.lambdaRecorder import io.element.android.tests.testutils.lambda.lambdaRecorder
import io.element.android.tests.testutils.lambda.value import io.element.android.tests.testutils.lambda.value
import io.element.android.tests.testutils.plantTestTimber import io.element.android.tests.testutils.plantTestTimber
@ -368,6 +370,83 @@ class DefaultActiveCallManagerTest {
assertThat(manager.activeCall.value).isNotNull() assertThat(manager.activeCall.value).isNotNull()
} }
@OptIn(ExperimentalCoroutinesApi::class)
@Test
fun `IncomingCall - rings no longer than expiration time`() = runTest {
setupShadowPowerManager()
val notificationManagerCompat = mockk<NotificationManagerCompat>(relaxed = true)
val clock = FakeSystemClock()
val manager = createActiveCallManager(notificationManagerCompat = notificationManagerCompat, systemClock = clock)
assertThat(manager.activeWakeLock?.isHeld).isFalse()
assertThat(manager.activeCall.value).isNull()
val eventTimestamp = A_FAKE_TIMESTAMP
// The call should not ring more than 30 seconds after the initial event was sent
val expirationTimestamp = eventTimestamp + 30_000
val callNotificationData = aCallNotificationData(
timestamp = eventTimestamp,
expirationTimestamp = expirationTimestamp,
)
// suppose it took 10s to be notified
clock.epochMillisResult = eventTimestamp + 10_000
manager.registerIncomingCall(callNotificationData)
assertThat(manager.activeCall.value).isEqualTo(
ActiveCall(
callType = CallType.RoomCall(
sessionId = callNotificationData.sessionId,
roomId = callNotificationData.roomId,
),
callState = CallState.Ringing(callNotificationData)
)
)
runCurrent()
assertThat(manager.activeWakeLock?.isHeld).isTrue()
verify { notificationManagerCompat.notify(notificationId, any()) }
// advance by 21s it should have stopped ringing
advanceTimeBy(21_000)
runCurrent()
verify { notificationManagerCompat.cancel(any()) }
}
@OptIn(ExperimentalCoroutinesApi::class)
@Test
fun `IncomingCall - ignore expired ring lifetime`() = runTest {
setupShadowPowerManager()
val notificationManagerCompat = mockk<NotificationManagerCompat>(relaxed = true)
val clock = FakeSystemClock()
val manager = createActiveCallManager(notificationManagerCompat = notificationManagerCompat, systemClock = clock)
assertThat(manager.activeWakeLock?.isHeld).isFalse()
assertThat(manager.activeCall.value).isNull()
val eventTimestamp = A_FAKE_TIMESTAMP
// The call should not ring more than 30 seconds after the initial event was sent
val expirationTimestamp = eventTimestamp + 30_000
val callNotificationData = aCallNotificationData(
timestamp = eventTimestamp,
expirationTimestamp = expirationTimestamp,
)
// suppose it took 35s to be notified
clock.epochMillisResult = eventTimestamp + 35_000
manager.registerIncomingCall(callNotificationData)
assertThat(manager.activeCall.value).isNull()
runCurrent()
assertThat(manager.activeWakeLock?.isHeld).isFalse()
verify(exactly = 0) { notificationManagerCompat.notify(notificationId, any()) }
}
private fun setupShadowPowerManager() { private fun setupShadowPowerManager() {
shadowOf(InstrumentationRegistry.getInstrumentation().targetContext.getSystemService<PowerManager>()).apply { shadowOf(InstrumentationRegistry.getInstrumentation().targetContext.getSystemService<PowerManager>()).apply {
setIsWakeLockLevelSupported(PowerManager.PARTIAL_WAKE_LOCK, true) setIsWakeLockLevelSupported(PowerManager.PARTIAL_WAKE_LOCK, true)
@ -378,6 +457,7 @@ class DefaultActiveCallManagerTest {
matrixClientProvider: FakeMatrixClientProvider = FakeMatrixClientProvider(), matrixClientProvider: FakeMatrixClientProvider = FakeMatrixClientProvider(),
onMissedCallNotificationHandler: FakeOnMissedCallNotificationHandler = FakeOnMissedCallNotificationHandler(), onMissedCallNotificationHandler: FakeOnMissedCallNotificationHandler = FakeOnMissedCallNotificationHandler(),
notificationManagerCompat: NotificationManagerCompat = mockk(relaxed = true), notificationManagerCompat: NotificationManagerCompat = mockk(relaxed = true),
systemClock: FakeSystemClock = FakeSystemClock(),
) = DefaultActiveCallManager( ) = DefaultActiveCallManager(
context = InstrumentationRegistry.getInstrumentation().targetContext, context = InstrumentationRegistry.getInstrumentation().targetContext,
coroutineScope = backgroundScope, coroutineScope = backgroundScope,
@ -393,5 +473,6 @@ class DefaultActiveCallManagerTest {
defaultCurrentCallService = DefaultCurrentCallService(), defaultCurrentCallService = DefaultCurrentCallService(),
appForegroundStateService = FakeAppForegroundStateService(), appForegroundStateService = FakeAppForegroundStateService(),
imageLoaderHolder = FakeImageLoaderHolder(), imageLoaderHolder = FakeImageLoaderHolder(),
systemClock = systemClock,
) )
} }

View file

@ -30,6 +30,7 @@ fun aCallNotificationData(
avatarUrl: String? = AN_AVATAR_URL, avatarUrl: String? = AN_AVATAR_URL,
notificationChannelId: String = "channel_id", notificationChannelId: String = "channel_id",
timestamp: Long = 0L, timestamp: Long = 0L,
expirationTimestamp: Long = 30_000L,
textContent: String? = null, textContent: String? = null,
): CallNotificationData = CallNotificationData( ): CallNotificationData = CallNotificationData(
sessionId = sessionId, sessionId = sessionId,
@ -41,5 +42,6 @@ fun aCallNotificationData(
avatarUrl = avatarUrl, avatarUrl = avatarUrl,
notificationChannelId = notificationChannelId, notificationChannelId = notificationChannelId,
timestamp = timestamp, timestamp = timestamp,
expirationTimestamp = expirationTimestamp,
textContent = textContent, textContent = textContent,
) )

View file

@ -38,6 +38,7 @@ class FakeElementCallEntryPoint(
senderName: String?, senderName: String?,
avatarUrl: String?, avatarUrl: String?,
timestamp: Long, timestamp: Long,
expirationTimestamp: Long,
notificationChannelId: String, notificationChannelId: String,
textContent: String?, textContent: String?,
) { ) {

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

@ -18,8 +18,8 @@ import io.element.android.features.messages.impl.crypto.sendfailure.VerifiedUser
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.fixtures.aMessageEvent import io.element.android.features.messages.impl.fixtures.aMessageEvent
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.event.TimelineItemCallNotifyContent
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.TimelineItemTextContent import io.element.android.features.messages.impl.timeline.model.event.TimelineItemTextContent
import io.element.android.features.messages.impl.timeline.model.event.aTimelineItemImageContent import io.element.android.features.messages.impl.timeline.model.event.aTimelineItemImageContent
import io.element.android.features.messages.impl.timeline.model.event.aTimelineItemPollContent import io.element.android.features.messages.impl.timeline.model.event.aTimelineItemPollContent
@ -1193,7 +1193,7 @@ class ActionListPresenterTest {
val initialState = awaitItem() val initialState = awaitItem()
val messageEvent = aMessageEvent( val messageEvent = aMessageEvent(
isMine = true, isMine = true,
content = TimelineItemCallNotifyContent(), content = TimelineItemRtcNotificationContent(),
) )
initialState.eventSink.invoke( initialState.eventSink.invoke(
ActionListEvents.ComputeForMessage( ActionListEvents.ComputeForMessage(

View file

@ -44,7 +44,7 @@ showkase = "1.0.5"
appyx = "1.7.1" appyx = "1.7.1"
sqldelight = "2.1.0" sqldelight = "2.1.0"
wysiwyg = "2.39.0" wysiwyg = "2.39.0"
telephoto = "0.16.0" telephoto = "0.17.0"
haze = "1.6.10" haze = "1.6.10"
# Dependency analysis # Dependency analysis
@ -166,7 +166,7 @@ test_detekt_test = { module = "io.gitlab.arturbosch.detekt:detekt-test", version
# https://github.com/matrix-org/matrix-rust-components-kotlin/commits/main/sdk/sdk-android/src/main/kotlin/org/matrix/rustcomponents/sdk/matrix_sdk_ffi.kt # https://github.com/matrix-org/matrix-rust-components-kotlin/commits/main/sdk/sdk-android/src/main/kotlin/org/matrix/rustcomponents/sdk/matrix_sdk_ffi.kt
# All new features should not be implemented in the pull request that upgrades the version, developers should # All new features should not be implemented in the pull request that upgrades the version, developers should
# only fix API breaks and may add some TODOs. # only fix API breaks and may add some TODOs.
matrix_sdk = "org.matrix.rustcomponents:sdk-android:25.9.16" matrix_sdk = "org.matrix.rustcomponents:sdk-android:25.9.18"
# Others # Others
coil = { module = "io.coil-kt.coil3:coil", version.ref = "coil" } coil = { module = "io.coil-kt.coil3:coil", version.ref = "coil" }

View file

@ -49,9 +49,10 @@ sealed interface NotificationContent {
val senderId: UserId, val senderId: UserId,
) : MessageLike ) : MessageLike
data class CallNotify( data class RtcNotification(
val senderId: UserId, val senderId: UserId,
val type: CallNotifyType, val type: RtcNotificationType,
val expirationTimestampMillis: Long
) : MessageLike ) : MessageLike
data object CallHangup : MessageLike data object CallHangup : MessageLike
@ -118,7 +119,7 @@ sealed interface NotificationContent {
) : NotificationContent ) : NotificationContent
} }
enum class CallNotifyType { enum class RtcNotificationType {
RING, RING,
NOTIFY NOTIFY
} }

View file

@ -12,7 +12,7 @@ enum class MessageEventType {
CALL_INVITE, CALL_INVITE,
CALL_HANGUP, CALL_HANGUP,
CALL_CANDIDATES, CALL_CANDIDATES,
CALL_NOTIFY, RTC_NOTIFICATION,
KEY_VERIFICATION_READY, KEY_VERIFICATION_READY,
KEY_VERIFICATION_START, KEY_VERIFICATION_START,
KEY_VERIFICATION_CANCEL, KEY_VERIFICATION_CANCEL,

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

@ -10,16 +10,16 @@ package io.element.android.libraries.matrix.impl.notification
import io.element.android.libraries.core.extensions.runCatchingExceptions 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.UserId 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.NotificationContent import io.element.android.libraries.matrix.api.notification.NotificationContent
import io.element.android.libraries.matrix.api.notification.RtcNotificationType
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.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
import org.matrix.rustcomponents.sdk.use import org.matrix.rustcomponents.sdk.use
import org.matrix.rustcomponents.sdk.RtcNotificationType as SdkRtcNotificationType
class TimelineEventToNotificationContentMapper { class TimelineEventToNotificationContentMapper {
fun map(timelineEvent: TimelineEvent): Result<NotificationContent> { fun map(timelineEvent: TimelineEvent): Result<NotificationContent> {
@ -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.RtcNotification(
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 SdkRtcNotificationType.map(): RtcNotificationType = when (this) {
NotifyType.NOTIFY -> CallNotifyType.NOTIFY SdkRtcNotificationType.NOTIFICATION -> RtcNotificationType.NOTIFY
NotifyType.RING -> CallNotifyType.RING SdkRtcNotificationType.RING -> RtcNotificationType.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.RTC_NOTIFICATION -> 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.RTC_NOTIFICATION
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

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

View file

@ -43,4 +43,5 @@ fun aRustSpaceRoom(
childrenCount = childrenCount, childrenCount = childrenCount,
state = state, state = state,
heroes = heroes, heroes = heroes,
via = emptyList()
) )

View file

@ -19,7 +19,7 @@ class MessageEventTypeKtTest {
assertThat(MessageLikeEventType.CALL_INVITE.map()).isEqualTo(MessageEventType.CALL_INVITE) assertThat(MessageLikeEventType.CALL_INVITE.map()).isEqualTo(MessageEventType.CALL_INVITE)
assertThat(MessageLikeEventType.CALL_HANGUP.map()).isEqualTo(MessageEventType.CALL_HANGUP) assertThat(MessageLikeEventType.CALL_HANGUP.map()).isEqualTo(MessageEventType.CALL_HANGUP)
assertThat(MessageLikeEventType.CALL_CANDIDATES.map()).isEqualTo(MessageEventType.CALL_CANDIDATES) assertThat(MessageLikeEventType.CALL_CANDIDATES.map()).isEqualTo(MessageEventType.CALL_CANDIDATES)
assertThat(MessageLikeEventType.CALL_NOTIFY.map()).isEqualTo(MessageEventType.CALL_NOTIFY) assertThat(MessageLikeEventType.RTC_NOTIFICATION.map()).isEqualTo(MessageEventType.RTC_NOTIFICATION)
assertThat(MessageLikeEventType.KEY_VERIFICATION_READY.map()).isEqualTo(MessageEventType.KEY_VERIFICATION_READY) assertThat(MessageLikeEventType.KEY_VERIFICATION_READY.map()).isEqualTo(MessageEventType.KEY_VERIFICATION_READY)
assertThat(MessageLikeEventType.KEY_VERIFICATION_START.map()).isEqualTo(MessageEventType.KEY_VERIFICATION_START) assertThat(MessageLikeEventType.KEY_VERIFICATION_START.map()).isEqualTo(MessageEventType.KEY_VERIFICATION_START)
assertThat(MessageLikeEventType.KEY_VERIFICATION_CANCEL.map()).isEqualTo(MessageEventType.KEY_VERIFICATION_CANCEL) assertThat(MessageLikeEventType.KEY_VERIFICATION_CANCEL.map()).isEqualTo(MessageEventType.KEY_VERIFICATION_CANCEL)
@ -46,7 +46,7 @@ class MessageEventTypeKtTest {
assertThat(MessageEventType.CALL_INVITE.map()).isEqualTo(MessageLikeEventType.CALL_INVITE) assertThat(MessageEventType.CALL_INVITE.map()).isEqualTo(MessageLikeEventType.CALL_INVITE)
assertThat(MessageEventType.CALL_HANGUP.map()).isEqualTo(MessageLikeEventType.CALL_HANGUP) assertThat(MessageEventType.CALL_HANGUP.map()).isEqualTo(MessageLikeEventType.CALL_HANGUP)
assertThat(MessageEventType.CALL_CANDIDATES.map()).isEqualTo(MessageLikeEventType.CALL_CANDIDATES) assertThat(MessageEventType.CALL_CANDIDATES.map()).isEqualTo(MessageLikeEventType.CALL_CANDIDATES)
assertThat(MessageEventType.CALL_NOTIFY.map()).isEqualTo(MessageLikeEventType.CALL_NOTIFY) assertThat(MessageEventType.RTC_NOTIFICATION.map()).isEqualTo(MessageLikeEventType.RTC_NOTIFICATION)
assertThat(MessageEventType.KEY_VERIFICATION_READY.map()).isEqualTo(MessageLikeEventType.KEY_VERIFICATION_READY) assertThat(MessageEventType.KEY_VERIFICATION_READY.map()).isEqualTo(MessageLikeEventType.KEY_VERIFICATION_READY)
assertThat(MessageEventType.KEY_VERIFICATION_START.map()).isEqualTo(MessageLikeEventType.KEY_VERIFICATION_START) assertThat(MessageEventType.KEY_VERIFICATION_START.map()).isEqualTo(MessageLikeEventType.KEY_VERIFICATION_START)
assertThat(MessageEventType.KEY_VERIFICATION_CANCEL.map()).isEqualTo(MessageLikeEventType.KEY_VERIFICATION_CANCEL) assertThat(MessageEventType.KEY_VERIFICATION_CANCEL.map()).isEqualTo(MessageLikeEventType.KEY_VERIFICATION_CANCEL)

View file

@ -10,7 +10,9 @@ package io.element.android.libraries.mediaviewer.impl.gallery
import android.net.Uri import android.net.Uri
import app.cash.turbine.ReceiveTurbine import app.cash.turbine.ReceiveTurbine
import com.google.common.truth.Truth.assertThat import com.google.common.truth.Truth.assertThat
import io.element.android.libraries.architecture.AsyncData
import io.element.android.libraries.designsystem.utils.snackbar.SnackbarDispatcher import io.element.android.libraries.designsystem.utils.snackbar.SnackbarDispatcher
import io.element.android.libraries.designsystem.utils.snackbar.SnackbarMessage
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.room.JoinedRoom import io.element.android.libraries.matrix.api.room.JoinedRoom
import io.element.android.libraries.matrix.api.timeline.Timeline import io.element.android.libraries.matrix.api.timeline.Timeline
@ -29,6 +31,7 @@ import io.element.android.libraries.mediaviewer.impl.details.MediaBottomSheetSta
import io.element.android.libraries.mediaviewer.impl.model.aMediaItemImage import io.element.android.libraries.mediaviewer.impl.model.aMediaItemImage
import io.element.android.libraries.mediaviewer.test.FakeLocalMediaActions import io.element.android.libraries.mediaviewer.test.FakeLocalMediaActions
import io.element.android.libraries.mediaviewer.test.FakeLocalMediaFactory import io.element.android.libraries.mediaviewer.test.FakeLocalMediaFactory
import io.element.android.libraries.ui.strings.CommonStrings
import io.element.android.tests.testutils.WarmUpRule import io.element.android.tests.testutils.WarmUpRule
import io.element.android.tests.testutils.lambda.lambdaRecorder import io.element.android.tests.testutils.lambda.lambdaRecorder
import io.element.android.tests.testutils.lambda.value import io.element.android.tests.testutils.lambda.value
@ -147,8 +150,8 @@ class MediaGalleryPresenterTest {
val presenter = createMediaGalleryPresenter( val presenter = createMediaGalleryPresenter(
room = FakeJoinedRoom( room = FakeJoinedRoom(
baseRoom = FakeBaseRoom( baseRoom = FakeBaseRoom(
sessionId = A_USER_ID, sessionId = A_USER_ID,
initialRoomInfo = aRoomInfo(name = A_ROOM_NAME), initialRoomInfo = aRoomInfo(name = A_ROOM_NAME),
canRedactOtherResult = { Result.success(canDeleteOther) }, canRedactOtherResult = { Result.success(canDeleteOther) },
), ),
createTimelineResult = { Result.success(FakeTimeline()) } createTimelineResult = { Result.success(FakeTimeline()) }
@ -223,23 +226,122 @@ class MediaGalleryPresenterTest {
} }
@Test @Test
fun `present - share item`() = runTest { fun `present - share item - item not found`() = runTest {
val presenter = createMediaGalleryPresenter() val presenter = createMediaGalleryPresenter()
presenter.test { presenter.test {
val initialState = awaitFirstItem() val initialState = awaitFirstItem()
initialState.eventSink(MediaGalleryEvents.Share(AN_EVENT_ID)) initialState.eventSink(MediaGalleryEvents.Share(AN_EVENT_ID))
} }
// TODO Add more test on this part
} }
@Test @Test
fun `present - save on disk`() = runTest { fun `present - share item - item found`() = runTest {
val mediaGalleryDataSource = FakeMediaGalleryDataSource(
startLambda = { },
)
mediaGalleryDataSource.emitGroupedMediaItems(
AsyncData.Success(
aGroupedMediaItems(
imageAndVideoItems = listOf(aMediaItemImage(eventId = AN_EVENT_ID)),
fileItems = emptyList(),
)
)
)
val presenter = createMediaGalleryPresenter(
mediaGalleryDataSource = mediaGalleryDataSource,
)
presenter.test {
val initialState = awaitFirstItem()
initialState.eventSink(MediaGalleryEvents.Share(AN_EVENT_ID))
val finalState = awaitItem()
assertThat(finalState.snackbarMessage).isNull()
}
}
@Test
fun `present - share item - item found - download error`() = runTest {
val mediaGalleryDataSource = FakeMediaGalleryDataSource(
startLambda = { },
)
mediaGalleryDataSource.emitGroupedMediaItems(
AsyncData.Success(
aGroupedMediaItems(
imageAndVideoItems = listOf(aMediaItemImage(eventId = AN_EVENT_ID)),
fileItems = emptyList(),
)
)
)
val presenter = createMediaGalleryPresenter(
mediaGalleryDataSource = mediaGalleryDataSource,
matrixMediaLoader = FakeMatrixMediaLoader().apply { shouldFail = true },
)
presenter.test {
val initialState = awaitFirstItem()
initialState.eventSink(MediaGalleryEvents.Share(AN_EVENT_ID))
skipItems(1)
val finalState = awaitItem()
assertThat(finalState.snackbarMessage).isInstanceOf(SnackbarMessage::class.java)
}
}
@Test
fun `present - save on disk - item not found`() = runTest {
val presenter = createMediaGalleryPresenter() val presenter = createMediaGalleryPresenter()
presenter.test { presenter.test {
val initialState = awaitFirstItem() val initialState = awaitFirstItem()
initialState.eventSink(MediaGalleryEvents.SaveOnDisk(AN_EVENT_ID)) initialState.eventSink(MediaGalleryEvents.SaveOnDisk(AN_EVENT_ID))
} }
// TODO Add more test on this part }
@Test
fun `present - save on disk - item found`() = runTest {
val mediaGalleryDataSource = FakeMediaGalleryDataSource(
startLambda = { },
)
mediaGalleryDataSource.emitGroupedMediaItems(
AsyncData.Success(
aGroupedMediaItems(
imageAndVideoItems = listOf(aMediaItemImage(eventId = AN_EVENT_ID)),
fileItems = emptyList(),
)
)
)
val presenter = createMediaGalleryPresenter(
mediaGalleryDataSource = mediaGalleryDataSource,
)
presenter.test {
val initialState = awaitFirstItem()
initialState.eventSink(MediaGalleryEvents.SaveOnDisk(AN_EVENT_ID))
skipItems(1)
val finalState = awaitItem()
assertThat(finalState.snackbarMessage?.messageResId).isEqualTo(CommonStrings.common_file_saved_on_disk_android)
}
}
@Test
fun `present - save on disk - item found - download error`() = runTest {
val mediaGalleryDataSource = FakeMediaGalleryDataSource(
startLambda = { },
)
mediaGalleryDataSource.emitGroupedMediaItems(
AsyncData.Success(
aGroupedMediaItems(
imageAndVideoItems = listOf(aMediaItemImage(eventId = AN_EVENT_ID)),
fileItems = emptyList(),
)
)
)
val presenter = createMediaGalleryPresenter(
mediaGalleryDataSource = mediaGalleryDataSource,
matrixMediaLoader = FakeMatrixMediaLoader().apply { shouldFail = true },
)
presenter.test {
val initialState = awaitFirstItem()
initialState.eventSink(MediaGalleryEvents.SaveOnDisk(AN_EVENT_ID))
skipItems(1)
val finalState = awaitItem()
assertThat(finalState.snackbarMessage).isInstanceOf(SnackbarMessage::class.java)
}
} }
@Test @Test

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.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.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.NotificationContent
import io.element.android.libraries.matrix.api.notification.NotificationData 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.matrix.api.timeline.item.event.EventType
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.NotifiableEvent import io.element.android.libraries.push.impl.notifications.model.NotifiableEvent
@ -58,13 +58,13 @@ class DefaultCallNotificationEventResolver(
notificationData: NotificationData, notificationData: NotificationData,
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.RtcNotification
?: throw NotificationResolverException.UnknownError("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
val isRoomCallActive = runCatchingExceptions { val isRoomCallActive = runCatchingExceptions {
if (content.type == CallNotifyType.RING) { if (content.type == RtcNotificationType.RING) {
appForegroundStateService.updateHasRingingCall(true) appForegroundStateService.updateHasRingingCall(true)
val client = clientProvider.getOrRestore( val client = clientProvider.getOrRestore(
@ -90,7 +90,7 @@ class DefaultCallNotificationEventResolver(
}.getOrDefault(false) }.getOrDefault(false)
notificationData.run { notificationData.run {
if (content.type == CallNotifyType.RING && isRoomCallActive && !forceNotify) { if (content.type == RtcNotificationType.RING && isRoomCallActive && !forceNotify) {
NotifiableRingingCallEvent( NotifiableRingingCallEvent(
sessionId = sessionId, sessionId = sessionId,
roomId = roomId, roomId = roomId,
@ -104,9 +104,10 @@ class DefaultCallNotificationEventResolver(
description = stringProvider.getString(R.string.notification_incoming_call), description = stringProvider.getString(R.string.notification_incoming_call),
senderDisambiguatedDisplayName = getDisambiguatedDisplayName(content.senderId), senderDisambiguatedDisplayName = getDisambiguatedDisplayName(content.senderId),
roomAvatarUrl = roomAvatarUrl, roomAvatarUrl = roomAvatarUrl,
callNotifyType = content.type, rtcNotificationType = 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

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

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

@ -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.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.notification.CallNotifyType import io.element.android.libraries.matrix.api.notification.RtcNotificationType
data class NotifiableRingingCallEvent( data class NotifiableRingingCallEvent(
override val sessionId: SessionId, override val sessionId: SessionId,
@ -27,6 +27,7 @@ data class NotifiableRingingCallEvent(
val senderDisambiguatedDisplayName: String?, val senderDisambiguatedDisplayName: String?,
val senderAvatarUrl: String?, val senderAvatarUrl: String?,
val roomAvatarUrl: String? = null, val roomAvatarUrl: String? = null,
val callNotifyType: CallNotifyType, val rtcNotificationType: RtcNotificationType,
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,
) )

View file

@ -8,8 +8,8 @@
package io.element.android.libraries.push.impl.notifications package io.element.android.libraries.push.impl.notifications
import com.google.common.truth.Truth.assertThat 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.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.AN_EVENT_ID
import io.element.android.libraries.matrix.test.A_ROOM_ID import io.element.android.libraries.matrix.test.A_ROOM_ID
import io.element.android.libraries.matrix.test.A_ROOM_NAME import io.element.android.libraries.matrix.test.A_ROOM_NAME
@ -61,11 +61,12 @@ class DefaultCallNotificationEventResolverTest {
isUpdated = false, isUpdated = false,
senderDisambiguatedDisplayName = A_USER_NAME_2, senderDisambiguatedDisplayName = A_USER_NAME_2,
senderAvatarUrl = null, senderAvatarUrl = null,
callNotifyType = CallNotifyType.RING, expirationTimestamp = 1567L,
rtcNotificationType = RtcNotificationType.RING,
) )
val notificationData = aNotificationData( 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) val result = resolver.resolveEvent(A_SESSION_ID, notificationData)
assertThat(result.getOrNull()).isEqualTo(expectedResult) assertThat(result.getOrNull()).isEqualTo(expectedResult)
@ -105,11 +106,11 @@ class DefaultCallNotificationEventResolverTest {
imageUriString = null, imageUriString = null,
imageMimeType = null, imageMimeType = null,
threadId = null, threadId = null,
type = "m.call.notify", type = "org.matrix.msc4075.rtc.notification",
) )
val notificationData = aNotificationData( 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) val result = resolver.resolveEvent(A_SESSION_ID, notificationData)
assertThat(result.getOrNull()).isEqualTo(expectedResult) assertThat(result.getOrNull()).isEqualTo(expectedResult)
@ -149,11 +150,11 @@ class DefaultCallNotificationEventResolverTest {
imageUriString = null, imageUriString = null,
imageMimeType = null, imageMimeType = null,
threadId = null, threadId = null,
type = "m.call.notify", type = "org.matrix.msc4075.rtc.notification",
) )
val notificationData = aNotificationData( 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) val result = resolver.resolveEvent(A_SESSION_ID, notificationData)
assertThat(result.getOrNull()).isEqualTo(expectedResult) 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.core.EventId
import io.element.android.libraries.matrix.api.exception.NotificationResolverException 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.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
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.room.RoomMembershipState
import io.element.android.libraries.matrix.api.timeline.item.event.AudioMessageType import io.element.android.libraries.matrix.api.timeline.item.event.AudioMessageType
import io.element.android.libraries.matrix.api.timeline.item.event.EmoteMessageType import io.element.android.libraries.matrix.api.timeline.item.event.EmoteMessageType
@ -693,9 +693,10 @@ class DefaultNotifiableEventResolverTest {
notificationResult = Result.success( notificationResult = Result.success(
mapOf( mapOf(
AN_EVENT_ID to Result.success(aNotificationData( AN_EVENT_ID to Result.success(aNotificationData(
content = NotificationContent.MessageLike.CallNotify( content = NotificationContent.MessageLike.RtcNotification(
A_USER_ID_2, A_USER_ID_2,
CallNotifyType.NOTIFY RtcNotificationType.NOTIFY,
0
), ),
)) ))
) )
@ -719,7 +720,7 @@ class DefaultNotifiableEventResolverTest {
isRedacted = false, isRedacted = false,
imageUriString = null, imageUriString = null,
imageMimeType = null, imageMimeType = null,
type = EventType.CALL_NOTIFY, type = EventType.RTC_NOTIFICATION,
) )
) )
callNotificationEventResolver.resolveEventLambda = { _, _, _ -> Result.success(expectedResult.notifiableEvent) } 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.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.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.api.timeline.item.event.EventType
import io.element.android.libraries.matrix.test.AN_AVATAR_URL import io.element.android.libraries.matrix.test.AN_AVATAR_URL
import io.element.android.libraries.matrix.test.AN_EVENT_ID import io.element.android.libraries.matrix.test.AN_EVENT_ID
@ -119,8 +119,9 @@ fun aNotifiableCallEvent(
senderName: String? = null, senderName: String? = null,
roomAvatarUrl: String? = AN_AVATAR_URL, roomAvatarUrl: String? = AN_AVATAR_URL,
senderAvatarUrl: String? = AN_AVATAR_URL, senderAvatarUrl: String? = AN_AVATAR_URL,
callNotifyType: CallNotifyType = CallNotifyType.NOTIFY, rtcNotificationType: RtcNotificationType = RtcNotificationType.NOTIFY,
timestamp: Long = 0L, timestamp: Long = 0L,
expirationTimestamp: Long = 0L,
) = NotifiableRingingCallEvent( ) = NotifiableRingingCallEvent(
sessionId = sessionId, sessionId = sessionId,
eventId = eventId, eventId = eventId,
@ -129,6 +130,7 @@ fun aNotifiableCallEvent(
editedEventId = null, editedEventId = null,
description = "description", description = "description",
timestamp = timestamp, timestamp = timestamp,
expirationTimestamp = expirationTimestamp,
canBeReplaced = false, canBeReplaced = false,
isRedacted = false, isRedacted = false,
isUpdated = false, isUpdated = false,
@ -136,5 +138,5 @@ fun aNotifiableCallEvent(
senderId = senderId, senderId = senderId,
roomAvatarUrl = roomAvatarUrl, roomAvatarUrl = roomAvatarUrl,
senderAvatarUrl = senderAvatarUrl, senderAvatarUrl = senderAvatarUrl,
callNotifyType = callNotifyType, rtcNotificationType = rtcNotificationType,
) )

View file

@ -20,7 +20,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.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.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.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
import io.element.android.libraries.matrix.test.AN_EVENT_ID_2 import io.element.android.libraries.matrix.test.AN_EVENT_ID_2
@ -388,7 +388,7 @@ class DefaultPushHandlerTest {
mapOf( mapOf(
request to Result.success( request to Result.success(
ResolvedPushEvent.Event( ResolvedPushEvent.Event(
aNotifiableCallEvent(callNotifyType = CallNotifyType.RING, timestamp = Instant.now().toEpochMilli()) aNotifiableCallEvent(rtcNotificationType = RtcNotificationType.RING, timestamp = Instant.now().toEpochMilli())
) )
) )
) )
@ -440,7 +440,7 @@ class DefaultPushHandlerTest {
onNotifiableEventsReceived = onNotifiableEventsReceived, onNotifiableEventsReceived = onNotifiableEventsReceived,
notifiableEventsResult = { _, _ -> notifiableEventsResult = { _, _ ->
val request = NotificationEventRequest(A_SESSION_ID, A_ROOM_ID, AN_EVENT_ID, A_PUSHER_INFO) 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 = {}, incrementPushCounterResult = {},
pushClientSecret = FakePushClientSecret( pushClientSecret = FakePushClientSecret(