Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions changelog.d/582.feature
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Adding the room name to the invitation notification (if the room summary is available)
Original file line number Diff line number Diff line change
Expand Up @@ -28,17 +28,14 @@ import androidx.localbroadcastmanager.content.LocalBroadcastManager
import com.google.firebase.messaging.FirebaseMessagingService
import com.google.firebase.messaging.RemoteMessage
import im.vector.app.BuildConfig
import im.vector.app.R
import im.vector.app.core.di.ActiveSessionHolder
import im.vector.app.core.extensions.vectorComponent
import im.vector.app.core.network.WifiDetector
import im.vector.app.core.pushers.PushersManager
import im.vector.app.features.badge.BadgeProxy
import im.vector.app.features.notifications.NotifiableEventResolver
import im.vector.app.features.notifications.NotifiableMessageEvent
import im.vector.app.features.notifications.NotificationDrawerManager
import im.vector.app.features.notifications.NotificationUtils
import im.vector.app.features.notifications.SimpleNotifiableEvent
import im.vector.app.features.settings.VectorDataStore
import im.vector.app.features.settings.VectorPreferences
import im.vector.app.push.fcm.FcmHelper
Expand All @@ -48,9 +45,7 @@ import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
import org.matrix.android.sdk.api.extensions.tryOrNull
import org.matrix.android.sdk.api.logger.LoggerTag
import org.matrix.android.sdk.api.pushrules.Action
import org.matrix.android.sdk.api.session.Session
import org.matrix.android.sdk.api.session.events.model.Event
import timber.log.Timber

private val loggerTag = LoggerTag("Push", LoggerTag.SYNC)
Expand Down Expand Up @@ -212,12 +207,11 @@ class VectorFirebaseMessagingService : FirebaseMessagingService() {
Timber.tag(loggerTag.value).d("Fast lane: start request")
val event = tryOrNull { session.getEvent(roomId, eventId) } ?: return@launch

val resolvedEvent = notifiableEventResolver.resolveInMemoryEvent(session, event)
val resolvedEvent = notifiableEventResolver.resolveInMemoryEvent(session, event, canBeReplaced = true)

resolvedEvent
?.also { Timber.tag(loggerTag.value).d("Fast lane: notify drawer") }
?.let {
it.isPushGatewayEvent = true
notificationDrawerManager.onNotifiableEventReceived(it)
notificationDrawerManager.refreshNotificationDrawer()
}
Expand All @@ -238,87 +232,4 @@ class VectorFirebaseMessagingService : FirebaseMessagingService() {
}
return false
}

private fun handleNotificationWithoutSyncingMode(data: Map<String, String>, session: Session?) {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this was unused, if we want it back we can retrieve it from the source control history

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the cleanup. In practice I think this code will be lost forever :), but anyway, this is not a big loss

if (session == null) {
Timber.tag(loggerTag.value).e("## handleNotificationWithoutSyncingMode cannot find session")
return
}

// The Matrix event ID of the event being notified about.
// This is required if the notification is about a particular Matrix event.
// It may be omitted for notifications that only contain updated badge counts.
// This ID can and should be used to detect duplicate notification requests.
val eventId = data["event_id"] ?: return // Just ignore

val eventType = data["type"]
if (eventType == null) {
// Just add a generic unknown event
val simpleNotifiableEvent = SimpleNotifiableEvent(
session.myUserId,
eventId,
null,
true, // It's an issue in this case, all event will bing even if expected to be silent.
title = getString(R.string.notification_unknown_new_event),
description = "",
type = null,
timestamp = System.currentTimeMillis(),
soundName = Action.ACTION_OBJECT_VALUE_VALUE_DEFAULT,
isPushGatewayEvent = true
)
notificationDrawerManager.onNotifiableEventReceived(simpleNotifiableEvent)
notificationDrawerManager.refreshNotificationDrawer()
} else {
val event = parseEvent(data) ?: return

val notifiableEvent = notifiableEventResolver.resolveEvent(event, session)

if (notifiableEvent == null) {
Timber.tag(loggerTag.value).e("Unsupported notifiable event $eventId")
if (BuildConfig.LOW_PRIVACY_LOG_ENABLE) {
Timber.tag(loggerTag.value).e("--> $event")
}
} else {
if (notifiableEvent is NotifiableMessageEvent) {
if (notifiableEvent.senderName.isNullOrEmpty()) {
notifiableEvent.senderName = data["sender_display_name"] ?: data["sender"] ?: ""
}
if (notifiableEvent.roomName.isNullOrEmpty()) {
notifiableEvent.roomName = findRoomNameBestEffort(data, session) ?: ""
}
}

notifiableEvent.isPushGatewayEvent = true
notifiableEvent.matrixID = session.myUserId
notificationDrawerManager.onNotifiableEventReceived(notifiableEvent)
notificationDrawerManager.refreshNotificationDrawer()
}
}
}

private fun findRoomNameBestEffort(data: Map<String, String>, session: Session?): String? {
var roomName: String? = data["room_name"]
val roomId = data["room_id"]
if (null == roomName && null != roomId) {
// Try to get the room name from our store
roomName = session?.getRoom(roomId)?.roomSummary()?.displayName
}
return roomName
}

/**
* Try to create an event from the FCM data
*
* @param data the FCM data
* @return the event or null if required data are missing
*/
private fun parseEvent(data: Map<String, String>?): Event? {
return Event(
eventId = data?.get("event_id") ?: return null,
senderId = data["sender"],
roomId = data["room_id"] ?: return null,
type = data["type"] ?: return null,
originServerTs = System.currentTimeMillis()
)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,22 +15,21 @@
*/
package im.vector.app.features.notifications

import androidx.core.app.NotificationCompat

data class InviteNotifiableEvent(
override var matrixID: String?,
val matrixID: String?,
override val eventId: String,
override val editedEventId: String?,
var roomId: String,
override var noisy: Boolean,
override val title: String,
override val description: String,
override val type: String?,
override val timestamp: Long,
override var soundName: String?,
override var isPushGatewayEvent: Boolean = false) : NotifiableEvent {
override val canBeReplaced: Boolean,
val roomId: String,
val roomName: String?,
val noisy: Boolean,
val title: String,
val description: String,
val type: String?,
val timestamp: Long,
val soundName: String?,
override val isRedacted: Boolean = false
) : NotifiableEvent {

override var hasBeenDisplayed: Boolean = false
override var isRedacted: Boolean = false
override var lockScreenVisibility = NotificationCompat.VISIBILITY_PUBLIC
override var hasBeenDisplayed = false
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,24 +20,12 @@ import java.io.Serializable
/**
* Parent interface for all events which can be displayed as a Notification
*/
interface NotifiableEvent : Serializable {
var matrixID: String?
sealed interface NotifiableEvent : Serializable {
val eventId: String
val editedEventId: String?
var noisy: Boolean
val title: String
val description: String?
val type: String?
val timestamp: Long

// NotificationCompat.VISIBILITY_PUBLIC , VISIBILITY_PRIVATE , VISIBILITY_SECRET
var lockScreenVisibility: Int

// Compat: Only for android <7, for newer version the sound is defined in the channel
var soundName: String?
var hasBeenDisplayed: Boolean
var isRedacted: Boolean

// Used to know if event should be replaced with the one coming from eventstream
var isPushGatewayEvent: Boolean
val canBeReplaced: Boolean
val isRedacted: Boolean
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@
*/
package im.vector.app.features.notifications

import androidx.core.app.NotificationCompat
import im.vector.app.BuildConfig
import im.vector.app.R
import im.vector.app.core.resources.StringProvider
Expand Down Expand Up @@ -54,21 +53,19 @@ class NotifiableEventResolver @Inject constructor(

// private val eventDisplay = RiotEventDisplay(context)

fun resolveEvent(event: Event/*, roomState: RoomState?, bingRule: PushRule?*/, session: Session): NotifiableEvent? {
fun resolveEvent(event: Event/*, roomState: RoomState?, bingRule: PushRule?*/, session: Session, isNoisy: Boolean): NotifiableEvent? {
val roomID = event.roomId ?: return null
val eventId = event.eventId ?: return null
if (event.getClearType() == EventType.STATE_ROOM_MEMBER) {
return resolveStateRoomEvent(event, session)
return resolveStateRoomEvent(event, session, canBeReplaced = false, isNoisy = isNoisy)
}
val timelineEvent = session.getRoom(roomID)?.getTimeLineEvent(eventId) ?: return null
when (event.getClearType()) {
EventType.MESSAGE -> {
return resolveMessageEvent(timelineEvent, session)
return resolveMessageEvent(timelineEvent, session, canBeReplaced = false, isNoisy = isNoisy)
}
EventType.ENCRYPTED -> {
val messageEvent = resolveMessageEvent(timelineEvent, session)
messageEvent?.lockScreenVisibility = NotificationCompat.VISIBILITY_PRIVATE
return messageEvent
return resolveMessageEvent(timelineEvent, session, canBeReplaced = false, isNoisy = isNoisy)
}
else -> {
// If the event can be displayed, display it as is
Expand All @@ -85,12 +82,14 @@ class NotifiableEventResolver @Inject constructor(
description = bodyPreview,
title = stringProvider.getString(R.string.notification_unknown_new_event),
soundName = null,
type = event.type)
type = event.type,
canBeReplaced = false
)
}
}
}

fun resolveInMemoryEvent(session: Session, event: Event): NotifiableEvent? {
fun resolveInMemoryEvent(session: Session, event: Event, canBeReplaced: Boolean): NotifiableEvent? {
if (event.getClearType() != EventType.MESSAGE) return null

// Ignore message edition
Expand All @@ -114,24 +113,14 @@ class NotifiableEventResolver @Inject constructor(
avatarUrl = user.avatarUrl
)
)

val notifiableEvent = resolveMessageEvent(timelineEvent, session)

if (notifiableEvent == null) {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

resolveMessageEvent no longer returns null 🎉

Timber.d("## Failed to resolve event")
// TODO
null
} else {
notifiableEvent.noisy = !notificationAction.soundName.isNullOrBlank()
notifiableEvent
}
resolveMessageEvent(timelineEvent, session, canBeReplaced = canBeReplaced, isNoisy = !notificationAction.soundName.isNullOrBlank())
} else {
Timber.d("Matched push rule is set to not notify")
null
}
}

private fun resolveMessageEvent(event: TimelineEvent, session: Session): NotifiableEvent? {
private fun resolveMessageEvent(event: TimelineEvent, session: Session, canBeReplaced: Boolean, isNoisy: Boolean): NotifiableEvent {
// The event only contains an eventId, and roomId (type is m.room.*) , we need to get the displayable content (names, avatar, text, etc...)
val room = session.getRoom(event.root.roomId!! /*roomID cannot be null*/)

Expand All @@ -142,19 +131,19 @@ class NotifiableEventResolver @Inject constructor(
val roomName = stringProvider.getString(R.string.notification_unknown_room_name)
val senderDisplayName = event.senderInfo.disambiguatedDisplayName

val notifiableEvent = NotifiableMessageEvent(
return NotifiableMessageEvent(
eventId = event.root.eventId!!,
editedEventId = event.getEditedEventId(),
canBeReplaced = canBeReplaced,
timestamp = event.root.originServerTs ?: 0,
noisy = false, // will be updated
noisy = isNoisy,
senderName = senderDisplayName,
senderId = event.root.senderId,
body = body.toString(),
roomId = event.root.roomId!!,
roomName = roomName)

notifiableEvent.matrixID = session.myUserId
return notifiableEvent
roomName = roomName,
matrixID = session.myUserId
)
} else {
if (event.root.isEncrypted() && event.root.mxDecryptionResult == null) {
// TODO use a global event decryptor? attache to session and that listen to new sessionId?
Expand All @@ -175,57 +164,56 @@ class NotifiableEventResolver @Inject constructor(
val roomName = room.roomSummary()?.displayName ?: ""
val senderDisplayName = event.senderInfo.disambiguatedDisplayName

val notifiableEvent = NotifiableMessageEvent(
return NotifiableMessageEvent(
eventId = event.root.eventId!!,
editedEventId = event.getEditedEventId(),
canBeReplaced = canBeReplaced,
timestamp = event.root.originServerTs ?: 0,
noisy = false, // will be updated
noisy = isNoisy,
senderName = senderDisplayName,
senderId = event.root.senderId,
body = body,
roomId = event.root.roomId!!,
roomName = roomName,
roomIsDirect = room.roomSummary()?.isDirect ?: false)

notifiableEvent.matrixID = session.myUserId
notifiableEvent.soundName = null

// Get the avatars URL
notifiableEvent.roomAvatarPath = session.contentUrlResolver()
.resolveThumbnail(room.roomSummary()?.avatarUrl,
250,
250,
ContentUrlResolver.ThumbnailMethod.SCALE)

notifiableEvent.senderAvatarPath = session.contentUrlResolver()
.resolveThumbnail(event.senderInfo.avatarUrl,
250,
250,
ContentUrlResolver.ThumbnailMethod.SCALE)

return notifiableEvent
roomIsDirect = room.roomSummary()?.isDirect ?: false,
roomAvatarPath = session.contentUrlResolver()
.resolveThumbnail(room.roomSummary()?.avatarUrl,
250,
250,
ContentUrlResolver.ThumbnailMethod.SCALE),
senderAvatarPath = session.contentUrlResolver()
.resolveThumbnail(event.senderInfo.avatarUrl,
250,
250,
ContentUrlResolver.ThumbnailMethod.SCALE),
matrixID = session.myUserId,
soundName = null
)
}
}

private fun resolveStateRoomEvent(event: Event, session: Session): NotifiableEvent? {
private fun resolveStateRoomEvent(event: Event, session: Session, canBeReplaced: Boolean, isNoisy: Boolean): NotifiableEvent? {
val content = event.content?.toModel<RoomMemberContent>() ?: return null
val roomId = event.roomId ?: return null
val dName = event.senderId?.let { session.getRoomMember(it, roomId)?.displayName }
if (Membership.INVITE == content.membership) {
val body = noticeEventFormatter.format(event, dName, isDm = session.getRoomSummary(roomId)?.isDirect.orFalse())
val roomSummary = session.getRoomSummary(roomId)
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the room name change is from #4230 (for next time I'll be sure to better highlight the merge ordering to avoid growing diffs)

val body = noticeEventFormatter.format(event, dName, isDm = roomSummary?.isDirect.orFalse())
?: stringProvider.getString(R.string.notification_new_invitation)
return InviteNotifiableEvent(
session.myUserId,
eventId = event.eventId!!,
editedEventId = null,
canBeReplaced = canBeReplaced,
roomId = roomId,
roomName = roomSummary?.displayName,
timestamp = event.originServerTs ?: 0,
noisy = false, // will be set later
noisy = isNoisy,
title = stringProvider.getString(R.string.notification_new_invitation),
description = body.toString(),
soundName = null, // will be set later
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this comment is a lie! the soundName is in fact never updated

type = event.getClearType(),
isPushGatewayEvent = false)
type = event.getClearType()
)
} else {
Timber.e("## unsupported notifiable event for eventId [${event.eventId}]")
if (BuildConfig.LOW_PRIVACY_LOG_ENABLE) {
Expand Down
Loading