Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ignore push for a thread if it's currently visible to user #7641

Merged
merged 3 commits into from
Nov 28, 2022
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/7634.bugfix
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Push notification for thread message is now shown correctly when user observes rooms main timeline
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import im.vector.app.core.network.WifiDetector
import im.vector.app.core.pushers.model.PushData
import im.vector.app.core.resources.BuildMeta
import im.vector.app.features.notifications.NotifiableEventResolver
import im.vector.app.features.notifications.NotifiableMessageEvent
import im.vector.app.features.notifications.NotificationActionIds
import im.vector.app.features.notifications.NotificationDrawerManager
import im.vector.app.features.settings.VectorDataStore
Expand Down Expand Up @@ -142,11 +143,6 @@ class VectorPushHandler @Inject constructor(
pushData.roomId ?: return
pushData.eventId ?: return

// If the room is currently displayed, we will not show a notification, so no need to get the Event faster
if (notificationDrawerManager.shouldIgnoreMessageEventInRoom(pushData.roomId)) {
Copy link
Member

Choose a reason for hiding this comment

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

I guess we do not have the threadId in the pushData, true?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

We don't :(

return
}

if (wifiDetector.isConnectedToWifi().not()) {
Timber.tag(loggerTag.value).d("No WiFi network, do not get Event")
return
Expand All @@ -157,6 +153,13 @@ class VectorPushHandler @Inject constructor(

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

if (resolvedEvent is NotifiableMessageEvent) {
// If the room is currently displayed, we will not show a notification, so no need to get the Event faster
if (notificationDrawerManager.shouldIgnoreMessageEventInRoom(resolvedEvent)) {
return
}
}

resolvedEvent
?.also { Timber.tag(loggerTag.value).d("Fast lane: notify drawer") }
?.let {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -972,6 +972,7 @@ class TimelineFragment :
override fun onResume() {
super.onResume()
notificationDrawerManager.setCurrentRoom(timelineArgs.roomId)
notificationDrawerManager.setCurrentThread(timelineArgs.threadTimelineArgs?.rootThreadEventId)
roomDetailPendingActionStore.data?.let { handlePendingAction(it) }
roomDetailPendingActionStore.data = null
}
Expand All @@ -991,6 +992,7 @@ class TimelineFragment :
override fun onPause() {
super.onPause()
notificationDrawerManager.setCurrentRoom(null)
notificationDrawerManager.setCurrentThread(null)
}

private val emojiActivityResultLauncher = registerStartForActivityResult { activityResult ->
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,13 +30,13 @@ class NotifiableEventProcessor @Inject constructor(
private val autoAcceptInvites: AutoAcceptInvites
) {

fun process(queuedEvents: List<NotifiableEvent>, currentRoomId: String?, renderedEvents: ProcessedEvents): ProcessedEvents {
fun process(queuedEvents: List<NotifiableEvent>, currentRoomId: String?, currentThreadId: String?, renderedEvents: ProcessedEvents): ProcessedEvents {
val processedEvents = queuedEvents.map {
val type = when (it) {
is InviteNotifiableEvent -> if (autoAcceptInvites.hideInvites) REMOVE else KEEP
is NotifiableMessageEvent -> when {
shouldIgnoreMessageEventInRoom(currentRoomId, it.roomId) -> REMOVE
.also { Timber.d("notification message removed due to currently viewing the same room") }
it.shouldIgnoreMessageEventInRoom(currentRoomId, currentThreadId) -> REMOVE
.also { Timber.d("notification message removed due to currently viewing the same room or thread") }
outdatedDetector.isMessageOutdated(it) -> REMOVE
.also { Timber.d("notification message removed due to being read") }
else -> KEEP
Expand All @@ -55,8 +55,4 @@ class NotifiableEventProcessor @Inject constructor(

return removedEventsDiff + processedEvents
}

private fun shouldIgnoreMessageEventInRoom(currentRoomId: String?, roomId: String?): Boolean {
return currentRoomId != null && roomId == currentRoomId
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ import org.matrix.android.sdk.api.session.crypto.MXCryptoError
import org.matrix.android.sdk.api.session.crypto.model.OlmDecryptionResult
import org.matrix.android.sdk.api.session.events.model.Event
import org.matrix.android.sdk.api.session.events.model.EventType
import org.matrix.android.sdk.api.session.events.model.getRootThreadEventId
import org.matrix.android.sdk.api.session.events.model.isEdition
import org.matrix.android.sdk.api.session.events.model.isImageMessage
import org.matrix.android.sdk.api.session.events.model.supportsNotification
Expand Down Expand Up @@ -133,7 +134,7 @@ class NotifiableEventResolver @Inject constructor(
}
}

private suspend fun resolveMessageEvent(event: TimelineEvent, session: Session, canBeReplaced: Boolean, isNoisy: Boolean): NotifiableEvent? {
private suspend fun resolveMessageEvent(event: TimelineEvent, session: Session, canBeReplaced: Boolean, isNoisy: Boolean): NotifiableMessageEvent? {
// 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 @@ -155,6 +156,7 @@ class NotifiableEventResolver @Inject constructor(
body = body.toString(),
imageUriString = event.fetchImageIfPresent(session)?.toString(),
roomId = event.root.roomId!!,
threadId = event.root.getRootThreadEventId(),
roomName = roomName,
matrixID = session.myUserId
)
Expand All @@ -178,6 +180,7 @@ class NotifiableEventResolver @Inject constructor(
body = body,
imageUriString = event.fetchImageIfPresent(session)?.toString(),
roomId = event.root.roomId!!,
threadId = event.root.getRootThreadEventId(),
roomName = roomName,
roomIsDirect = room.roomSummary()?.isDirect ?: false,
roomAvatarPath = session.contentUrlResolver()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ data class NotifiableMessageEvent(
// NotSerializableException when persisting this to storage
val imageUriString: String?,
val roomId: String,
val threadId: String?,
val roomName: String?,
val roomIsDirect: Boolean = false,
val roomAvatarPath: String? = null,
Expand All @@ -51,3 +52,10 @@ data class NotifiableMessageEvent(
val imageUri: Uri?
get() = imageUriString?.let { Uri.parse(it) }
}

fun NotifiableMessageEvent.shouldIgnoreMessageEventInRoom(currentRoomId: String?, currentThreadId: String?): Boolean {
return when (currentRoomId) {
null -> false
else -> roomId == currentRoomId && threadId == currentThreadId
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,7 @@ class NotificationBroadcastReceiver : BroadcastReceiver() {
body = message,
imageUriString = null,
roomId = room.roomId,
threadId = null, // needs to be changed: https://github.com/vector-im/element-android/issues/7475
roomName = room.roomSummary()?.displayName ?: room.roomId,
roomIsDirect = room.roomSummary()?.isDirect == true,
outGoingMessage = true,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ class NotificationDrawerManager @Inject constructor(
private val notificationState by lazy { createInitialNotificationState() }
private val avatarSize = context.resources.getDimensionPixelSize(R.dimen.profile_avatar_size)
private var currentRoomId: String? = null
private var currentThreadId: String? = null
private val firstThrottler = FirstThrottler(200)

private var useCompleteNotificationFormat = vectorPreferences.useCompleteNotificationFormat()
Expand Down Expand Up @@ -123,6 +124,22 @@ class NotificationDrawerManager @Inject constructor(
}
}

/**
* Should be called when the application is currently opened and showing timeline for the given threadId.
* Used to ignore events related to that thread (no need to display notification) and clean any existing notification on this room.
*/
fun setCurrentThread(threadId: String?) {
Copy link
Member

Choose a reason for hiding this comment

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

Maybe merge with the method setCurrentRoom, just adding the parameter threadId: String? to it?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

user can switch between different threads in the same rooms, so i think it's better to separate changing room and changing thread

Copy link
Member

Choose a reason for hiding this comment

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

OK

updateEvents {
val hasChanged = threadId != currentThreadId
currentThreadId = threadId
currentRoomId?.let { roomId ->
if (hasChanged && threadId != null) {
it.clearMessagesForThread(roomId, threadId)
}
}
}
}

fun notificationStyleChanged() {
updateEvents {
val newSettings = vectorPreferences.useCompleteNotificationFormat()
Expand Down Expand Up @@ -164,7 +181,7 @@ class NotificationDrawerManager @Inject constructor(
private fun refreshNotificationDrawerBg() {
Timber.v("refreshNotificationDrawerBg()")
val eventsToRender = notificationState.updateQueuedEvents(this) { queuedEvents, renderedEvents ->
notifiableEventProcessor.process(queuedEvents.rawEvents(), currentRoomId, renderedEvents).also {
notifiableEventProcessor.process(queuedEvents.rawEvents(), currentRoomId, currentThreadId, renderedEvents).also {
queuedEvents.clearAndAdd(it.onlyKeptEvents())
}
}
Expand Down Expand Up @@ -198,8 +215,8 @@ class NotificationDrawerManager @Inject constructor(
notificationRenderer.render(session.myUserId, myUserDisplayName, myUserAvatarUrl, useCompleteNotificationFormat, eventsToRender)
}

fun shouldIgnoreMessageEventInRoom(roomId: String?): Boolean {
return currentRoomId != null && roomId == currentRoomId
fun shouldIgnoreMessageEventInRoom(resolvedEvent: NotifiableMessageEvent): Boolean {
return resolvedEvent.shouldIgnoreMessageEventInRoom(currentRoomId, currentThreadId)
}

companion object {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -122,15 +122,20 @@ data class NotificationEventQueue(
}

fun clearMemberShipNotificationForRoom(roomId: String) {
Timber.v("clearMemberShipOfRoom $roomId")
Timber.d("clearMemberShipOfRoom $roomId")
queue.removeAll { it is InviteNotifiableEvent && it.roomId == roomId }
}

fun clearMessagesForRoom(roomId: String) {
Timber.v("clearMessageEventOfRoom $roomId")
Timber.d("clearMessageEventOfRoom $roomId")
queue.removeAll { it is NotifiableMessageEvent && it.roomId == roomId }
}

fun clearMessagesForThread(roomId: String, threadId: String) {
Timber.d("clearMessageEventOfThread $roomId, $threadId")
queue.removeAll { it is NotifiableMessageEvent && it.roomId == roomId && it.threadId == threadId }
}

fun rawEvents(): List<NotifiableEvent> = queue
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import org.junit.Test
import org.matrix.android.sdk.api.session.events.model.EventType

private val NOT_VIEWING_A_ROOM: String? = null
private val NOT_VIEWING_A_THREAD: String? = null

class NotifiableEventProcessorTest {

Expand All @@ -42,7 +43,7 @@ class NotifiableEventProcessorTest {
aSimpleNotifiableEvent(eventId = "event-2")
)

val result = eventProcessor.process(events, currentRoomId = NOT_VIEWING_A_ROOM, renderedEvents = emptyList())
val result = eventProcessor.process(events, currentRoomId = NOT_VIEWING_A_ROOM, currentThreadId = NOT_VIEWING_A_THREAD, renderedEvents = emptyList())

result shouldBeEqualTo listOfProcessedEvents(
Type.KEEP to events[0],
Expand All @@ -54,7 +55,7 @@ class NotifiableEventProcessorTest {
fun `given redacted simple event when processing then remove redaction event`() {
val events = listOf(aSimpleNotifiableEvent(eventId = "event-1", type = EventType.REDACTION))

val result = eventProcessor.process(events, currentRoomId = NOT_VIEWING_A_ROOM, renderedEvents = emptyList())
val result = eventProcessor.process(events, currentRoomId = NOT_VIEWING_A_ROOM, currentThreadId = NOT_VIEWING_A_THREAD, renderedEvents = emptyList())

result shouldBeEqualTo listOfProcessedEvents(
Type.REMOVE to events[0]
Expand All @@ -69,7 +70,7 @@ class NotifiableEventProcessorTest {
anInviteNotifiableEvent(roomId = "room-2")
)

val result = eventProcessor.process(events, currentRoomId = NOT_VIEWING_A_ROOM, renderedEvents = emptyList())
val result = eventProcessor.process(events, currentRoomId = NOT_VIEWING_A_ROOM, currentThreadId = NOT_VIEWING_A_THREAD, renderedEvents = emptyList())

result shouldBeEqualTo listOfProcessedEvents(
Type.REMOVE to events[0],
Expand All @@ -85,7 +86,7 @@ class NotifiableEventProcessorTest {
anInviteNotifiableEvent(roomId = "room-2")
)

val result = eventProcessor.process(events, currentRoomId = NOT_VIEWING_A_ROOM, renderedEvents = emptyList())
val result = eventProcessor.process(events, currentRoomId = NOT_VIEWING_A_ROOM, currentThreadId = NOT_VIEWING_A_THREAD, renderedEvents = emptyList())

result shouldBeEqualTo listOfProcessedEvents(
Type.KEEP to events[0],
Expand All @@ -98,7 +99,7 @@ class NotifiableEventProcessorTest {
val events = listOf(aNotifiableMessageEvent(eventId = "event-1", roomId = "room-1"))
outdatedDetector.givenEventIsOutOfDate(events[0])

val result = eventProcessor.process(events, currentRoomId = NOT_VIEWING_A_ROOM, renderedEvents = emptyList())
val result = eventProcessor.process(events, currentRoomId = NOT_VIEWING_A_ROOM, currentThreadId = NOT_VIEWING_A_THREAD, renderedEvents = emptyList())

result shouldBeEqualTo listOfProcessedEvents(
Type.REMOVE to events[0],
Expand All @@ -110,24 +111,59 @@ class NotifiableEventProcessorTest {
val events = listOf(aNotifiableMessageEvent(eventId = "event-1", roomId = "room-1"))
outdatedDetector.givenEventIsInDate(events[0])

val result = eventProcessor.process(events, currentRoomId = NOT_VIEWING_A_ROOM, renderedEvents = emptyList())
val result = eventProcessor.process(events, currentRoomId = NOT_VIEWING_A_ROOM, currentThreadId = NOT_VIEWING_A_THREAD, renderedEvents = emptyList())

result shouldBeEqualTo listOfProcessedEvents(
Type.KEEP to events[0],
)
}

@Test
fun `given viewing the same room as message event when processing then removes message`() {
val events = listOf(aNotifiableMessageEvent(eventId = "event-1", roomId = "room-1"))
fun `given viewing the same room main timeline when processing main timeline message event then removes message`() {
val events = listOf(aNotifiableMessageEvent(eventId = "event-1", roomId = "room-1", threadId = null))

val result = eventProcessor.process(events, currentRoomId = "room-1", currentThreadId = null, renderedEvents = emptyList())

result shouldBeEqualTo listOfProcessedEvents(
Type.REMOVE to events[0],
)
}

val result = eventProcessor.process(events, currentRoomId = "room-1", renderedEvents = emptyList())
@Test
fun `given viewing the same thread timeline when processing thread message event then removes message`() {
val events = listOf(aNotifiableMessageEvent(eventId = "event-1", roomId = "room-1", threadId = "thread-1"))

val result = eventProcessor.process(events, currentRoomId = "room-1", currentThreadId = "thread-1", renderedEvents = emptyList())

result shouldBeEqualTo listOfProcessedEvents(
Type.REMOVE to events[0],
)
}

@Test
fun `given viewing main timeline of the same room when processing thread timeline message event then keep message`() {
val events = listOf(aNotifiableMessageEvent(eventId = "event-1", roomId = "room-1", threadId = "thread-1"))
outdatedDetector.givenEventIsInDate(events[0])

val result = eventProcessor.process(events, currentRoomId = "room-1", currentThreadId = null, renderedEvents = emptyList())

result shouldBeEqualTo listOfProcessedEvents(
Type.KEEP to events[0],
)
}

@Test
fun `given viewing thread timeline of the same room when processing main timeline message event then keep message`() {
val events = listOf(aNotifiableMessageEvent(eventId = "event-1", roomId = "room-1"))
outdatedDetector.givenEventIsInDate(events[0])

val result = eventProcessor.process(events, currentRoomId = "room-1", currentThreadId = "thread-1", renderedEvents = emptyList())

result shouldBeEqualTo listOfProcessedEvents(
Type.KEEP to events[0],
)
}

@Test
fun `given events are different to rendered events when processing then removes difference`() {
val events = listOf(aSimpleNotifiableEvent(eventId = "event-1"))
Expand All @@ -136,7 +172,7 @@ class NotifiableEventProcessorTest {
ProcessedEvent(Type.KEEP, anInviteNotifiableEvent(roomId = "event-2"))
)

val result = eventProcessor.process(events, currentRoomId = NOT_VIEWING_A_ROOM, renderedEvents = renderedEvents)
val result = eventProcessor.process(events, currentRoomId = NOT_VIEWING_A_ROOM, currentThreadId = NOT_VIEWING_A_THREAD, renderedEvents = renderedEvents)

result shouldBeEqualTo listOfProcessedEvents(
Type.REMOVE to renderedEvents[1].event,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ fun anInviteNotifiableEvent(
fun aNotifiableMessageEvent(
eventId: String = "a-message-event-id",
roomId: String = "a-message-room-id",
threadId: String? = null,
isRedacted: Boolean = false
) = NotifiableMessageEvent(
eventId = eventId,
Expand All @@ -73,6 +74,7 @@ fun aNotifiableMessageEvent(
senderId = "sending-id",
body = "message-body",
roomId = roomId,
threadId = threadId,
roomName = "room-name",
roomIsDirect = false,
canBeReplaced = false,
Expand Down