Notify of ringing call when there's an active call (#3003)
* Add `CallNotificationEventResolver` to be able to force the new ringing notification to be non-ringing given an existing ringing one.
This commit is contained in:
parent
02d6fa7a92
commit
f07ec61ecc
12 changed files with 232 additions and 81 deletions
|
|
@ -89,6 +89,7 @@ class DefaultActiveCallManager @Inject constructor(
|
||||||
|
|
||||||
override fun registerIncomingCall(notificationData: CallNotificationData) {
|
override fun registerIncomingCall(notificationData: CallNotificationData) {
|
||||||
if (activeCall.value != null) {
|
if (activeCall.value != null) {
|
||||||
|
displayMissedCallNotification(notificationData)
|
||||||
Timber.w("Already have an active call, ignoring incoming call: $notificationData")
|
Timber.w("Already have an active call, ignoring incoming call: $notificationData")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
@ -99,7 +100,6 @@ class DefaultActiveCallManager @Inject constructor(
|
||||||
)
|
)
|
||||||
|
|
||||||
timedOutCallJob = coroutineScope.launch {
|
timedOutCallJob = coroutineScope.launch {
|
||||||
registerIncomingCall(notificationData)
|
|
||||||
showIncomingCallNotification(notificationData)
|
showIncomingCallNotification(notificationData)
|
||||||
|
|
||||||
// Wait for the call to end
|
// Wait for the call to end
|
||||||
|
|
@ -115,13 +115,7 @@ class DefaultActiveCallManager @Inject constructor(
|
||||||
|
|
||||||
cancelIncomingCallNotification()
|
cancelIncomingCallNotification()
|
||||||
|
|
||||||
coroutineScope.launch {
|
displayMissedCallNotification(notificationData)
|
||||||
onMissedCallNotificationHandler.addMissedCallNotification(
|
|
||||||
sessionId = previousActiveCall.sessionId,
|
|
||||||
roomId = previousActiveCall.roomId,
|
|
||||||
eventId = notificationData.eventId,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun hungUpCall() {
|
override fun hungUpCall() {
|
||||||
|
|
@ -174,6 +168,16 @@ class DefaultActiveCallManager @Inject constructor(
|
||||||
private fun cancelIncomingCallNotification() {
|
private fun cancelIncomingCallNotification() {
|
||||||
notificationManagerCompat.cancel(NotificationIdProvider.getForegroundServiceNotificationId(ForegroundServiceType.INCOMING_CALL))
|
notificationManagerCompat.cancel(NotificationIdProvider.getForegroundServiceNotificationId(ForegroundServiceType.INCOMING_CALL))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun displayMissedCallNotification(notificationData: CallNotificationData) {
|
||||||
|
coroutineScope.launch {
|
||||||
|
onMissedCallNotificationHandler.addMissedCallNotification(
|
||||||
|
sessionId = notificationData.sessionId,
|
||||||
|
roomId = notificationData.roomId,
|
||||||
|
eventId = notificationData.eventId,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
|
|
@ -27,6 +27,7 @@ import io.element.android.features.call.test.aCallNotificationData
|
||||||
import io.element.android.libraries.matrix.api.core.EventId
|
import io.element.android.libraries.matrix.api.core.EventId
|
||||||
import io.element.android.libraries.matrix.api.core.RoomId
|
import io.element.android.libraries.matrix.api.core.RoomId
|
||||||
import io.element.android.libraries.matrix.api.core.SessionId
|
import io.element.android.libraries.matrix.api.core.SessionId
|
||||||
|
import io.element.android.libraries.matrix.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_ID_2
|
import io.element.android.libraries.matrix.test.A_ROOM_ID_2
|
||||||
import io.element.android.libraries.matrix.test.A_SESSION_ID
|
import io.element.android.libraries.matrix.test.A_SESSION_ID
|
||||||
|
|
@ -39,10 +40,12 @@ 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.tests.testutils.lambda.lambdaRecorder
|
import io.element.android.tests.testutils.lambda.lambdaRecorder
|
||||||
|
import io.element.android.tests.testutils.lambda.value
|
||||||
import io.mockk.mockk
|
import io.mockk.mockk
|
||||||
import io.mockk.verify
|
import io.mockk.verify
|
||||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||||
import kotlinx.coroutines.test.TestScope
|
import kotlinx.coroutines.test.TestScope
|
||||||
|
import kotlinx.coroutines.test.advanceTimeBy
|
||||||
import kotlinx.coroutines.test.runCurrent
|
import kotlinx.coroutines.test.runCurrent
|
||||||
import kotlinx.coroutines.test.runTest
|
import kotlinx.coroutines.test.runTest
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
|
|
@ -77,9 +80,14 @@ class DefaultActiveCallManagerTest {
|
||||||
verify { notificationManagerCompat.notify(notificationId, any()) }
|
verify { notificationManagerCompat.notify(notificationId, any()) }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@OptIn(ExperimentalCoroutinesApi::class)
|
||||||
@Test
|
@Test
|
||||||
fun `registerIncomingCall - when there is an already active call does nothing`() = runTest {
|
fun `registerIncomingCall - when there is an already active call adds missed call notification`() = runTest {
|
||||||
val manager = createActiveCallManager()
|
val addMissedCallNotificationLambda = lambdaRecorder<SessionId, RoomId, EventId, Unit> { _, _, _ -> }
|
||||||
|
val onMissedCallNotificationHandler = FakeOnMissedCallNotificationHandler(addMissedCallNotificationLambda = addMissedCallNotificationLambda)
|
||||||
|
val manager = createActiveCallManager(
|
||||||
|
onMissedCallNotificationHandler = onMissedCallNotificationHandler,
|
||||||
|
)
|
||||||
|
|
||||||
// Register existing call
|
// Register existing call
|
||||||
val callNotificationData = aCallNotificationData()
|
val callNotificationData = aCallNotificationData()
|
||||||
|
|
@ -91,6 +99,12 @@ class DefaultActiveCallManagerTest {
|
||||||
|
|
||||||
assertThat(manager.activeCall.value).isEqualTo(activeCall)
|
assertThat(manager.activeCall.value).isEqualTo(activeCall)
|
||||||
assertThat(manager.activeCall.value?.roomId).isNotEqualTo(A_ROOM_ID_2)
|
assertThat(manager.activeCall.value?.roomId).isNotEqualTo(A_ROOM_ID_2)
|
||||||
|
|
||||||
|
advanceTimeBy(1)
|
||||||
|
|
||||||
|
addMissedCallNotificationLambda.assertions()
|
||||||
|
.isCalledOnce()
|
||||||
|
.with(value(A_SESSION_ID), value(A_ROOM_ID_2), value(AN_EVENT_ID))
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|
@ -119,12 +133,10 @@ class DefaultActiveCallManagerTest {
|
||||||
assertThat(manager.activeCall.value).isNotNull()
|
assertThat(manager.activeCall.value).isNotNull()
|
||||||
|
|
||||||
manager.incomingCallTimedOut()
|
manager.incomingCallTimedOut()
|
||||||
|
advanceTimeBy(1)
|
||||||
|
|
||||||
assertThat(manager.activeCall.value).isNull()
|
assertThat(manager.activeCall.value).isNull()
|
||||||
|
|
||||||
runCurrent()
|
|
||||||
|
|
||||||
addMissedCallNotificationLambda.assertions().isCalledOnce()
|
addMissedCallNotificationLambda.assertions().isCalledOnce()
|
||||||
|
|
||||||
verify { notificationManagerCompat.cancel(notificationId) }
|
verify { notificationManagerCompat.cancel(notificationId) }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -17,9 +17,8 @@
|
||||||
package io.element.android.libraries.matrix.api.notification
|
package io.element.android.libraries.matrix.api.notification
|
||||||
|
|
||||||
import com.google.common.truth.Truth.assertThat
|
import com.google.common.truth.Truth.assertThat
|
||||||
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_USER_ID
|
import io.element.android.libraries.matrix.test.A_USER_ID
|
||||||
|
import io.element.android.libraries.matrix.test.notification.aNotificationData
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
|
|
||||||
class NotificationDataTest {
|
class NotificationDataTest {
|
||||||
|
|
@ -49,25 +48,4 @@ class NotificationDataTest {
|
||||||
)
|
)
|
||||||
assertThat(sut.getDisambiguatedDisplayName(A_USER_ID)).isEqualTo("Alice (@alice:server.org)")
|
assertThat(sut.getDisambiguatedDisplayName(A_USER_ID)).isEqualTo("Alice (@alice:server.org)")
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun aNotificationData(
|
|
||||||
senderDisplayName: String?,
|
|
||||||
senderIsNameAmbiguous: Boolean,
|
|
||||||
): NotificationData {
|
|
||||||
return NotificationData(
|
|
||||||
eventId = AN_EVENT_ID,
|
|
||||||
roomId = A_ROOM_ID,
|
|
||||||
senderAvatarUrl = null,
|
|
||||||
senderDisplayName = senderDisplayName,
|
|
||||||
senderIsNameAmbiguous = senderIsNameAmbiguous,
|
|
||||||
roomAvatarUrl = null,
|
|
||||||
roomDisplayName = null,
|
|
||||||
isDirect = false,
|
|
||||||
isEncrypted = false,
|
|
||||||
isNoisy = false,
|
|
||||||
timestamp = 0L,
|
|
||||||
content = NotificationContent.MessageLike.RoomEncrypted,
|
|
||||||
hasMention = false,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,43 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2024 New Vector Ltd
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.element.android.libraries.matrix.test.notification
|
||||||
|
|
||||||
|
import io.element.android.libraries.matrix.api.notification.NotificationContent
|
||||||
|
import io.element.android.libraries.matrix.api.notification.NotificationData
|
||||||
|
import io.element.android.libraries.matrix.test.AN_EVENT_ID
|
||||||
|
import io.element.android.libraries.matrix.test.A_ROOM_ID
|
||||||
|
|
||||||
|
fun aNotificationData(
|
||||||
|
senderDisplayName: String?,
|
||||||
|
senderIsNameAmbiguous: Boolean,
|
||||||
|
): NotificationData {
|
||||||
|
return NotificationData(
|
||||||
|
eventId = AN_EVENT_ID,
|
||||||
|
roomId = A_ROOM_ID,
|
||||||
|
senderAvatarUrl = null,
|
||||||
|
senderDisplayName = senderDisplayName,
|
||||||
|
senderIsNameAmbiguous = senderIsNameAmbiguous,
|
||||||
|
roomAvatarUrl = null,
|
||||||
|
roomDisplayName = null,
|
||||||
|
isDirect = false,
|
||||||
|
isEncrypted = false,
|
||||||
|
isNoisy = false,
|
||||||
|
timestamp = 0L,
|
||||||
|
content = NotificationContent.MessageLike.RoomEncrypted,
|
||||||
|
hasMention = false,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,90 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2024 New Vector Ltd
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.element.android.libraries.push.impl.notifications
|
||||||
|
|
||||||
|
import com.squareup.anvil.annotations.ContributesBinding
|
||||||
|
import io.element.android.libraries.di.AppScope
|
||||||
|
import io.element.android.libraries.matrix.api.core.SessionId
|
||||||
|
import io.element.android.libraries.matrix.api.notification.NotificationContent
|
||||||
|
import io.element.android.libraries.matrix.api.notification.NotificationData
|
||||||
|
import io.element.android.libraries.matrix.api.timeline.item.event.EventType
|
||||||
|
import io.element.android.libraries.push.impl.R
|
||||||
|
import io.element.android.libraries.push.impl.notifications.model.NotifiableEvent
|
||||||
|
import io.element.android.libraries.push.impl.notifications.model.NotifiableRingingCallEvent
|
||||||
|
import io.element.android.services.toolbox.api.strings.StringProvider
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Helper to resolve a valid [NotifiableEvent] from a [NotificationData].
|
||||||
|
*/
|
||||||
|
interface CallNotificationEventResolver {
|
||||||
|
/**
|
||||||
|
* Resolve a call notification event from a notification data depending on whether it should be a ringing one or not.
|
||||||
|
* @param sessionId the current session id
|
||||||
|
* @param notificationData the notification data
|
||||||
|
* @param forceNotify `true` to force the notification to be non-ringing, `false` to use the default behaviour. Default is `false`.
|
||||||
|
* @return a [NotifiableEvent] if the notification data is a call notification, null otherwise
|
||||||
|
*/
|
||||||
|
fun resolveEvent(sessionId: SessionId, notificationData: NotificationData, forceNotify: Boolean = false): NotifiableEvent?
|
||||||
|
}
|
||||||
|
|
||||||
|
@ContributesBinding(AppScope::class)
|
||||||
|
class DefaultCallNotificationEventResolver @Inject constructor(
|
||||||
|
private val stringProvider: StringProvider,
|
||||||
|
) : CallNotificationEventResolver {
|
||||||
|
override fun resolveEvent(sessionId: SessionId, notificationData: NotificationData, forceNotify: Boolean): NotifiableEvent? {
|
||||||
|
val content = notificationData.content as? NotificationContent.MessageLike.CallNotify ?: return null
|
||||||
|
return notificationData.run {
|
||||||
|
if (NotifiableRingingCallEvent.shouldRing(content.type, timestamp) && !forceNotify) {
|
||||||
|
NotifiableRingingCallEvent(
|
||||||
|
sessionId = sessionId,
|
||||||
|
roomId = roomId,
|
||||||
|
eventId = eventId,
|
||||||
|
roomName = roomDisplayName,
|
||||||
|
editedEventId = null,
|
||||||
|
canBeReplaced = true,
|
||||||
|
timestamp = this.timestamp,
|
||||||
|
isRedacted = false,
|
||||||
|
isUpdated = false,
|
||||||
|
description = stringProvider.getString(R.string.notification_incoming_call),
|
||||||
|
senderDisambiguatedDisplayName = getDisambiguatedDisplayName(content.senderId),
|
||||||
|
roomAvatarUrl = roomAvatarUrl,
|
||||||
|
callNotifyType = content.type,
|
||||||
|
senderId = content.senderId,
|
||||||
|
senderAvatarUrl = senderAvatarUrl,
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
// Create a simple message notification event
|
||||||
|
buildNotifiableMessageEvent(
|
||||||
|
sessionId = sessionId,
|
||||||
|
senderId = content.senderId,
|
||||||
|
roomId = roomId,
|
||||||
|
eventId = eventId,
|
||||||
|
noisy = true,
|
||||||
|
timestamp = this.timestamp,
|
||||||
|
senderDisambiguatedDisplayName = getDisambiguatedDisplayName(content.senderId),
|
||||||
|
body = "☎️ ${stringProvider.getString(R.string.notification_incoming_call)}",
|
||||||
|
roomName = roomDisplayName,
|
||||||
|
roomIsDirect = isDirect,
|
||||||
|
roomAvatarPath = roomAvatarUrl,
|
||||||
|
senderAvatarPath = senderAvatarUrl,
|
||||||
|
type = EventType.CALL_NOTIFY,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -52,7 +52,6 @@ import io.element.android.libraries.push.impl.notifications.model.FallbackNotifi
|
||||||
import io.element.android.libraries.push.impl.notifications.model.InviteNotifiableEvent
|
import io.element.android.libraries.push.impl.notifications.model.InviteNotifiableEvent
|
||||||
import io.element.android.libraries.push.impl.notifications.model.NotifiableEvent
|
import io.element.android.libraries.push.impl.notifications.model.NotifiableEvent
|
||||||
import io.element.android.libraries.push.impl.notifications.model.NotifiableMessageEvent
|
import io.element.android.libraries.push.impl.notifications.model.NotifiableMessageEvent
|
||||||
import io.element.android.libraries.push.impl.notifications.model.NotifiableRingingCallEvent
|
|
||||||
import io.element.android.libraries.ui.strings.CommonStrings
|
import io.element.android.libraries.ui.strings.CommonStrings
|
||||||
import io.element.android.services.toolbox.api.strings.StringProvider
|
import io.element.android.services.toolbox.api.strings.StringProvider
|
||||||
import io.element.android.services.toolbox.api.systemclock.SystemClock
|
import io.element.android.services.toolbox.api.systemclock.SystemClock
|
||||||
|
|
@ -79,6 +78,7 @@ class DefaultNotifiableEventResolver @Inject constructor(
|
||||||
private val notificationMediaRepoFactory: NotificationMediaRepo.Factory,
|
private val notificationMediaRepoFactory: NotificationMediaRepo.Factory,
|
||||||
@ApplicationContext private val context: Context,
|
@ApplicationContext private val context: Context,
|
||||||
private val permalinkParser: PermalinkParser,
|
private val permalinkParser: PermalinkParser,
|
||||||
|
private val callNotificationEventResolver: CallNotificationEventResolver,
|
||||||
) : NotifiableEventResolver {
|
) : NotifiableEventResolver {
|
||||||
override suspend fun resolveEvent(sessionId: SessionId, roomId: RoomId, eventId: EventId): NotifiableEvent? {
|
override suspend fun resolveEvent(sessionId: SessionId, roomId: RoomId, eventId: EventId): NotifiableEvent? {
|
||||||
// Restore session
|
// Restore session
|
||||||
|
|
@ -174,42 +174,7 @@ class DefaultNotifiableEventResolver @Inject constructor(
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
is NotificationContent.MessageLike.CallNotify -> {
|
is NotificationContent.MessageLike.CallNotify -> {
|
||||||
if (NotifiableRingingCallEvent.shouldRing(content.type, timestamp)) {
|
callNotificationEventResolver.resolveEvent(userId, this)
|
||||||
NotifiableRingingCallEvent(
|
|
||||||
sessionId = userId,
|
|
||||||
roomId = roomId,
|
|
||||||
eventId = eventId,
|
|
||||||
roomName = roomDisplayName,
|
|
||||||
editedEventId = null,
|
|
||||||
canBeReplaced = true,
|
|
||||||
timestamp = this.timestamp,
|
|
||||||
isRedacted = false,
|
|
||||||
isUpdated = false,
|
|
||||||
description = stringProvider.getString(R.string.notification_incoming_call),
|
|
||||||
senderDisambiguatedDisplayName = getDisambiguatedDisplayName(content.senderId),
|
|
||||||
roomAvatarUrl = roomAvatarUrl,
|
|
||||||
callNotifyType = content.type,
|
|
||||||
senderId = content.senderId,
|
|
||||||
senderAvatarUrl = senderAvatarUrl,
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
// Create a simple message notification event
|
|
||||||
buildNotifiableMessageEvent(
|
|
||||||
sessionId = userId,
|
|
||||||
senderId = content.senderId,
|
|
||||||
roomId = roomId,
|
|
||||||
eventId = eventId,
|
|
||||||
noisy = true,
|
|
||||||
timestamp = this.timestamp,
|
|
||||||
senderDisambiguatedDisplayName = getDisambiguatedDisplayName(content.senderId),
|
|
||||||
body = "☎️ ${stringProvider.getString(R.string.notification_incoming_call)}",
|
|
||||||
roomName = roomDisplayName,
|
|
||||||
roomIsDirect = isDirect,
|
|
||||||
roomAvatarPath = roomAvatarUrl,
|
|
||||||
senderAvatarPath = senderAvatarUrl,
|
|
||||||
type = EventType.CALL_NOTIFY,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
NotificationContent.MessageLike.KeyVerificationAccept,
|
NotificationContent.MessageLike.KeyVerificationAccept,
|
||||||
NotificationContent.MessageLike.KeyVerificationCancel,
|
NotificationContent.MessageLike.KeyVerificationCancel,
|
||||||
|
|
@ -349,7 +314,7 @@ class DefaultNotifiableEventResolver @Inject constructor(
|
||||||
}
|
}
|
||||||
|
|
||||||
@Suppress("LongParameterList")
|
@Suppress("LongParameterList")
|
||||||
private fun buildNotifiableMessageEvent(
|
internal fun buildNotifiableMessageEvent(
|
||||||
sessionId: SessionId,
|
sessionId: SessionId,
|
||||||
senderId: UserId,
|
senderId: UserId,
|
||||||
roomId: RoomId,
|
roomId: RoomId,
|
||||||
|
|
|
||||||
|
|
@ -18,6 +18,7 @@ package io.element.android.libraries.push.impl.notifications
|
||||||
|
|
||||||
import com.squareup.anvil.annotations.ContributesBinding
|
import com.squareup.anvil.annotations.ContributesBinding
|
||||||
import io.element.android.libraries.di.AppScope
|
import io.element.android.libraries.di.AppScope
|
||||||
|
import io.element.android.libraries.matrix.api.MatrixClientProvider
|
||||||
import io.element.android.libraries.matrix.api.core.EventId
|
import io.element.android.libraries.matrix.api.core.EventId
|
||||||
import io.element.android.libraries.matrix.api.core.RoomId
|
import io.element.android.libraries.matrix.api.core.RoomId
|
||||||
import io.element.android.libraries.matrix.api.core.SessionId
|
import io.element.android.libraries.matrix.api.core.SessionId
|
||||||
|
|
@ -26,8 +27,9 @@ import javax.inject.Inject
|
||||||
|
|
||||||
@ContributesBinding(AppScope::class)
|
@ContributesBinding(AppScope::class)
|
||||||
class DefaultOnMissedCallNotificationHandler @Inject constructor(
|
class DefaultOnMissedCallNotificationHandler @Inject constructor(
|
||||||
|
private val matrixClientProvider: MatrixClientProvider,
|
||||||
private val defaultNotificationDrawerManager: DefaultNotificationDrawerManager,
|
private val defaultNotificationDrawerManager: DefaultNotificationDrawerManager,
|
||||||
private val notifiableEventResolver: NotifiableEventResolver,
|
private val callNotificationEventResolver: CallNotificationEventResolver,
|
||||||
) : OnMissedCallNotificationHandler {
|
) : OnMissedCallNotificationHandler {
|
||||||
override suspend fun addMissedCallNotification(
|
override suspend fun addMissedCallNotification(
|
||||||
sessionId: SessionId,
|
sessionId: SessionId,
|
||||||
|
|
@ -35,7 +37,18 @@ class DefaultOnMissedCallNotificationHandler @Inject constructor(
|
||||||
eventId: EventId,
|
eventId: EventId,
|
||||||
) {
|
) {
|
||||||
// Resolve the event and add a notification for it, at this point it should no longer be a ringing one
|
// Resolve the event and add a notification for it, at this point it should no longer be a ringing one
|
||||||
val notifiableEvent = notifiableEventResolver.resolveEvent(sessionId, roomId, eventId)
|
val notificationData = matrixClientProvider.getOrRestore(sessionId).getOrNull()
|
||||||
|
?.notificationService()
|
||||||
|
?.getNotification(sessionId, roomId, eventId)
|
||||||
|
?.getOrNull()
|
||||||
|
?: return
|
||||||
|
|
||||||
|
val notifiableEvent = callNotificationEventResolver.resolveEvent(
|
||||||
|
sessionId = sessionId,
|
||||||
|
notificationData = notificationData,
|
||||||
|
// Make sure the notifiable event is not a ringing one
|
||||||
|
forceNotify = true,
|
||||||
|
)
|
||||||
notifiableEvent?.let { defaultNotificationDrawerManager.onNotifiableEventReceived(it) }
|
notifiableEvent?.let { defaultNotificationDrawerManager.onNotifiableEventReceived(it) }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -660,6 +660,9 @@ class DefaultNotifiableEventResolverTest {
|
||||||
notificationMediaRepoFactory = notificationMediaRepoFactory,
|
notificationMediaRepoFactory = notificationMediaRepoFactory,
|
||||||
context = context,
|
context = context,
|
||||||
permalinkParser = FakePermalinkParser(),
|
permalinkParser = FakePermalinkParser(),
|
||||||
|
callNotificationEventResolver = DefaultCallNotificationEventResolver(
|
||||||
|
stringProvider = AndroidStringProvider(context.resources)
|
||||||
|
),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -19,11 +19,16 @@ package io.element.android.libraries.push.impl.notifications
|
||||||
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_SESSION_ID
|
import io.element.android.libraries.matrix.test.A_SESSION_ID
|
||||||
|
import io.element.android.libraries.matrix.test.A_USER_NAME
|
||||||
|
import io.element.android.libraries.matrix.test.FakeMatrixClient
|
||||||
import io.element.android.libraries.matrix.test.FakeMatrixClientProvider
|
import io.element.android.libraries.matrix.test.FakeMatrixClientProvider
|
||||||
|
import io.element.android.libraries.matrix.test.notification.FakeNotificationService
|
||||||
|
import io.element.android.libraries.matrix.test.notification.aNotificationData
|
||||||
import io.element.android.libraries.push.impl.notifications.fake.FakeActiveNotificationsProvider
|
import io.element.android.libraries.push.impl.notifications.fake.FakeActiveNotificationsProvider
|
||||||
import io.element.android.libraries.push.impl.notifications.fake.FakeNotificationDataFactory
|
import io.element.android.libraries.push.impl.notifications.fake.FakeNotificationDataFactory
|
||||||
import io.element.android.libraries.push.impl.notifications.fake.FakeNotificationDisplayer
|
import io.element.android.libraries.push.impl.notifications.fake.FakeNotificationDisplayer
|
||||||
import io.element.android.libraries.push.impl.notifications.fixtures.aNotifiableMessageEvent
|
import io.element.android.libraries.push.impl.notifications.fixtures.aNotifiableMessageEvent
|
||||||
|
import io.element.android.libraries.push.test.notifications.FakeCallNotificationEventResolver
|
||||||
import io.element.android.libraries.push.test.notifications.FakeImageLoaderHolder
|
import io.element.android.libraries.push.test.notifications.FakeImageLoaderHolder
|
||||||
import io.element.android.services.appnavstate.test.FakeAppNavigationStateService
|
import io.element.android.services.appnavstate.test.FakeAppNavigationStateService
|
||||||
import io.element.android.tests.testutils.lambda.lambdaRecorder
|
import io.element.android.tests.testutils.lambda.lambdaRecorder
|
||||||
|
|
@ -47,7 +52,15 @@ class DefaultOnMissedCallNotificationHandlerTest {
|
||||||
val dataFactory = FakeNotificationDataFactory(
|
val dataFactory = FakeNotificationDataFactory(
|
||||||
messageEventToNotificationsResult = lambdaRecorder { _, _, _ -> emptyList() }
|
messageEventToNotificationsResult = lambdaRecorder { _, _, _ -> emptyList() }
|
||||||
)
|
)
|
||||||
|
// Create a fake matrix client provider that returns a fake matrix client with a fake notification service that returns a valid notification data
|
||||||
|
val matrixClientProvider = FakeMatrixClientProvider(getClient = {
|
||||||
|
val notificationService = FakeNotificationService().apply {
|
||||||
|
givenGetNotificationResult(Result.success(aNotificationData(senderDisplayName = A_USER_NAME, senderIsNameAmbiguous = false)))
|
||||||
|
}
|
||||||
|
Result.success(FakeMatrixClient(notificationService = notificationService))
|
||||||
|
})
|
||||||
val defaultOnMissedCallNotificationHandler = DefaultOnMissedCallNotificationHandler(
|
val defaultOnMissedCallNotificationHandler = DefaultOnMissedCallNotificationHandler(
|
||||||
|
matrixClientProvider = matrixClientProvider,
|
||||||
defaultNotificationDrawerManager = DefaultNotificationDrawerManager(
|
defaultNotificationDrawerManager = DefaultNotificationDrawerManager(
|
||||||
notificationManager = mockk(relaxed = true),
|
notificationManager = mockk(relaxed = true),
|
||||||
notificationRenderer = NotificationRenderer(
|
notificationRenderer = NotificationRenderer(
|
||||||
|
|
@ -60,7 +73,7 @@ class DefaultOnMissedCallNotificationHandlerTest {
|
||||||
imageLoaderHolder = FakeImageLoaderHolder(),
|
imageLoaderHolder = FakeImageLoaderHolder(),
|
||||||
activeNotificationsProvider = FakeActiveNotificationsProvider(),
|
activeNotificationsProvider = FakeActiveNotificationsProvider(),
|
||||||
),
|
),
|
||||||
notifiableEventResolver = FakeNotifiableEventResolver(notifiableEventResult = { _, _, _ -> aNotifiableMessageEvent() }),
|
callNotificationEventResolver = FakeCallNotificationEventResolver(resolveEventLambda = { _, _, _ -> aNotifiableMessageEvent() }),
|
||||||
)
|
)
|
||||||
|
|
||||||
defaultOnMissedCallNotificationHandler.addMissedCallNotification(
|
defaultOnMissedCallNotificationHandler.addMissedCallNotification(
|
||||||
|
|
|
||||||
|
|
@ -104,7 +104,7 @@ fun aNotifiableMessageEvent(
|
||||||
type = type,
|
type = type,
|
||||||
)
|
)
|
||||||
|
|
||||||
fun anNotifiableCallEvent(
|
fun aNotifiableCallEvent(
|
||||||
sessionId: SessionId = A_SESSION_ID,
|
sessionId: SessionId = A_SESSION_ID,
|
||||||
roomId: RoomId = A_ROOM_ID,
|
roomId: RoomId = A_ROOM_ID,
|
||||||
eventId: EventId = AN_EVENT_ID,
|
eventId: EventId = AN_EVENT_ID,
|
||||||
|
|
|
||||||
|
|
@ -37,8 +37,8 @@ import io.element.android.libraries.matrix.test.auth.FakeMatrixAuthenticationSer
|
||||||
import io.element.android.libraries.matrix.test.core.aBuildMeta
|
import io.element.android.libraries.matrix.test.core.aBuildMeta
|
||||||
import io.element.android.libraries.push.impl.notifications.FakeNotifiableEventResolver
|
import io.element.android.libraries.push.impl.notifications.FakeNotifiableEventResolver
|
||||||
import io.element.android.libraries.push.impl.notifications.channels.FakeNotificationChannels
|
import io.element.android.libraries.push.impl.notifications.channels.FakeNotificationChannels
|
||||||
|
import io.element.android.libraries.push.impl.notifications.fixtures.aNotifiableCallEvent
|
||||||
import io.element.android.libraries.push.impl.notifications.fixtures.aNotifiableMessageEvent
|
import io.element.android.libraries.push.impl.notifications.fixtures.aNotifiableMessageEvent
|
||||||
import io.element.android.libraries.push.impl.notifications.fixtures.anNotifiableCallEvent
|
|
||||||
import io.element.android.libraries.push.impl.notifications.model.NotifiableEvent
|
import io.element.android.libraries.push.impl.notifications.model.NotifiableEvent
|
||||||
import io.element.android.libraries.push.impl.test.DefaultTestPush
|
import io.element.android.libraries.push.impl.test.DefaultTestPush
|
||||||
import io.element.android.libraries.push.impl.troubleshoot.DiagnosticPushHandler
|
import io.element.android.libraries.push.impl.troubleshoot.DiagnosticPushHandler
|
||||||
|
|
@ -240,7 +240,7 @@ class DefaultPushHandlerTest {
|
||||||
val elementCallEntryPoint = FakeElementCallEntryPoint(handleIncomingCallResult = handleIncomingCallLambda)
|
val elementCallEntryPoint = FakeElementCallEntryPoint(handleIncomingCallResult = handleIncomingCallLambda)
|
||||||
val defaultPushHandler = createDefaultPushHandler(
|
val defaultPushHandler = createDefaultPushHandler(
|
||||||
elementCallEntryPoint = elementCallEntryPoint,
|
elementCallEntryPoint = elementCallEntryPoint,
|
||||||
notifiableEventResult = { _, _, _ -> anNotifiableCallEvent(callNotifyType = CallNotifyType.RING, timestamp = Instant.now().toEpochMilli()) },
|
notifiableEventResult = { _, _, _ -> aNotifiableCallEvent(callNotifyType = CallNotifyType.RING, timestamp = Instant.now().toEpochMilli()) },
|
||||||
incrementPushCounterResult = {},
|
incrementPushCounterResult = {},
|
||||||
pushClientSecret = FakePushClientSecret(
|
pushClientSecret = FakePushClientSecret(
|
||||||
getUserIdFromSecretResult = { A_USER_ID }
|
getUserIdFromSecretResult = { A_USER_ID }
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,30 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2024 New Vector Ltd
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.element.android.libraries.push.test.notifications
|
||||||
|
|
||||||
|
import io.element.android.libraries.matrix.api.core.SessionId
|
||||||
|
import io.element.android.libraries.matrix.api.notification.NotificationData
|
||||||
|
import io.element.android.libraries.push.impl.notifications.CallNotificationEventResolver
|
||||||
|
import io.element.android.libraries.push.impl.notifications.model.NotifiableEvent
|
||||||
|
|
||||||
|
class FakeCallNotificationEventResolver(
|
||||||
|
var resolveEventLambda: (sessionId: SessionId, notificationData: NotificationData, forceNotify: Boolean) -> NotifiableEvent? = { _, _, _ -> null },
|
||||||
|
) : CallNotificationEventResolver {
|
||||||
|
override fun resolveEvent(sessionId: SessionId, notificationData: NotificationData, forceNotify: Boolean): NotifiableEvent? {
|
||||||
|
return resolveEventLambda(sessionId, notificationData, forceNotify)
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue