Element Call ringing notifications (#2978)
- Add `ActiveCallManager` to handle incoming and ongoing calls. - Add ringing call notifications with full screen intents and missed call ones as part of the 'conversation' notifications. --------- Co-authored-by: ElementBot <benoitm+elementbot@element.io>
This commit is contained in:
parent
4867354fd4
commit
30a1367714
186 changed files with 2686 additions and 330 deletions
|
|
@ -22,6 +22,7 @@ import com.squareup.anvil.annotations.ContributesBinding
|
|||
import io.element.android.libraries.di.AppScope
|
||||
import io.element.android.libraries.matrix.api.core.RoomId
|
||||
import io.element.android.libraries.matrix.api.core.SessionId
|
||||
import io.element.android.libraries.push.api.notifications.NotificationIdProvider
|
||||
import javax.inject.Inject
|
||||
|
||||
interface ActiveNotificationsProvider {
|
||||
|
|
@ -37,7 +38,6 @@ interface ActiveNotificationsProvider {
|
|||
@ContributesBinding(AppScope::class)
|
||||
class DefaultActiveNotificationsProvider @Inject constructor(
|
||||
private val notificationManager: NotificationManagerCompat,
|
||||
private val notificationIdProvider: NotificationIdProvider,
|
||||
) : ActiveNotificationsProvider {
|
||||
override fun getAllNotifications(): List<StatusBarNotification> {
|
||||
return notificationManager.activeNotifications
|
||||
|
|
@ -48,22 +48,22 @@ class DefaultActiveNotificationsProvider @Inject constructor(
|
|||
}
|
||||
|
||||
override fun getMembershipNotificationForSession(sessionId: SessionId): List<StatusBarNotification> {
|
||||
val notificationId = notificationIdProvider.getRoomInvitationNotificationId(sessionId)
|
||||
val notificationId = NotificationIdProvider.getRoomInvitationNotificationId(sessionId)
|
||||
return getNotificationsForSession(sessionId).filter { it.id == notificationId }
|
||||
}
|
||||
|
||||
override fun getMessageNotificationsForRoom(sessionId: SessionId, roomId: RoomId): List<StatusBarNotification> {
|
||||
val notificationId = notificationIdProvider.getRoomMessagesNotificationId(sessionId)
|
||||
val notificationId = NotificationIdProvider.getRoomMessagesNotificationId(sessionId)
|
||||
return getNotificationsForSession(sessionId).filter { it.id == notificationId && it.tag == roomId.value }
|
||||
}
|
||||
|
||||
override fun getMembershipNotificationForRoom(sessionId: SessionId, roomId: RoomId): List<StatusBarNotification> {
|
||||
val notificationId = notificationIdProvider.getRoomInvitationNotificationId(sessionId)
|
||||
val notificationId = NotificationIdProvider.getRoomInvitationNotificationId(sessionId)
|
||||
return getNotificationsForSession(sessionId).filter { it.id == notificationId && it.tag == roomId.value }
|
||||
}
|
||||
|
||||
override fun getSummaryNotification(sessionId: SessionId): StatusBarNotification? {
|
||||
val summaryId = notificationIdProvider.getSummaryNotificationId(sessionId)
|
||||
val summaryId = NotificationIdProvider.getSummaryNotificationId(sessionId)
|
||||
return getNotificationsForSession(sessionId).find { it.id == summaryId }
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -36,6 +36,7 @@ import io.element.android.libraries.matrix.api.permalink.PermalinkParser
|
|||
import io.element.android.libraries.matrix.api.room.RoomMembershipState
|
||||
import io.element.android.libraries.matrix.api.timeline.item.event.AudioMessageType
|
||||
import io.element.android.libraries.matrix.api.timeline.item.event.EmoteMessageType
|
||||
import io.element.android.libraries.matrix.api.timeline.item.event.EventType
|
||||
import io.element.android.libraries.matrix.api.timeline.item.event.FileMessageType
|
||||
import io.element.android.libraries.matrix.api.timeline.item.event.ImageMessageType
|
||||
import io.element.android.libraries.matrix.api.timeline.item.event.LocationMessageType
|
||||
|
|
@ -51,6 +52,7 @@ 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.NotifiableEvent
|
||||
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.services.toolbox.api.strings.StringProvider
|
||||
import io.element.android.services.toolbox.api.systemclock.SystemClock
|
||||
|
|
@ -150,8 +152,7 @@ class DefaultNotifiableEventResolver @Inject constructor(
|
|||
}
|
||||
NotificationContent.MessageLike.CallAnswer,
|
||||
NotificationContent.MessageLike.CallCandidates,
|
||||
NotificationContent.MessageLike.CallHangup,
|
||||
is NotificationContent.MessageLike.CallNotify -> { // TODO CallNotify will be handled separately in the future
|
||||
NotificationContent.MessageLike.CallHangup -> {
|
||||
Timber.tag(loggerTag.value).d("Ignoring notification for call ${content.javaClass.simpleName}")
|
||||
null
|
||||
}
|
||||
|
|
@ -172,6 +173,44 @@ class DefaultNotifiableEventResolver @Inject constructor(
|
|||
senderAvatarPath = senderAvatarUrl,
|
||||
)
|
||||
}
|
||||
is NotificationContent.MessageLike.CallNotify -> {
|
||||
if (NotifiableRingingCallEvent.shouldRing(content.type, timestamp)) {
|
||||
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.KeyVerificationCancel,
|
||||
NotificationContent.MessageLike.KeyVerificationDone,
|
||||
|
|
@ -334,7 +373,8 @@ private fun buildNotifiableMessageEvent(
|
|||
outGoingMessage: Boolean = false,
|
||||
outGoingMessageFailed: Boolean = false,
|
||||
isRedacted: Boolean = false,
|
||||
isUpdated: Boolean = false
|
||||
isUpdated: Boolean = false,
|
||||
type: String = EventType.MESSAGE,
|
||||
) = NotifiableMessageEvent(
|
||||
sessionId = sessionId,
|
||||
senderId = senderId,
|
||||
|
|
@ -356,5 +396,6 @@ private fun buildNotifiableMessageEvent(
|
|||
outGoingMessage = outGoingMessage,
|
||||
outGoingMessageFailed = outGoingMessageFailed,
|
||||
isRedacted = isRedacted,
|
||||
isUpdated = isUpdated
|
||||
isUpdated = isUpdated,
|
||||
type = type,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -24,23 +24,27 @@ import androidx.core.graphics.drawable.toBitmap
|
|||
import coil.ImageLoader
|
||||
import coil.request.ImageRequest
|
||||
import coil.transform.CircleCropTransformation
|
||||
import com.squareup.anvil.annotations.ContributesBinding
|
||||
import io.element.android.libraries.di.AppScope
|
||||
import io.element.android.libraries.di.ApplicationContext
|
||||
import io.element.android.libraries.matrix.api.media.MediaSource
|
||||
import io.element.android.libraries.matrix.ui.media.MediaRequestData
|
||||
import io.element.android.libraries.push.api.notifications.NotificationBitmapLoader
|
||||
import io.element.android.services.toolbox.api.sdk.BuildVersionSdkIntProvider
|
||||
import timber.log.Timber
|
||||
import javax.inject.Inject
|
||||
|
||||
class NotificationBitmapLoader @Inject constructor(
|
||||
@ContributesBinding(AppScope::class)
|
||||
class DefaultNotificationBitmapLoader @Inject constructor(
|
||||
@ApplicationContext private val context: Context,
|
||||
private val sdkIntProvider: BuildVersionSdkIntProvider,
|
||||
) {
|
||||
) : NotificationBitmapLoader {
|
||||
/**
|
||||
* Get icon of a room.
|
||||
* @param path mxc url
|
||||
* @param imageLoader Coil image loader
|
||||
*/
|
||||
suspend fun getRoomBitmap(path: String?, imageLoader: ImageLoader): Bitmap? {
|
||||
override suspend fun getRoomBitmap(path: String?, imageLoader: ImageLoader): Bitmap? {
|
||||
if (path == null) {
|
||||
return null
|
||||
}
|
||||
|
|
@ -67,7 +71,7 @@ class NotificationBitmapLoader @Inject constructor(
|
|||
* @param path mxc url
|
||||
* @param imageLoader Coil image loader
|
||||
*/
|
||||
suspend fun getUserIcon(path: String?, imageLoader: ImageLoader): IconCompat? {
|
||||
override suspend fun getUserIcon(path: String?, imageLoader: ImageLoader): IconCompat? {
|
||||
if (path == null || sdkIntProvider.get() < Build.VERSION_CODES.P) {
|
||||
return null
|
||||
}
|
||||
|
|
@ -31,6 +31,7 @@ import io.element.android.libraries.matrix.api.core.ThreadId
|
|||
import io.element.android.libraries.matrix.api.user.MatrixUser
|
||||
import io.element.android.libraries.matrix.ui.media.ImageLoaderHolder
|
||||
import io.element.android.libraries.push.api.notifications.NotificationDrawerManager
|
||||
import io.element.android.libraries.push.api.notifications.NotificationIdProvider
|
||||
import io.element.android.libraries.push.impl.notifications.model.NotifiableEvent
|
||||
import io.element.android.libraries.push.impl.notifications.model.shouldIgnoreEventInRoom
|
||||
import io.element.android.services.appnavstate.api.AppNavigationStateService
|
||||
|
|
@ -53,7 +54,6 @@ private val loggerTag = LoggerTag("DefaultNotificationDrawerManager", LoggerTag.
|
|||
class DefaultNotificationDrawerManager @Inject constructor(
|
||||
private val notificationManager: NotificationManagerCompat,
|
||||
private val notificationRenderer: NotificationRenderer,
|
||||
private val notificationIdProvider: NotificationIdProvider,
|
||||
private val appNavigationStateService: AppNavigationStateService,
|
||||
coroutineScope: CoroutineScope,
|
||||
private val matrixClientProvider: MatrixClientProvider,
|
||||
|
|
@ -124,7 +124,7 @@ class DefaultNotificationDrawerManager @Inject constructor(
|
|||
* Clear all known message events for a [sessionId].
|
||||
*/
|
||||
override fun clearAllMessagesEvents(sessionId: SessionId) {
|
||||
notificationManager.cancel(null, notificationIdProvider.getRoomMessagesNotificationId(sessionId))
|
||||
notificationManager.cancel(null, NotificationIdProvider.getRoomMessagesNotificationId(sessionId))
|
||||
clearSummaryNotificationIfNeeded(sessionId)
|
||||
}
|
||||
|
||||
|
|
@ -142,7 +142,7 @@ class DefaultNotificationDrawerManager @Inject constructor(
|
|||
* Can also be called when a notification for this room is dismissed by the user.
|
||||
*/
|
||||
override fun clearMessagesForRoom(sessionId: SessionId, roomId: RoomId) {
|
||||
notificationManager.cancel(roomId.value, notificationIdProvider.getRoomMessagesNotificationId(sessionId))
|
||||
notificationManager.cancel(roomId.value, NotificationIdProvider.getRoomMessagesNotificationId(sessionId))
|
||||
clearSummaryNotificationIfNeeded(sessionId)
|
||||
}
|
||||
|
||||
|
|
@ -165,7 +165,7 @@ class DefaultNotificationDrawerManager @Inject constructor(
|
|||
* Clear the notifications for a single event.
|
||||
*/
|
||||
override fun clearEvent(sessionId: SessionId, eventId: EventId) {
|
||||
val id = notificationIdProvider.getRoomEventNotificationId(sessionId)
|
||||
val id = NotificationIdProvider.getRoomEventNotificationId(sessionId)
|
||||
notificationManager.cancel(eventId.value, id)
|
||||
clearSummaryNotificationIfNeeded(sessionId)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,41 @@
|
|||
/*
|
||||
* 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.EventId
|
||||
import io.element.android.libraries.matrix.api.core.RoomId
|
||||
import io.element.android.libraries.matrix.api.core.SessionId
|
||||
import io.element.android.libraries.push.api.notifications.OnMissedCallNotificationHandler
|
||||
import javax.inject.Inject
|
||||
|
||||
@ContributesBinding(AppScope::class)
|
||||
class DefaultOnMissedCallNotificationHandler @Inject constructor(
|
||||
private val defaultNotificationDrawerManager: DefaultNotificationDrawerManager,
|
||||
private val notifiableEventResolver: NotifiableEventResolver,
|
||||
) : OnMissedCallNotificationHandler {
|
||||
override suspend fun addMissedCallNotification(
|
||||
sessionId: SessionId,
|
||||
roomId: RoomId,
|
||||
eventId: EventId,
|
||||
) {
|
||||
// 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)
|
||||
notifiableEvent?.let { defaultNotificationDrawerManager.onNotifiableEventReceived(it) }
|
||||
}
|
||||
}
|
||||
|
|
@ -49,6 +49,8 @@ interface NotificationDataFactory {
|
|||
@JvmName("toNotificationSimpleEvents")
|
||||
@Suppress("INAPPLICABLE_JVM_NAME")
|
||||
fun toNotifications(simpleEvents: List<SimpleNotifiableEvent>): List<OneShotNotification>
|
||||
@JvmName("toNotificationFallbackEvents")
|
||||
@Suppress("INAPPLICABLE_JVM_NAME")
|
||||
fun toNotifications(fallback: List<FallbackNotifiableEvent>): List<OneShotNotification>
|
||||
|
||||
fun createSummaryNotification(
|
||||
|
|
@ -130,6 +132,8 @@ class DefaultNotificationDataFactory @Inject constructor(
|
|||
}
|
||||
}
|
||||
|
||||
@JvmName("toNotificationFallbackEvents")
|
||||
@Suppress("INAPPLICABLE_JVM_NAME")
|
||||
override fun toNotifications(fallback: List<FallbackNotifiableEvent>): List<OneShotNotification> {
|
||||
return fallback.map { event ->
|
||||
OneShotNotification(
|
||||
|
|
|
|||
|
|
@ -1,56 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2023 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 io.element.android.libraries.matrix.api.core.SessionId
|
||||
import javax.inject.Inject
|
||||
import kotlin.math.abs
|
||||
|
||||
class NotificationIdProvider @Inject constructor() {
|
||||
fun getSummaryNotificationId(sessionId: SessionId): Int {
|
||||
return getOffset(sessionId) + SUMMARY_NOTIFICATION_ID
|
||||
}
|
||||
|
||||
fun getRoomMessagesNotificationId(sessionId: SessionId): Int {
|
||||
return getOffset(sessionId) + ROOM_MESSAGES_NOTIFICATION_ID
|
||||
}
|
||||
|
||||
fun getRoomEventNotificationId(sessionId: SessionId): Int {
|
||||
return getOffset(sessionId) + ROOM_EVENT_NOTIFICATION_ID
|
||||
}
|
||||
|
||||
fun getRoomInvitationNotificationId(sessionId: SessionId): Int {
|
||||
return getOffset(sessionId) + ROOM_INVITATION_NOTIFICATION_ID
|
||||
}
|
||||
|
||||
fun getFallbackNotificationId(sessionId: SessionId): Int {
|
||||
return getOffset(sessionId) + FALLBACK_NOTIFICATION_ID
|
||||
}
|
||||
|
||||
private fun getOffset(sessionId: SessionId): Int {
|
||||
// Compute a int from a string with a low risk of collision.
|
||||
return abs(sessionId.value.hashCode() % 100_000) * 10
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val FALLBACK_NOTIFICATION_ID = -1
|
||||
private const val SUMMARY_NOTIFICATION_ID = 0
|
||||
private const val ROOM_MESSAGES_NOTIFICATION_ID = 1
|
||||
private const val ROOM_EVENT_NOTIFICATION_ID = 2
|
||||
private const val ROOM_INVITATION_NOTIFICATION_ID = 3
|
||||
}
|
||||
}
|
||||
|
|
@ -19,10 +19,12 @@ package io.element.android.libraries.push.impl.notifications
|
|||
import coil.ImageLoader
|
||||
import io.element.android.libraries.core.log.logger.LoggerTag
|
||||
import io.element.android.libraries.matrix.api.user.MatrixUser
|
||||
import io.element.android.libraries.push.api.notifications.NotificationIdProvider
|
||||
import io.element.android.libraries.push.impl.notifications.model.FallbackNotifiableEvent
|
||||
import io.element.android.libraries.push.impl.notifications.model.InviteNotifiableEvent
|
||||
import io.element.android.libraries.push.impl.notifications.model.NotifiableEvent
|
||||
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.push.impl.notifications.model.SimpleNotifiableEvent
|
||||
import timber.log.Timber
|
||||
import javax.inject.Inject
|
||||
|
|
@ -30,7 +32,6 @@ import javax.inject.Inject
|
|||
private val loggerTag = LoggerTag("NotificationRenderer", LoggerTag.NotificationLoggerTag)
|
||||
|
||||
class NotificationRenderer @Inject constructor(
|
||||
private val notificationIdProvider: NotificationIdProvider,
|
||||
private val notificationDisplayer: NotificationDisplayer,
|
||||
private val notificationDataFactory: NotificationDataFactory,
|
||||
) {
|
||||
|
|
@ -59,14 +60,14 @@ class NotificationRenderer @Inject constructor(
|
|||
Timber.tag(loggerTag.value).d("Removing summary notification")
|
||||
notificationDisplayer.cancelNotificationMessage(
|
||||
tag = null,
|
||||
id = notificationIdProvider.getSummaryNotificationId(currentUser.userId)
|
||||
id = NotificationIdProvider.getSummaryNotificationId(currentUser.userId)
|
||||
)
|
||||
}
|
||||
|
||||
roomNotifications.forEach { notificationData ->
|
||||
notificationDisplayer.showNotificationMessage(
|
||||
tag = notificationData.roomId.value,
|
||||
id = notificationIdProvider.getRoomMessagesNotificationId(currentUser.userId),
|
||||
id = NotificationIdProvider.getRoomMessagesNotificationId(currentUser.userId),
|
||||
notification = notificationData.notification
|
||||
)
|
||||
}
|
||||
|
|
@ -76,7 +77,7 @@ class NotificationRenderer @Inject constructor(
|
|||
Timber.tag(loggerTag.value).d("Updating invitation notification ${notificationData.key}")
|
||||
notificationDisplayer.showNotificationMessage(
|
||||
tag = notificationData.key,
|
||||
id = notificationIdProvider.getRoomInvitationNotificationId(currentUser.userId),
|
||||
id = NotificationIdProvider.getRoomInvitationNotificationId(currentUser.userId),
|
||||
notification = notificationData.notification
|
||||
)
|
||||
}
|
||||
|
|
@ -87,7 +88,7 @@ class NotificationRenderer @Inject constructor(
|
|||
Timber.tag(loggerTag.value).d("Updating simple notification ${notificationData.key}")
|
||||
notificationDisplayer.showNotificationMessage(
|
||||
tag = notificationData.key,
|
||||
id = notificationIdProvider.getRoomEventNotificationId(currentUser.userId),
|
||||
id = NotificationIdProvider.getRoomEventNotificationId(currentUser.userId),
|
||||
notification = notificationData.notification
|
||||
)
|
||||
}
|
||||
|
|
@ -98,7 +99,7 @@ class NotificationRenderer @Inject constructor(
|
|||
Timber.tag(loggerTag.value).d("Showing fallback notification")
|
||||
notificationDisplayer.showNotificationMessage(
|
||||
tag = "FALLBACK",
|
||||
id = notificationIdProvider.getFallbackNotificationId(currentUser.userId),
|
||||
id = NotificationIdProvider.getFallbackNotificationId(currentUser.userId),
|
||||
notification = fallbackNotifications.first().notification
|
||||
)
|
||||
}
|
||||
|
|
@ -108,7 +109,7 @@ class NotificationRenderer @Inject constructor(
|
|||
Timber.tag(loggerTag.value).d("Updating summary notification")
|
||||
notificationDisplayer.showNotificationMessage(
|
||||
tag = null,
|
||||
id = notificationIdProvider.getSummaryNotificationId(currentUser.userId),
|
||||
id = NotificationIdProvider.getSummaryNotificationId(currentUser.userId),
|
||||
notification = summaryNotification.notification
|
||||
)
|
||||
}
|
||||
|
|
@ -127,6 +128,8 @@ private fun List<NotifiableEvent>.groupByType(): GroupedNotificationEvents {
|
|||
is NotifiableMessageEvent -> roomEvents.add(event.castedToEventType())
|
||||
is SimpleNotifiableEvent -> simpleEvents.add(event.castedToEventType())
|
||||
is FallbackNotifiableEvent -> fallbackEvents.add(event.castedToEventType())
|
||||
// Nothing should be done for ringing call events as they're not handled here
|
||||
is NotifiableRingingCallEvent -> {}
|
||||
}
|
||||
}
|
||||
return GroupedNotificationEvents(roomEvents, simpleEvents, invitationEvents, fallbackEvents)
|
||||
|
|
|
|||
|
|
@ -23,6 +23,7 @@ import com.squareup.anvil.annotations.ContributesBinding
|
|||
import io.element.android.libraries.di.AppScope
|
||||
import io.element.android.libraries.matrix.api.core.RoomId
|
||||
import io.element.android.libraries.matrix.api.user.MatrixUser
|
||||
import io.element.android.libraries.push.api.notifications.NotificationBitmapLoader
|
||||
import io.element.android.libraries.push.impl.R
|
||||
import io.element.android.libraries.push.impl.notifications.factories.NotificationCreator
|
||||
import io.element.android.libraries.push.impl.notifications.factories.isSmartReplyError
|
||||
|
|
|
|||
|
|
@ -19,10 +19,15 @@ package io.element.android.libraries.push.impl.notifications.channels
|
|||
import android.app.NotificationChannel
|
||||
import android.app.NotificationManager
|
||||
import android.content.Context
|
||||
import android.media.AudioAttributes
|
||||
import android.media.AudioManager
|
||||
import android.media.RingtoneManager
|
||||
import android.os.Build
|
||||
import androidx.annotation.ChecksSdkIntAtLeast
|
||||
import androidx.core.app.NotificationChannelCompat
|
||||
import androidx.core.app.NotificationManagerCompat
|
||||
import androidx.core.content.ContextCompat
|
||||
import com.squareup.anvil.annotations.ContributesBinding
|
||||
import io.element.android.libraries.di.AppScope
|
||||
import io.element.android.libraries.di.ApplicationContext
|
||||
import io.element.android.libraries.di.SingleIn
|
||||
|
|
@ -30,15 +35,51 @@ import io.element.android.libraries.push.impl.R
|
|||
import io.element.android.services.toolbox.api.strings.StringProvider
|
||||
import javax.inject.Inject
|
||||
|
||||
/* ==========================================================================================
|
||||
* IDs for channels
|
||||
* ========================================================================================== */
|
||||
private const val LISTENING_FOR_EVENTS_NOTIFICATION_CHANNEL_ID = "LISTEN_FOR_EVENTS_NOTIFICATION_CHANNEL_ID"
|
||||
internal const val SILENT_NOTIFICATION_CHANNEL_ID = "DEFAULT_SILENT_NOTIFICATION_CHANNEL_ID_V2"
|
||||
internal const val NOISY_NOTIFICATION_CHANNEL_ID = "DEFAULT_NOISY_NOTIFICATION_CHANNEL_ID"
|
||||
|
||||
// Legacy channel
|
||||
private const val CALL_NOTIFICATION_CHANNEL_ID_V2 = "CALL_NOTIFICATION_CHANNEL_ID_V2"
|
||||
|
||||
internal const val CALL_NOTIFICATION_CHANNEL_ID_V3 = "CALL_NOTIFICATION_CHANNEL_ID_V3"
|
||||
internal const val RINGING_CALL_NOTIFICATION_CHANNEL_ID = "RINGING_CALL_NOTIFICATION_CHANNEL_ID"
|
||||
|
||||
/**
|
||||
* on devices >= android O, we need to define a channel for each notifications.
|
||||
*/
|
||||
interface NotificationChannels {
|
||||
/**
|
||||
* Get the channel for incoming call.
|
||||
* @param ring true if the device should ring when receiving the call.
|
||||
*/
|
||||
fun getChannelForIncomingCall(ring: Boolean): String
|
||||
|
||||
/**
|
||||
* Get the channel for messages.
|
||||
* @param noisy true if the notification should have sound and vibration.
|
||||
*/
|
||||
fun getChannelIdForMessage(noisy: Boolean): String
|
||||
|
||||
/**
|
||||
* Get the channel for test notifications.
|
||||
*/
|
||||
fun getChannelIdForTest(): String
|
||||
}
|
||||
|
||||
@ChecksSdkIntAtLeast(api = Build.VERSION_CODES.O)
|
||||
private fun supportNotificationChannels() = Build.VERSION.SDK_INT >= Build.VERSION_CODES.O
|
||||
|
||||
@SingleIn(AppScope::class)
|
||||
class NotificationChannels @Inject constructor(
|
||||
@ContributesBinding(AppScope::class)
|
||||
class DefaultNotificationChannels @Inject constructor(
|
||||
@ApplicationContext private val context: Context,
|
||||
private val notificationManager: NotificationManagerCompat,
|
||||
private val stringProvider: StringProvider,
|
||||
) {
|
||||
) : NotificationChannels {
|
||||
init {
|
||||
createNotificationChannels()
|
||||
}
|
||||
|
|
@ -75,6 +116,9 @@ class NotificationChannels @Inject constructor(
|
|||
}
|
||||
}
|
||||
|
||||
// Migration - Create new call channel
|
||||
notificationManager.deleteNotificationChannel(CALL_NOTIFICATION_CHANNEL_ID_V2)
|
||||
|
||||
/**
|
||||
* Default notification importance: shows everywhere, makes noise, but does not visually
|
||||
* intrude.
|
||||
|
|
@ -123,46 +167,52 @@ class NotificationChannels @Inject constructor(
|
|||
}
|
||||
)
|
||||
|
||||
// Register a channel for incoming and in progress call notifications with no ringing
|
||||
notificationManager.createNotificationChannel(
|
||||
NotificationChannel(
|
||||
CALL_NOTIFICATION_CHANNEL_ID,
|
||||
CALL_NOTIFICATION_CHANNEL_ID_V3,
|
||||
stringProvider.getString(R.string.notification_channel_call).ifEmpty { "Call" },
|
||||
NotificationManager.IMPORTANCE_HIGH
|
||||
)
|
||||
.apply {
|
||||
description = stringProvider.getString(R.string.notification_channel_call)
|
||||
setSound(null, null)
|
||||
enableVibration(true)
|
||||
enableLights(true)
|
||||
lightColor = accentColor
|
||||
}
|
||||
)
|
||||
|
||||
// Register a channel for incoming call notifications which will ring the device when received
|
||||
val ringtoneUri = RingtoneManager.getActualDefaultRingtoneUri(context, RingtoneManager.TYPE_RINGTONE)
|
||||
notificationManager.createNotificationChannel(
|
||||
NotificationChannelCompat.Builder(
|
||||
RINGING_CALL_NOTIFICATION_CHANNEL_ID,
|
||||
NotificationManagerCompat.IMPORTANCE_MAX,
|
||||
)
|
||||
.setName(stringProvider.getString(R.string.notification_channel_ringing_calls).ifEmpty { "Ringing calls" })
|
||||
.setVibrationEnabled(true)
|
||||
.setSound(
|
||||
ringtoneUri,
|
||||
AudioAttributes.Builder()
|
||||
.setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION)
|
||||
.setLegacyStreamType(AudioManager.STREAM_RING)
|
||||
.setUsage(AudioAttributes.USAGE_NOTIFICATION_RINGTONE)
|
||||
.build()
|
||||
)
|
||||
.setDescription(stringProvider.getString(R.string.notification_channel_ringing_calls))
|
||||
.setLightsEnabled(true)
|
||||
.setLightColor(accentColor)
|
||||
.build()
|
||||
)
|
||||
}
|
||||
|
||||
private fun getChannel(channelId: String): NotificationChannel? {
|
||||
return notificationManager.getNotificationChannel(channelId)
|
||||
override fun getChannelForIncomingCall(ring: Boolean): String {
|
||||
return if (ring) RINGING_CALL_NOTIFICATION_CHANNEL_ID else CALL_NOTIFICATION_CHANNEL_ID_V3
|
||||
}
|
||||
|
||||
fun getChannelForIncomingCall(fromBg: Boolean): NotificationChannel? {
|
||||
val notificationChannel = if (fromBg) CALL_NOTIFICATION_CHANNEL_ID else SILENT_NOTIFICATION_CHANNEL_ID
|
||||
return getChannel(notificationChannel)
|
||||
}
|
||||
|
||||
fun getChannelIdForMessage(noisy: Boolean): String {
|
||||
override fun getChannelIdForMessage(noisy: Boolean): String {
|
||||
return if (noisy) NOISY_NOTIFICATION_CHANNEL_ID else SILENT_NOTIFICATION_CHANNEL_ID
|
||||
}
|
||||
|
||||
fun getChannelIdForTest(): String = NOISY_NOTIFICATION_CHANNEL_ID
|
||||
|
||||
companion object {
|
||||
/* ==========================================================================================
|
||||
* IDs for channels
|
||||
* ========================================================================================== */
|
||||
private const val LISTENING_FOR_EVENTS_NOTIFICATION_CHANNEL_ID = "LISTEN_FOR_EVENTS_NOTIFICATION_CHANNEL_ID"
|
||||
private const val SILENT_NOTIFICATION_CHANNEL_ID = "DEFAULT_SILENT_NOTIFICATION_CHANNEL_ID_V2"
|
||||
private const val NOISY_NOTIFICATION_CHANNEL_ID = "DEFAULT_NOISY_NOTIFICATION_CHANNEL_ID"
|
||||
private const val CALL_NOTIFICATION_CHANNEL_ID = "CALL_NOTIFICATION_CHANNEL_ID_V2"
|
||||
|
||||
@ChecksSdkIntAtLeast(api = Build.VERSION_CODES.O)
|
||||
private fun supportNotificationChannels() = Build.VERSION.SDK_INT >= Build.VERSION_CODES.O
|
||||
}
|
||||
override fun getChannelIdForTest(): String = NOISY_NOTIFICATION_CHANNEL_ID
|
||||
}
|
||||
|
|
|
|||
|
|
@ -35,9 +35,10 @@ import io.element.android.libraries.di.AppScope
|
|||
import io.element.android.libraries.di.ApplicationContext
|
||||
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.timeline.item.event.EventType
|
||||
import io.element.android.libraries.matrix.api.user.MatrixUser
|
||||
import io.element.android.libraries.push.api.notifications.NotificationBitmapLoader
|
||||
import io.element.android.libraries.push.impl.R
|
||||
import io.element.android.libraries.push.impl.notifications.NotificationBitmapLoader
|
||||
import io.element.android.libraries.push.impl.notifications.RoomEventGroupInfo
|
||||
import io.element.android.libraries.push.impl.notifications.channels.NotificationChannels
|
||||
import io.element.android.libraries.push.impl.notifications.debug.annotateForDebug
|
||||
|
|
@ -129,12 +130,16 @@ class DefaultNotificationCreator @Inject constructor(
|
|||
|
||||
val smallIcon = CommonDrawables.ic_notification_small
|
||||
|
||||
val channelId = notificationChannels.getChannelIdForMessage(roomInfo.shouldBing)
|
||||
val containsMissedCall = events.any { it.type == EventType.CALL_NOTIFY }
|
||||
val channelId = if (containsMissedCall) {
|
||||
notificationChannels.getChannelForIncomingCall(false)
|
||||
} else {
|
||||
notificationChannels.getChannelIdForMessage(noisy = roomInfo.shouldBing)
|
||||
}
|
||||
val builder = if (existingNotification != null) {
|
||||
NotificationCompat.Builder(context, existingNotification)
|
||||
} else {
|
||||
NotificationCompat.Builder(context, channelId)
|
||||
.setOnlyAlertOnce(roomInfo.isUpdated)
|
||||
// A category allows groups of notifications to be ranked and filtered – per user or system settings.
|
||||
// For example, alarm notifications should display before promo notifications, or message from known contact
|
||||
// that can be displayed in not disturb mode if white listed (the later will need compat28.x)
|
||||
|
|
@ -210,6 +215,11 @@ class DefaultNotificationCreator @Inject constructor(
|
|||
setLargeIcon(largeIcon)
|
||||
}
|
||||
setDeleteIntent(pendingIntentFactory.createDismissRoomPendingIntent(roomInfo.sessionId, roomInfo.roomId))
|
||||
|
||||
// If any of the events are of call notify type it means a missed call, set the category to the right value
|
||||
if (events.any { it.type == EventType.CALL_NOTIFY }) {
|
||||
setCategory(NotificationCompat.CATEGORY_MISSED_CALL)
|
||||
}
|
||||
}
|
||||
.setTicker(tickerText)
|
||||
.build()
|
||||
|
|
@ -343,7 +353,6 @@ class DefaultNotificationCreator @Inject constructor(
|
|||
.setWhen(lastMessageTimestamp)
|
||||
.setCategory(NotificationCompat.CATEGORY_MESSAGE)
|
||||
.setSmallIcon(smallIcon)
|
||||
// set content text to support devices running API level < 24
|
||||
.setGroup(currentUser.userId.value)
|
||||
// set this notification as the summary for the group
|
||||
.setGroupSummary(true)
|
||||
|
|
|
|||
|
|
@ -51,9 +51,9 @@ data class NotifiableMessageEvent(
|
|||
val outGoingMessage: Boolean = false,
|
||||
val outGoingMessageFailed: Boolean = false,
|
||||
override val isRedacted: Boolean = false,
|
||||
override val isUpdated: Boolean = false
|
||||
) : NotifiableEvent {
|
||||
override val isUpdated: Boolean = false,
|
||||
val type: String = EventType.MESSAGE
|
||||
) : NotifiableEvent {
|
||||
override val description: String = body ?: ""
|
||||
|
||||
// Example of value:
|
||||
|
|
@ -69,9 +69,16 @@ fun NotifiableEvent.shouldIgnoreEventInRoom(appNavigationState: AppNavigationSta
|
|||
val currentSessionId = appNavigationState.navigationState.currentSessionId() ?: return false
|
||||
return when (val currentRoomId = appNavigationState.navigationState.currentRoomId()) {
|
||||
null -> false
|
||||
else -> appNavigationState.isInForeground &&
|
||||
sessionId == currentSessionId &&
|
||||
roomId == currentRoomId &&
|
||||
(this as? NotifiableMessageEvent)?.threadId == appNavigationState.navigationState.currentThreadId()
|
||||
else -> {
|
||||
// Never ignore ringing call notifications
|
||||
if (this is NotifiableRingingCallEvent) {
|
||||
false
|
||||
} else {
|
||||
appNavigationState.isInForeground &&
|
||||
sessionId == currentSessionId &&
|
||||
roomId == currentRoomId &&
|
||||
(this as? NotifiableMessageEvent)?.threadId == appNavigationState.navigationState.currentThreadId()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,52 @@
|
|||
/*
|
||||
* 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.model
|
||||
|
||||
import io.element.android.libraries.matrix.api.core.EventId
|
||||
import io.element.android.libraries.matrix.api.core.RoomId
|
||||
import io.element.android.libraries.matrix.api.core.SessionId
|
||||
import io.element.android.libraries.matrix.api.core.UserId
|
||||
import io.element.android.libraries.matrix.api.notification.CallNotifyType
|
||||
import java.time.Instant
|
||||
import kotlin.time.Duration.Companion.seconds
|
||||
|
||||
data class NotifiableRingingCallEvent(
|
||||
override val sessionId: SessionId,
|
||||
override val roomId: RoomId,
|
||||
override val eventId: EventId,
|
||||
override val editedEventId: EventId?,
|
||||
override val description: String?,
|
||||
override val canBeReplaced: Boolean,
|
||||
override val isRedacted: Boolean,
|
||||
override val isUpdated: Boolean,
|
||||
val roomName: String?,
|
||||
val senderId: UserId,
|
||||
val senderDisambiguatedDisplayName: String?,
|
||||
val senderAvatarUrl: String?,
|
||||
val roomAvatarUrl: String? = null,
|
||||
val callNotifyType: CallNotifyType,
|
||||
val timestamp: Long,
|
||||
) : NotifiableEvent {
|
||||
companion object {
|
||||
fun shouldRing(callNotifyType: CallNotifyType, timestamp: Long): Boolean {
|
||||
val timeout = 10.seconds.inWholeMilliseconds
|
||||
val elapsed = Instant.now().toEpochMilli() - timestamp
|
||||
// Only ring if the type is RING and the elapsed time is less than the timeout
|
||||
return callNotifyType == CallNotifyType.RING && elapsed < timeout
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -17,11 +17,15 @@
|
|||
package io.element.android.libraries.push.impl.push
|
||||
|
||||
import com.squareup.anvil.annotations.ContributesBinding
|
||||
import io.element.android.features.call.api.CallType
|
||||
import io.element.android.features.call.api.ElementCallEntryPoint
|
||||
import io.element.android.libraries.core.log.logger.LoggerTag
|
||||
import io.element.android.libraries.core.meta.BuildMeta
|
||||
import io.element.android.libraries.di.AppScope
|
||||
import io.element.android.libraries.matrix.api.auth.MatrixAuthenticationService
|
||||
import io.element.android.libraries.push.impl.notifications.NotifiableEventResolver
|
||||
import io.element.android.libraries.push.impl.notifications.channels.NotificationChannels
|
||||
import io.element.android.libraries.push.impl.notifications.model.NotifiableRingingCallEvent
|
||||
import io.element.android.libraries.push.impl.test.DefaultTestPush
|
||||
import io.element.android.libraries.push.impl.troubleshoot.DiagnosticPushHandler
|
||||
import io.element.android.libraries.pushproviders.api.PushData
|
||||
|
|
@ -44,6 +48,8 @@ class DefaultPushHandler @Inject constructor(
|
|||
private val buildMeta: BuildMeta,
|
||||
private val matrixAuthenticationService: MatrixAuthenticationService,
|
||||
private val diagnosticPushHandler: DiagnosticPushHandler,
|
||||
private val elementCallEntryPoint: ElementCallEntryPoint,
|
||||
private val notificationChannels: NotificationChannels,
|
||||
) : PushHandler {
|
||||
/**
|
||||
* Called when message is received.
|
||||
|
|
@ -91,19 +97,33 @@ class DefaultPushHandler @Inject constructor(
|
|||
return
|
||||
}
|
||||
val userPushStore = userPushStoreFactory.getOrCreate(userId)
|
||||
if (userPushStore.getNotificationEnabledForDevice().first()) {
|
||||
val areNotificationsEnabled = userPushStore.getNotificationEnabledForDevice().first()
|
||||
if (areNotificationsEnabled) {
|
||||
val notifiableEvent = notifiableEventResolver.resolveEvent(userId, pushData.roomId, pushData.eventId)
|
||||
if (notifiableEvent == null) {
|
||||
Timber.w("Unable to get a notification data")
|
||||
return
|
||||
when (notifiableEvent) {
|
||||
null -> Timber.tag(loggerTag.value).w("Unable to get a notification data")
|
||||
is NotifiableRingingCallEvent -> handleRingingCallEvent(notifiableEvent)
|
||||
else -> onNotifiableEventReceived.onNotifiableEventReceived(notifiableEvent)
|
||||
}
|
||||
onNotifiableEventReceived.onNotifiableEventReceived(notifiableEvent)
|
||||
} else {
|
||||
// TODO We need to check if this is an incoming call
|
||||
Timber.tag(loggerTag.value).i("Notification are disabled for this device, ignore push.")
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
Timber.tag(loggerTag.value).e(e, "## handleInternal() failed")
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleRingingCallEvent(notifiableEvent: NotifiableRingingCallEvent) {
|
||||
Timber.i("## handleInternal() : Incoming call.")
|
||||
elementCallEntryPoint.handleIncomingCall(
|
||||
callType = CallType.RoomCall(notifiableEvent.sessionId, notifiableEvent.roomId),
|
||||
eventId = notifiableEvent.eventId,
|
||||
senderId = notifiableEvent.senderId,
|
||||
roomName = notifiableEvent.roomName,
|
||||
senderName = notifiableEvent.senderDisambiguatedDisplayName,
|
||||
avatarUrl = notifiableEvent.roomAvatarUrl,
|
||||
timestamp = notifiableEvent.timestamp,
|
||||
notificationChannelId = notificationChannels.getChannelForIncomingCall(ring = true),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@
|
|||
<string name="notification_channel_call">"Call"</string>
|
||||
<string name="notification_channel_listening_for_events">"Listening for events"</string>
|
||||
<string name="notification_channel_noisy">"Noisy notifications"</string>
|
||||
<string name="notification_channel_ringing_calls">"Ringing calls"</string>
|
||||
<string name="notification_channel_silent">"Silent notifications"</string>
|
||||
<plurals name="notification_compat_summary_line_for_room">
|
||||
<item quantity="one">"%1$s: %2$d message"</item>
|
||||
|
|
@ -13,6 +14,7 @@
|
|||
<item quantity="other">"%d notifications"</item>
|
||||
</plurals>
|
||||
<string name="notification_fallback_content">"Notification"</string>
|
||||
<string name="notification_incoming_call">"Incoming call"</string>
|
||||
<string name="notification_inline_reply_failed">"** Failed to send - please open room"</string>
|
||||
<string name="notification_invitation_action_join">"Join"</string>
|
||||
<string name="notification_invitation_action_reject">"Reject"</string>
|
||||
|
|
|
|||
|
|
@ -25,6 +25,7 @@ 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_SESSION_ID
|
||||
import io.element.android.libraries.matrix.test.A_SESSION_ID_2
|
||||
import io.element.android.libraries.push.api.notifications.NotificationIdProvider
|
||||
import io.mockk.every
|
||||
import io.mockk.mockk
|
||||
import org.junit.Test
|
||||
|
|
@ -33,6 +34,8 @@ import org.robolectric.RobolectricTestRunner
|
|||
|
||||
@RunWith(RobolectricTestRunner::class)
|
||||
class DefaultActiveNotificationsProviderTest {
|
||||
private val notificationIdProvider = NotificationIdProvider
|
||||
|
||||
@Test
|
||||
fun `getAllNotifications with no active notifications returns empty list`() {
|
||||
val activeNotificationsProvider = createActiveNotificationsProvider(activeNotifications = emptyList())
|
||||
|
|
@ -43,7 +46,6 @@ class DefaultActiveNotificationsProviderTest {
|
|||
|
||||
@Test
|
||||
fun `getAllNotifications with active notifications returns all`() {
|
||||
val notificationIdProvider = NotificationIdProvider()
|
||||
val activeNotifications = listOf(
|
||||
aStatusBarNotification(id = notificationIdProvider.getRoomMessagesNotificationId(A_SESSION_ID), groupId = A_SESSION_ID.value),
|
||||
aStatusBarNotification(id = notificationIdProvider.getSummaryNotificationId(A_SESSION_ID), groupId = A_SESSION_ID.value),
|
||||
|
|
@ -57,7 +59,6 @@ class DefaultActiveNotificationsProviderTest {
|
|||
|
||||
@Test
|
||||
fun `getNotificationsForSession returns only notifications for that session id`() {
|
||||
val notificationIdProvider = NotificationIdProvider()
|
||||
val activeNotifications = listOf(
|
||||
aStatusBarNotification(id = notificationIdProvider.getRoomMessagesNotificationId(A_SESSION_ID), groupId = A_SESSION_ID.value),
|
||||
aStatusBarNotification(id = notificationIdProvider.getSummaryNotificationId(A_SESSION_ID_2), groupId = A_SESSION_ID_2.value),
|
||||
|
|
@ -71,7 +72,6 @@ class DefaultActiveNotificationsProviderTest {
|
|||
|
||||
@Test
|
||||
fun `getMembershipNotificationsForSession returns only membership notifications for that session id`() {
|
||||
val notificationIdProvider = NotificationIdProvider()
|
||||
val activeNotifications = listOf(
|
||||
aStatusBarNotification(id = notificationIdProvider.getRoomMessagesNotificationId(A_SESSION_ID), groupId = A_SESSION_ID.value,),
|
||||
aStatusBarNotification(id = notificationIdProvider.getSummaryNotificationId(A_SESSION_ID_2), groupId = A_SESSION_ID_2.value),
|
||||
|
|
@ -89,7 +89,6 @@ class DefaultActiveNotificationsProviderTest {
|
|||
|
||||
@Test
|
||||
fun `getMessageNotificationsForRoom returns only message notifications for those session and room ids`() {
|
||||
val notificationIdProvider = NotificationIdProvider()
|
||||
val activeNotifications = listOf(
|
||||
aStatusBarNotification(
|
||||
id = notificationIdProvider.getRoomMessagesNotificationId(A_SESSION_ID),
|
||||
|
|
@ -117,7 +116,6 @@ class DefaultActiveNotificationsProviderTest {
|
|||
|
||||
@Test
|
||||
fun `getMembershipNotificationsForRoom returns only membership notifications for those session and room ids`() {
|
||||
val notificationIdProvider = NotificationIdProvider()
|
||||
val activeNotifications = listOf(
|
||||
aStatusBarNotification(
|
||||
id = notificationIdProvider.getRoomMessagesNotificationId(A_SESSION_ID),
|
||||
|
|
@ -145,7 +143,6 @@ class DefaultActiveNotificationsProviderTest {
|
|||
|
||||
@Test
|
||||
fun `getSummaryNotification returns only the summary notification for that session id if it exists`() {
|
||||
val notificationIdProvider = NotificationIdProvider()
|
||||
val activeNotifications = listOf(
|
||||
aStatusBarNotification(id = notificationIdProvider.getRoomMessagesNotificationId(A_SESSION_ID), groupId = A_SESSION_ID.value),
|
||||
aStatusBarNotification(id = notificationIdProvider.getSummaryNotificationId(A_SESSION_ID), groupId = A_SESSION_ID.value),
|
||||
|
|
@ -172,7 +169,6 @@ class DefaultActiveNotificationsProviderTest {
|
|||
}
|
||||
return DefaultActiveNotificationsProvider(
|
||||
notificationManager = notificationManager,
|
||||
notificationIdProvider = NotificationIdProvider(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -18,12 +18,15 @@ package io.element.android.libraries.push.impl.notifications
|
|||
|
||||
import android.content.Context
|
||||
import com.google.common.truth.Truth.assertThat
|
||||
import io.element.android.libraries.matrix.api.core.UserId
|
||||
import io.element.android.libraries.matrix.api.media.MediaSource
|
||||
import io.element.android.libraries.matrix.api.notification.CallNotifyType
|
||||
import io.element.android.libraries.matrix.api.notification.NotificationContent
|
||||
import io.element.android.libraries.matrix.api.notification.NotificationData
|
||||
import io.element.android.libraries.matrix.api.room.RoomMembershipState
|
||||
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.EventType
|
||||
import io.element.android.libraries.matrix.api.timeline.item.event.FileMessageType
|
||||
import io.element.android.libraries.matrix.api.timeline.item.event.FormattedBody
|
||||
import io.element.android.libraries.matrix.api.timeline.item.event.ImageMessageType
|
||||
|
|
@ -48,7 +51,9 @@ import io.element.android.libraries.push.impl.notifications.fake.FakeNotificatio
|
|||
import io.element.android.libraries.push.impl.notifications.model.FallbackNotifiableEvent
|
||||
import io.element.android.libraries.push.impl.notifications.model.InviteNotifiableEvent
|
||||
import io.element.android.libraries.push.impl.notifications.model.NotifiableMessageEvent
|
||||
import io.element.android.libraries.push.impl.notifications.model.NotifiableRingingCallEvent
|
||||
import io.element.android.services.toolbox.impl.strings.AndroidStringProvider
|
||||
import io.element.android.services.toolbox.impl.systemclock.DefaultSystemClock
|
||||
import io.element.android.services.toolbox.test.systemclock.A_FAKE_TIMESTAMP
|
||||
import io.element.android.services.toolbox.test.systemclock.FakeSystemClock
|
||||
import kotlinx.coroutines.test.runTest
|
||||
|
|
@ -58,6 +63,7 @@ import org.robolectric.RobolectricTestRunner
|
|||
import org.robolectric.RuntimeEnvironment
|
||||
import org.robolectric.annotation.Config
|
||||
|
||||
@Suppress("LargeClass")
|
||||
@RunWith(RobolectricTestRunner::class)
|
||||
class DefaultNotifiableEventResolverTest {
|
||||
@Test
|
||||
|
|
@ -479,6 +485,109 @@ class DefaultNotifiableEventResolverTest {
|
|||
assertThat(result).isEqualTo(expectedResult)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `resolve CallNotify - ringing`() = runTest {
|
||||
val timestamp = DefaultSystemClock().epochMillis()
|
||||
val sut = createDefaultNotifiableEventResolver(
|
||||
notificationResult = Result.success(
|
||||
createNotificationData(
|
||||
content = NotificationContent.MessageLike.CallNotify(
|
||||
A_USER_ID_2,
|
||||
CallNotifyType.RING
|
||||
),
|
||||
timestamp = timestamp,
|
||||
)
|
||||
)
|
||||
)
|
||||
val expectedResult = NotifiableRingingCallEvent(
|
||||
sessionId = A_SESSION_ID,
|
||||
roomId = A_ROOM_ID,
|
||||
eventId = AN_EVENT_ID,
|
||||
senderId = A_USER_ID_2,
|
||||
roomName = null,
|
||||
editedEventId = null,
|
||||
description = "Incoming call",
|
||||
timestamp = timestamp,
|
||||
canBeReplaced = true,
|
||||
isRedacted = false,
|
||||
isUpdated = false,
|
||||
senderDisambiguatedDisplayName = "Bob",
|
||||
senderAvatarUrl = null,
|
||||
callNotifyType = CallNotifyType.RING,
|
||||
)
|
||||
val result = sut.resolveEvent(A_SESSION_ID, A_ROOM_ID, AN_EVENT_ID)
|
||||
assertThat(result).isEqualTo(expectedResult)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `resolve CallNotify - ring but timed out displays the same as notify`() = runTest {
|
||||
val sut = createDefaultNotifiableEventResolver(
|
||||
notificationResult = Result.success(
|
||||
createNotificationData(
|
||||
content = NotificationContent.MessageLike.CallNotify(
|
||||
A_USER_ID_2,
|
||||
CallNotifyType.RING
|
||||
),
|
||||
timestamp = 0L,
|
||||
)
|
||||
)
|
||||
)
|
||||
val expectedResult = NotifiableMessageEvent(
|
||||
sessionId = A_SESSION_ID,
|
||||
eventId = AN_EVENT_ID,
|
||||
editedEventId = null,
|
||||
noisy = true,
|
||||
timestamp = 0L,
|
||||
senderDisambiguatedDisplayName = "Bob",
|
||||
senderId = UserId("@bob:server.org"),
|
||||
body = "☎\uFE0F Incoming call",
|
||||
roomId = A_ROOM_ID,
|
||||
threadId = null,
|
||||
roomName = null,
|
||||
roomIsDirect = false,
|
||||
canBeReplaced = false,
|
||||
isRedacted = false,
|
||||
imageUriString = null,
|
||||
type = EventType.CALL_NOTIFY,
|
||||
)
|
||||
val result = sut.resolveEvent(A_SESSION_ID, A_ROOM_ID, AN_EVENT_ID)
|
||||
assertThat(result).isEqualTo(expectedResult)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `resolve CallNotify - notify`() = runTest {
|
||||
val sut = createDefaultNotifiableEventResolver(
|
||||
notificationResult = Result.success(
|
||||
createNotificationData(
|
||||
content = NotificationContent.MessageLike.CallNotify(
|
||||
A_USER_ID_2,
|
||||
CallNotifyType.NOTIFY
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
val expectedResult = NotifiableMessageEvent(
|
||||
sessionId = A_SESSION_ID,
|
||||
eventId = AN_EVENT_ID,
|
||||
editedEventId = null,
|
||||
noisy = true,
|
||||
timestamp = A_TIMESTAMP,
|
||||
senderDisambiguatedDisplayName = "Bob",
|
||||
senderId = UserId("@bob:server.org"),
|
||||
body = "☎\uFE0F Incoming call",
|
||||
roomId = A_ROOM_ID,
|
||||
threadId = null,
|
||||
roomName = null,
|
||||
roomIsDirect = false,
|
||||
canBeReplaced = false,
|
||||
isRedacted = false,
|
||||
imageUriString = null,
|
||||
type = EventType.CALL_NOTIFY,
|
||||
)
|
||||
val result = sut.resolveEvent(A_SESSION_ID, A_ROOM_ID, AN_EVENT_ID)
|
||||
assertThat(result).isEqualTo(expectedResult)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `resolve null cases`() {
|
||||
testNull(NotificationContent.MessageLike.CallAnswer)
|
||||
|
|
@ -558,6 +667,7 @@ class DefaultNotifiableEventResolverTest {
|
|||
content: NotificationContent,
|
||||
isDirect: Boolean = false,
|
||||
hasMention: Boolean = false,
|
||||
timestamp: Long = A_TIMESTAMP,
|
||||
): NotificationData {
|
||||
return NotificationData(
|
||||
eventId = AN_EVENT_ID,
|
||||
|
|
@ -570,7 +680,7 @@ class DefaultNotifiableEventResolverTest {
|
|||
isDirect = isDirect,
|
||||
isEncrypted = false,
|
||||
isNoisy = false,
|
||||
timestamp = A_TIMESTAMP,
|
||||
timestamp = timestamp,
|
||||
content = content,
|
||||
hasMention = hasMention,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -28,12 +28,13 @@ import io.element.android.libraries.matrix.test.A_THREAD_ID
|
|||
import io.element.android.libraries.matrix.test.FakeMatrixClient
|
||||
import io.element.android.libraries.matrix.test.FakeMatrixClientProvider
|
||||
import io.element.android.libraries.matrix.ui.components.aMatrixUser
|
||||
import io.element.android.libraries.push.api.notifications.NotificationIdProvider
|
||||
import io.element.android.libraries.push.impl.notifications.fake.FakeActiveNotificationsProvider
|
||||
import io.element.android.libraries.push.impl.notifications.fake.FakeImageLoaderHolder
|
||||
import io.element.android.libraries.push.impl.notifications.fake.FakeNotificationCreator
|
||||
import io.element.android.libraries.push.impl.notifications.fake.FakeRoomGroupMessageCreator
|
||||
import io.element.android.libraries.push.impl.notifications.fake.FakeSummaryGroupMessageCreator
|
||||
import io.element.android.libraries.push.impl.notifications.fixtures.aNotifiableMessageEvent
|
||||
import io.element.android.libraries.push.test.notifications.FakeImageLoaderHolder
|
||||
import io.element.android.services.appnavstate.api.AppNavigationState
|
||||
import io.element.android.services.appnavstate.api.AppNavigationStateService
|
||||
import io.element.android.services.appnavstate.api.NavigationState
|
||||
|
|
@ -164,7 +165,7 @@ class DefaultNotificationDrawerManagerTest {
|
|||
val notificationManager = mockk<NotificationManagerCompat> {
|
||||
every { cancel(any(), any()) } returns Unit
|
||||
}
|
||||
val summaryId = NotificationIdProvider().getSummaryNotificationId(A_SESSION_ID)
|
||||
val summaryId = NotificationIdProvider.getSummaryNotificationId(A_SESSION_ID)
|
||||
val activeNotificationsProvider = FakeActiveNotificationsProvider(
|
||||
mutableListOf(
|
||||
mockk {
|
||||
|
|
@ -198,7 +199,6 @@ class DefaultNotificationDrawerManagerTest {
|
|||
return DefaultNotificationDrawerManager(
|
||||
notificationManager = notificationManager,
|
||||
notificationRenderer = NotificationRenderer(
|
||||
notificationIdProvider = NotificationIdProvider(),
|
||||
notificationDisplayer = DefaultNotificationDisplayer(context, NotificationManagerCompat.from(context)),
|
||||
notificationDataFactory = DefaultNotificationDataFactory(
|
||||
notificationCreator = FakeNotificationCreator(),
|
||||
|
|
@ -208,7 +208,6 @@ class DefaultNotificationDrawerManagerTest {
|
|||
stringProvider = FakeStringProvider(),
|
||||
),
|
||||
),
|
||||
notificationIdProvider = NotificationIdProvider(),
|
||||
appNavigationStateService = appNavigationStateService,
|
||||
coroutineScope = this,
|
||||
matrixClientProvider = matrixClientProvider,
|
||||
|
|
|
|||
|
|
@ -0,0 +1,79 @@
|
|||
/*
|
||||
* 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 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_SESSION_ID
|
||||
import io.element.android.libraries.matrix.test.FakeMatrixClientProvider
|
||||
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.FakeNotificationDisplayer
|
||||
import io.element.android.libraries.push.impl.notifications.fixtures.aNotifiableMessageEvent
|
||||
import io.element.android.libraries.push.test.notifications.FakeImageLoaderHolder
|
||||
import io.element.android.services.appnavstate.test.FakeAppNavigationStateService
|
||||
import io.element.android.tests.testutils.lambda.lambdaRecorder
|
||||
import io.mockk.mockk
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
import kotlinx.coroutines.SupervisorJob
|
||||
import kotlinx.coroutines.cancel
|
||||
import kotlinx.coroutines.test.runCurrent
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import org.robolectric.RobolectricTestRunner
|
||||
|
||||
@RunWith(RobolectricTestRunner::class)
|
||||
class DefaultOnMissedCallNotificationHandlerTest {
|
||||
@OptIn(ExperimentalCoroutinesApi::class)
|
||||
@Test
|
||||
fun `addMissedCallNotification - should add missed call notification`() = runTest {
|
||||
val childScope = CoroutineScope(coroutineContext + SupervisorJob())
|
||||
val dataFactory = FakeNotificationDataFactory(
|
||||
messageEventToNotificationsResult = lambdaRecorder { _, _, _ -> emptyList() }
|
||||
)
|
||||
val defaultOnMissedCallNotificationHandler = DefaultOnMissedCallNotificationHandler(
|
||||
defaultNotificationDrawerManager = DefaultNotificationDrawerManager(
|
||||
notificationManager = mockk(relaxed = true),
|
||||
notificationRenderer = NotificationRenderer(
|
||||
notificationDisplayer = FakeNotificationDisplayer(),
|
||||
notificationDataFactory = dataFactory,
|
||||
),
|
||||
appNavigationStateService = FakeAppNavigationStateService(),
|
||||
coroutineScope = childScope,
|
||||
matrixClientProvider = FakeMatrixClientProvider(),
|
||||
imageLoaderHolder = FakeImageLoaderHolder(),
|
||||
activeNotificationsProvider = FakeActiveNotificationsProvider(),
|
||||
),
|
||||
notifiableEventResolver = FakeNotifiableEventResolver(notifiableEventResult = { _, _, _ -> aNotifiableMessageEvent() }),
|
||||
)
|
||||
|
||||
defaultOnMissedCallNotificationHandler.addMissedCallNotification(
|
||||
sessionId = A_SESSION_ID,
|
||||
roomId = A_ROOM_ID,
|
||||
eventId = AN_EVENT_ID,
|
||||
)
|
||||
|
||||
runCurrent()
|
||||
|
||||
dataFactory.messageEventToNotificationsResult.assertions().isCalledOnce()
|
||||
|
||||
// Cancel the coroutine scope so the test can finish
|
||||
childScope.cancel()
|
||||
}
|
||||
}
|
||||
|
|
@ -25,8 +25,8 @@ import io.element.android.libraries.matrix.test.A_ROOM_ID
|
|||
import io.element.android.libraries.matrix.ui.components.aMatrixUser
|
||||
import io.element.android.libraries.matrix.ui.media.MediaRequestData
|
||||
import io.element.android.libraries.push.impl.notifications.factories.createNotificationCreator
|
||||
import io.element.android.libraries.push.impl.notifications.fake.FakeImageLoader
|
||||
import io.element.android.libraries.push.impl.notifications.fixtures.aNotifiableMessageEvent
|
||||
import io.element.android.libraries.push.test.notifications.FakeImageLoader
|
||||
import io.element.android.services.toolbox.api.sdk.BuildVersionSdkIntProvider
|
||||
import io.element.android.services.toolbox.impl.strings.AndroidStringProvider
|
||||
import io.element.android.services.toolbox.test.sdk.FakeBuildVersionSdkIntProvider
|
||||
|
|
@ -212,7 +212,7 @@ fun createRoomGroupMessageCreator(
|
|||
sdkIntProvider: BuildVersionSdkIntProvider = FakeBuildVersionSdkIntProvider(Build.VERSION_CODES.O),
|
||||
): RoomGroupMessageCreator {
|
||||
val context = RuntimeEnvironment.getApplication() as Context
|
||||
val bitmapLoader = NotificationBitmapLoader(
|
||||
val bitmapLoader = DefaultNotificationBitmapLoader(
|
||||
context = RuntimeEnvironment.getApplication(),
|
||||
sdkIntProvider = sdkIntProvider,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -23,13 +23,13 @@ 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_SESSION_ID
|
||||
import io.element.android.libraries.push.impl.notifications.fake.FakeActiveNotificationsProvider
|
||||
import io.element.android.libraries.push.impl.notifications.fake.FakeImageLoader
|
||||
import io.element.android.libraries.push.impl.notifications.fake.FakeNotificationCreator
|
||||
import io.element.android.libraries.push.impl.notifications.fake.FakeRoomGroupMessageCreator
|
||||
import io.element.android.libraries.push.impl.notifications.fake.FakeSummaryGroupMessageCreator
|
||||
import io.element.android.libraries.push.impl.notifications.fixtures.aNotifiableMessageEvent
|
||||
import io.element.android.libraries.push.impl.notifications.fixtures.aSimpleNotifiableEvent
|
||||
import io.element.android.libraries.push.impl.notifications.fixtures.anInviteNotifiableEvent
|
||||
import io.element.android.libraries.push.test.notifications.FakeImageLoader
|
||||
import io.element.android.services.toolbox.test.strings.FakeStringProvider
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import org.junit.Test
|
||||
|
|
@ -37,6 +37,7 @@ import org.junit.runner.RunWith
|
|||
import org.robolectric.RobolectricTestRunner
|
||||
|
||||
private val MY_AVATAR_URL: String? = null
|
||||
|
||||
private val AN_INVITATION_EVENT = anInviteNotifiableEvent(roomId = A_ROOM_ID)
|
||||
private val A_SIMPLE_EVENT = aSimpleNotifiableEvent(eventId = AN_EVENT_ID)
|
||||
private val A_MESSAGE_EVENT = aNotifiableMessageEvent(eventId = AN_EVENT_ID, roomId = A_ROOM_ID)
|
||||
|
|
|
|||
|
|
@ -19,12 +19,13 @@ package io.element.android.libraries.push.impl.notifications
|
|||
import com.google.common.truth.Truth.assertThat
|
||||
import io.element.android.libraries.matrix.test.A_SESSION_ID
|
||||
import io.element.android.libraries.matrix.test.A_SESSION_ID_2
|
||||
import io.element.android.libraries.push.api.notifications.NotificationIdProvider
|
||||
import org.junit.Test
|
||||
|
||||
class NotificationIdProviderTest {
|
||||
@Test
|
||||
fun `test notification id provider`() {
|
||||
val sut = NotificationIdProvider()
|
||||
val sut = NotificationIdProvider
|
||||
val offsetForASessionId = 305_410
|
||||
assertThat(sut.getSummaryNotificationId(A_SESSION_ID)).isEqualTo(offsetForASessionId + 0)
|
||||
assertThat(sut.getRoomMessagesNotificationId(A_SESSION_ID)).isEqualTo(offsetForASessionId + 1)
|
||||
|
|
|
|||
|
|
@ -20,8 +20,8 @@ import io.element.android.libraries.matrix.api.user.MatrixUser
|
|||
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_SESSION_ID
|
||||
import io.element.android.libraries.push.api.notifications.NotificationIdProvider
|
||||
import io.element.android.libraries.push.impl.notifications.fake.FakeActiveNotificationsProvider
|
||||
import io.element.android.libraries.push.impl.notifications.fake.FakeImageLoader
|
||||
import io.element.android.libraries.push.impl.notifications.fake.FakeNotificationCreator
|
||||
import io.element.android.libraries.push.impl.notifications.fake.FakeNotificationDisplayer
|
||||
import io.element.android.libraries.push.impl.notifications.fake.FakeRoomGroupMessageCreator
|
||||
|
|
@ -31,6 +31,7 @@ import io.element.android.libraries.push.impl.notifications.fixtures.aNotifiable
|
|||
import io.element.android.libraries.push.impl.notifications.fixtures.aSimpleNotifiableEvent
|
||||
import io.element.android.libraries.push.impl.notifications.fixtures.anInviteNotifiableEvent
|
||||
import io.element.android.libraries.push.impl.notifications.model.NotifiableEvent
|
||||
import io.element.android.libraries.push.test.notifications.FakeImageLoader
|
||||
import io.element.android.services.toolbox.test.strings.FakeStringProvider
|
||||
import io.element.android.tests.testutils.lambda.lambdaRecorder
|
||||
import io.element.android.tests.testutils.lambda.value
|
||||
|
|
@ -61,10 +62,9 @@ class NotificationRendererTest {
|
|||
activeNotificationsProvider = FakeActiveNotificationsProvider(),
|
||||
stringProvider = FakeStringProvider(),
|
||||
)
|
||||
private val notificationIdProvider = NotificationIdProvider()
|
||||
private val notificationIdProvider = NotificationIdProvider
|
||||
|
||||
private val notificationRenderer = NotificationRenderer(
|
||||
notificationIdProvider = notificationIdProvider,
|
||||
notificationDisplayer = notificationDisplayer,
|
||||
notificationDataFactory = notificationDataFactory,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -0,0 +1,35 @@
|
|||
/*
|
||||
* 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.channels
|
||||
|
||||
class FakeNotificationChannels(
|
||||
var channelForIncomingCall: (ring: Boolean) -> String = { _ -> "" },
|
||||
var channelIdForMessage: (noisy: Boolean) -> String = { _ -> "" },
|
||||
var channelIdForTest: () -> String = { "" }
|
||||
) : NotificationChannels {
|
||||
override fun getChannelForIncomingCall(ring: Boolean): String {
|
||||
return channelForIncomingCall(ring)
|
||||
}
|
||||
|
||||
override fun getChannelIdForMessage(noisy: Boolean): String {
|
||||
return channelIdForMessage(noisy)
|
||||
}
|
||||
|
||||
override fun getChannelIdForTest(): String {
|
||||
return channelIdForTest()
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,83 @@
|
|||
/*
|
||||
* 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.channels
|
||||
|
||||
import android.app.NotificationChannel
|
||||
import android.os.Build
|
||||
import androidx.core.app.NotificationChannelCompat
|
||||
import androidx.core.app.NotificationManagerCompat
|
||||
import androidx.test.platform.app.InstrumentationRegistry
|
||||
import com.google.common.truth.Truth.assertThat
|
||||
import io.element.android.services.toolbox.test.strings.FakeStringProvider
|
||||
import io.mockk.every
|
||||
import io.mockk.mockk
|
||||
import io.mockk.verify
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import org.robolectric.RobolectricTestRunner
|
||||
import org.robolectric.annotation.Config
|
||||
|
||||
@RunWith(RobolectricTestRunner::class)
|
||||
class NotificationChannelsTest {
|
||||
@Test
|
||||
@Config(sdk = [Build.VERSION_CODES.O])
|
||||
fun `init - creates notification channels and migrates old ones`() {
|
||||
val notificationManager = mockk<NotificationManagerCompat>(relaxed = true) {
|
||||
every { notificationChannels } returns emptyList()
|
||||
}
|
||||
|
||||
createNotificationChannels(notificationManager = notificationManager)
|
||||
|
||||
verify { notificationManager.createNotificationChannel(any<NotificationChannelCompat>()) }
|
||||
verify { notificationManager.createNotificationChannel(any<NotificationChannel>()) }
|
||||
verify { notificationManager.deleteNotificationChannel(any<String>()) }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `getChannelForIncomingCall - returns the right channel`() {
|
||||
val notificationChannels = createNotificationChannels()
|
||||
|
||||
val ringingChannel = notificationChannels.getChannelForIncomingCall(ring = true)
|
||||
assertThat(ringingChannel).isEqualTo(RINGING_CALL_NOTIFICATION_CHANNEL_ID)
|
||||
|
||||
val normalChannel = notificationChannels.getChannelForIncomingCall(ring = false)
|
||||
assertThat(normalChannel).isEqualTo(CALL_NOTIFICATION_CHANNEL_ID_V3)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `getChannelIdForMessage - returns the right channel`() {
|
||||
val notificationChannels = createNotificationChannels()
|
||||
|
||||
assertThat(notificationChannels.getChannelIdForMessage(noisy = true)).isEqualTo(NOISY_NOTIFICATION_CHANNEL_ID)
|
||||
assertThat(notificationChannels.getChannelIdForMessage(noisy = false)).isEqualTo(SILENT_NOTIFICATION_CHANNEL_ID)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `getChannelIdForTest - returns the right channel`() {
|
||||
val notificationChannels = createNotificationChannels()
|
||||
|
||||
assertThat(notificationChannels.getChannelIdForTest()).isEqualTo(NOISY_NOTIFICATION_CHANNEL_ID)
|
||||
}
|
||||
|
||||
private fun createNotificationChannels(
|
||||
notificationManager: NotificationManagerCompat = mockk(relaxed = true),
|
||||
) = DefaultNotificationChannels(
|
||||
context = InstrumentationRegistry.getInstrumentation().targetContext,
|
||||
notificationManager = notificationManager,
|
||||
stringProvider = FakeStringProvider(),
|
||||
)
|
||||
}
|
||||
|
|
@ -29,18 +29,20 @@ import io.element.android.libraries.matrix.test.A_SESSION_ID
|
|||
import io.element.android.libraries.matrix.test.A_THREAD_ID
|
||||
import io.element.android.libraries.matrix.test.core.aBuildMeta
|
||||
import io.element.android.libraries.matrix.ui.components.aMatrixUser
|
||||
import io.element.android.libraries.push.api.notifications.NotificationBitmapLoader
|
||||
import io.element.android.libraries.push.impl.notifications.DefaultNotificationBitmapLoader
|
||||
import io.element.android.libraries.push.impl.notifications.NotificationActionIds
|
||||
import io.element.android.libraries.push.impl.notifications.NotificationBitmapLoader
|
||||
import io.element.android.libraries.push.impl.notifications.RoomEventGroupInfo
|
||||
import io.element.android.libraries.push.impl.notifications.channels.DefaultNotificationChannels
|
||||
import io.element.android.libraries.push.impl.notifications.channels.NotificationChannels
|
||||
import io.element.android.libraries.push.impl.notifications.factories.action.AcceptInvitationActionFactory
|
||||
import io.element.android.libraries.push.impl.notifications.factories.action.MarkAsReadActionFactory
|
||||
import io.element.android.libraries.push.impl.notifications.factories.action.QuickReplyActionFactory
|
||||
import io.element.android.libraries.push.impl.notifications.factories.action.RejectInvitationActionFactory
|
||||
import io.element.android.libraries.push.impl.notifications.fake.FakeImageLoader
|
||||
import io.element.android.libraries.push.impl.notifications.model.FallbackNotifiableEvent
|
||||
import io.element.android.libraries.push.impl.notifications.model.InviteNotifiableEvent
|
||||
import io.element.android.libraries.push.impl.notifications.model.SimpleNotifiableEvent
|
||||
import io.element.android.libraries.push.test.notifications.FakeImageLoader
|
||||
import io.element.android.services.toolbox.test.sdk.FakeBuildVersionSdkIntProvider
|
||||
import io.element.android.services.toolbox.test.strings.FakeStringProvider
|
||||
import io.element.android.services.toolbox.test.systemclock.A_FAKE_TIMESTAMP
|
||||
|
|
@ -284,7 +286,7 @@ fun createNotificationCreator(
|
|||
context: Context = RuntimeEnvironment.getApplication(),
|
||||
buildMeta: BuildMeta = aBuildMeta(),
|
||||
notificationChannels: NotificationChannels = createNotificationChannels(),
|
||||
bitmapLoader: NotificationBitmapLoader = NotificationBitmapLoader(context, FakeBuildVersionSdkIntProvider(Build.VERSION_CODES.R)),
|
||||
bitmapLoader: NotificationBitmapLoader = DefaultNotificationBitmapLoader(context, FakeBuildVersionSdkIntProvider(Build.VERSION_CODES.R)),
|
||||
): NotificationCreator {
|
||||
return DefaultNotificationCreator(
|
||||
context = context,
|
||||
|
|
@ -327,5 +329,5 @@ fun createNotificationCreator(
|
|||
|
||||
fun createNotificationChannels(): NotificationChannels {
|
||||
val context = RuntimeEnvironment.getApplication()
|
||||
return NotificationChannels(context, NotificationManagerCompat.from(context), FakeStringProvider(""))
|
||||
return DefaultNotificationChannels(context, NotificationManagerCompat.from(context), FakeStringProvider(""))
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,55 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2023 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.fake
|
||||
|
||||
import android.graphics.Color
|
||||
import android.graphics.drawable.ColorDrawable
|
||||
import coil.ImageLoader
|
||||
import coil.annotation.ExperimentalCoilApi
|
||||
import coil.test.FakeImageLoaderEngine
|
||||
import org.robolectric.RuntimeEnvironment
|
||||
|
||||
@OptIn(ExperimentalCoilApi::class)
|
||||
class FakeImageLoader {
|
||||
private val coilRequests = mutableListOf<Any>()
|
||||
|
||||
private var cache: ImageLoader? = null
|
||||
|
||||
fun getImageLoader(): ImageLoader {
|
||||
return cache ?: ImageLoader.Builder(RuntimeEnvironment.getApplication())
|
||||
.components {
|
||||
val engine = FakeImageLoaderEngine.Builder()
|
||||
.intercept(
|
||||
predicate = {
|
||||
coilRequests.add(it)
|
||||
true
|
||||
},
|
||||
drawable = ColorDrawable(Color.BLUE)
|
||||
)
|
||||
.build()
|
||||
add(engine)
|
||||
}
|
||||
.build()
|
||||
.also {
|
||||
cache = it
|
||||
}
|
||||
}
|
||||
|
||||
fun getCoilRequests(): List<Any> {
|
||||
return coilRequests.toList()
|
||||
}
|
||||
}
|
||||
|
|
@ -1,28 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2023 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.fake
|
||||
|
||||
import coil.ImageLoader
|
||||
import io.element.android.libraries.matrix.api.MatrixClient
|
||||
import io.element.android.libraries.matrix.ui.media.ImageLoaderHolder
|
||||
|
||||
class FakeImageLoaderHolder : ImageLoaderHolder {
|
||||
private val fakeImageLoader = FakeImageLoader()
|
||||
override fun get(client: MatrixClient): ImageLoader {
|
||||
return fakeImageLoader.getImageLoader()
|
||||
}
|
||||
}
|
||||
|
|
@ -46,7 +46,7 @@ class FakeNotificationDataFactory(
|
|||
var inviteToNotificationsResult: LambdaOneParamRecorder<List<InviteNotifiableEvent>, List<OneShotNotification>> = lambdaRecorder { _ -> emptyList() },
|
||||
var simpleEventToNotificationsResult: LambdaOneParamRecorder<List<SimpleNotifiableEvent>, List<OneShotNotification>> = lambdaRecorder { _ -> emptyList() },
|
||||
var fallbackEventToNotificationsResult: LambdaOneParamRecorder<List<FallbackNotifiableEvent>, List<OneShotNotification>> =
|
||||
lambdaRecorder { _ -> emptyList() },
|
||||
lambdaRecorder { _ -> emptyList() },
|
||||
) : NotificationDataFactory {
|
||||
override suspend fun toNotifications(messages: List<NotifiableMessageEvent>, currentUser: MatrixUser, imageLoader: ImageLoader): List<RoomNotification> {
|
||||
return messageEventToNotificationsResult(messages, currentUser, imageLoader)
|
||||
|
|
@ -64,6 +64,8 @@ class FakeNotificationDataFactory(
|
|||
return simpleEventToNotificationsResult(simpleEvents)
|
||||
}
|
||||
|
||||
@JvmName("toNotificationFallbackEvents")
|
||||
@Suppress("INAPPLICABLE_JVM_NAME")
|
||||
override fun toNotifications(fallback: List<FallbackNotifiableEvent>): List<OneShotNotification> {
|
||||
return fallbackEventToNotificationsResult(fallback)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -18,8 +18,8 @@ package io.element.android.libraries.push.impl.notifications.fake
|
|||
|
||||
import android.app.Notification
|
||||
import io.element.android.libraries.matrix.test.A_SESSION_ID
|
||||
import io.element.android.libraries.push.api.notifications.NotificationIdProvider
|
||||
import io.element.android.libraries.push.impl.notifications.NotificationDisplayer
|
||||
import io.element.android.libraries.push.impl.notifications.NotificationIdProvider
|
||||
import io.element.android.tests.testutils.lambda.LambdaNoParamRecorder
|
||||
import io.element.android.tests.testutils.lambda.LambdaOneParamRecorder
|
||||
import io.element.android.tests.testutils.lambda.LambdaThreeParamsRecorder
|
||||
|
|
@ -51,7 +51,7 @@ class FakeNotificationDisplayer(
|
|||
|
||||
fun verifySummaryCancelled(times: Int = 1) {
|
||||
cancelNotificationMessageResult.assertions().isCalledExactly(times).withSequence(
|
||||
listOf(value(null), value(NotificationIdProvider().getSummaryNotificationId(A_SESSION_ID)))
|
||||
listOf(value(null), value(NotificationIdProvider.getSummaryNotificationId(A_SESSION_ID)))
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -21,11 +21,16 @@ import io.element.android.libraries.matrix.api.core.RoomId
|
|||
import io.element.android.libraries.matrix.api.core.SessionId
|
||||
import io.element.android.libraries.matrix.api.core.ThreadId
|
||||
import io.element.android.libraries.matrix.api.core.UserId
|
||||
import io.element.android.libraries.matrix.api.notification.CallNotifyType
|
||||
import io.element.android.libraries.matrix.api.timeline.item.event.EventType
|
||||
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.A_ROOM_ID
|
||||
import io.element.android.libraries.matrix.test.A_SESSION_ID
|
||||
import io.element.android.libraries.matrix.test.A_USER_ID_2
|
||||
import io.element.android.libraries.push.impl.notifications.model.InviteNotifiableEvent
|
||||
import io.element.android.libraries.push.impl.notifications.model.NotifiableMessageEvent
|
||||
import io.element.android.libraries.push.impl.notifications.model.NotifiableRingingCallEvent
|
||||
import io.element.android.libraries.push.impl.notifications.model.SimpleNotifiableEvent
|
||||
|
||||
fun aSimpleNotifiableEvent(
|
||||
|
|
@ -79,6 +84,7 @@ fun aNotifiableMessageEvent(
|
|||
threadId: ThreadId? = null,
|
||||
isRedacted: Boolean = false,
|
||||
timestamp: Long = 0,
|
||||
type: String = EventType.MESSAGE,
|
||||
) = NotifiableMessageEvent(
|
||||
sessionId = sessionId,
|
||||
eventId = eventId,
|
||||
|
|
@ -94,5 +100,34 @@ fun aNotifiableMessageEvent(
|
|||
roomIsDirect = false,
|
||||
canBeReplaced = false,
|
||||
isRedacted = isRedacted,
|
||||
imageUriString = null
|
||||
imageUriString = null,
|
||||
type = type,
|
||||
)
|
||||
|
||||
fun anNotifiableCallEvent(
|
||||
sessionId: SessionId = A_SESSION_ID,
|
||||
roomId: RoomId = A_ROOM_ID,
|
||||
eventId: EventId = AN_EVENT_ID,
|
||||
senderId: UserId = A_USER_ID_2,
|
||||
senderName: String? = null,
|
||||
roomAvatarUrl: String? = AN_AVATAR_URL,
|
||||
senderAvatarUrl: String? = AN_AVATAR_URL,
|
||||
callNotifyType: CallNotifyType = CallNotifyType.NOTIFY,
|
||||
timestamp: Long = 0L,
|
||||
) = NotifiableRingingCallEvent(
|
||||
sessionId = sessionId,
|
||||
eventId = eventId,
|
||||
roomId = roomId,
|
||||
roomName = "a room name",
|
||||
editedEventId = null,
|
||||
description = "description",
|
||||
timestamp = timestamp,
|
||||
canBeReplaced = false,
|
||||
isRedacted = false,
|
||||
isUpdated = false,
|
||||
senderDisambiguatedDisplayName = senderName,
|
||||
senderId = senderId,
|
||||
roomAvatarUrl = roomAvatarUrl,
|
||||
senderAvatarUrl = senderAvatarUrl,
|
||||
callNotifyType = callNotifyType,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -19,11 +19,16 @@
|
|||
package io.element.android.libraries.push.impl.push
|
||||
|
||||
import app.cash.turbine.test
|
||||
import io.element.android.features.call.api.CallType
|
||||
import io.element.android.features.call.test.FakeElementCallEntryPoint
|
||||
import io.element.android.libraries.core.meta.BuildMeta
|
||||
import io.element.android.libraries.matrix.api.auth.MatrixAuthenticationService
|
||||
import io.element.android.libraries.matrix.api.core.EventId
|
||||
import io.element.android.libraries.matrix.api.core.RoomId
|
||||
import io.element.android.libraries.matrix.api.core.SessionId
|
||||
import io.element.android.libraries.matrix.api.core.UserId
|
||||
import io.element.android.libraries.matrix.api.notification.CallNotifyType
|
||||
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.A_ROOM_ID
|
||||
import io.element.android.libraries.matrix.test.A_SECRET
|
||||
|
|
@ -31,7 +36,9 @@ import io.element.android.libraries.matrix.test.A_USER_ID
|
|||
import io.element.android.libraries.matrix.test.auth.FakeMatrixAuthenticationService
|
||||
import io.element.android.libraries.matrix.test.core.aBuildMeta
|
||||
import io.element.android.libraries.push.impl.notifications.FakeNotifiableEventResolver
|
||||
import io.element.android.libraries.push.impl.notifications.channels.FakeNotificationChannels
|
||||
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.test.DefaultTestPush
|
||||
import io.element.android.libraries.push.impl.troubleshoot.DiagnosticPushHandler
|
||||
|
|
@ -47,6 +54,7 @@ import io.element.android.tests.testutils.lambda.value
|
|||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import org.junit.Test
|
||||
import java.time.Instant
|
||||
|
||||
class DefaultPushHandlerTest {
|
||||
@Test
|
||||
|
|
@ -220,6 +228,55 @@ class DefaultPushHandlerTest {
|
|||
.isNeverCalled()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `when ringing call PushData is received, the incoming call will be handled`() = runTest {
|
||||
val aPushData = PushData(
|
||||
eventId = AN_EVENT_ID,
|
||||
roomId = A_ROOM_ID,
|
||||
unread = 0,
|
||||
clientSecret = A_SECRET,
|
||||
)
|
||||
val handleIncomingCallLambda = lambdaRecorder<CallType.RoomCall, EventId, UserId, String?, String?, String?, String, Unit> { _, _, _, _, _, _, _ -> }
|
||||
val elementCallEntryPoint = FakeElementCallEntryPoint(handleIncomingCallResult = handleIncomingCallLambda)
|
||||
val defaultPushHandler = createDefaultPushHandler(
|
||||
elementCallEntryPoint = elementCallEntryPoint,
|
||||
notifiableEventResult = { _, _, _ -> anNotifiableCallEvent(callNotifyType = CallNotifyType.RING, timestamp = Instant.now().toEpochMilli()) },
|
||||
incrementPushCounterResult = {},
|
||||
pushClientSecret = FakePushClientSecret(
|
||||
getUserIdFromSecretResult = { A_USER_ID }
|
||||
),
|
||||
)
|
||||
defaultPushHandler.handle(aPushData)
|
||||
|
||||
handleIncomingCallLambda.assertions().isCalledOnce()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `when notify call PushData is received, the incoming call will be treated as a normal notification`() = runTest {
|
||||
val aPushData = PushData(
|
||||
eventId = AN_EVENT_ID,
|
||||
roomId = A_ROOM_ID,
|
||||
unread = 0,
|
||||
clientSecret = A_SECRET,
|
||||
)
|
||||
val onNotifiableEventReceived = lambdaRecorder<NotifiableEvent, Unit> {}
|
||||
val handleIncomingCallLambda = lambdaRecorder<CallType.RoomCall, EventId, UserId, String?, String?, String?, String, Unit> { _, _, _, _, _, _, _ -> }
|
||||
val elementCallEntryPoint = FakeElementCallEntryPoint(handleIncomingCallResult = handleIncomingCallLambda)
|
||||
val defaultPushHandler = createDefaultPushHandler(
|
||||
elementCallEntryPoint = elementCallEntryPoint,
|
||||
onNotifiableEventReceived = onNotifiableEventReceived,
|
||||
notifiableEventResult = { _, _, _ -> aNotifiableMessageEvent(type = EventType.CALL_NOTIFY) },
|
||||
incrementPushCounterResult = {},
|
||||
pushClientSecret = FakePushClientSecret(
|
||||
getUserIdFromSecretResult = { A_USER_ID }
|
||||
),
|
||||
)
|
||||
defaultPushHandler.handle(aPushData)
|
||||
|
||||
handleIncomingCallLambda.assertions().isNeverCalled()
|
||||
onNotifiableEventReceived.assertions().isCalledOnce()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `when diagnostic PushData is received, the diagnostic push handler is informed `() =
|
||||
runTest {
|
||||
|
|
@ -249,6 +306,8 @@ class DefaultPushHandlerTest {
|
|||
buildMeta: BuildMeta = aBuildMeta(),
|
||||
matrixAuthenticationService: MatrixAuthenticationService = FakeMatrixAuthenticationService(),
|
||||
diagnosticPushHandler: DiagnosticPushHandler = DiagnosticPushHandler(),
|
||||
elementCallEntryPoint: FakeElementCallEntryPoint = FakeElementCallEntryPoint(),
|
||||
notificationChannels: FakeNotificationChannels = FakeNotificationChannels(),
|
||||
): DefaultPushHandler {
|
||||
return DefaultPushHandler(
|
||||
onNotifiableEventReceived = FakeOnNotifiableEventReceived(onNotifiableEventReceived),
|
||||
|
|
@ -263,6 +322,8 @@ class DefaultPushHandlerTest {
|
|||
buildMeta = buildMeta,
|
||||
matrixAuthenticationService = matrixAuthenticationService,
|
||||
diagnosticPushHandler = diagnosticPushHandler,
|
||||
elementCallEntryPoint = elementCallEntryPoint,
|
||||
notificationChannels = notificationChannels,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue