Skip to content

Implements MSC3773 (Thread Notifications) #7424

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

Merged
merged 16 commits into from
Oct 26, 2022
Merged
Show file tree
Hide file tree
Changes from 11 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/7424.misc
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Gets thread notifications from sync response
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,11 @@ data class HomeServerCapabilities(
* True if the home server supports login via qr code, false otherwise.
*/
val canLoginWithQrCode: Boolean = false,

/**
* True if the home server supports threaded read receipts and unread notifications.
*/
val canUseThreadReadReceiptsAndNotifications: Boolean = false,
) {

enum class RoomCapabilitySupport {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,14 @@ data class RoomSummary(
* Number of unread and highlighted message in this room.
*/
val highlightCount: Int = 0,
/**
* Number of threads with unread messages in this room
*/
val threadNotificationCount: Int = 0,
/**
* Number of threads with highlighted messages in this room
*/
val threadHighlightCount: Int = 0,
/**
* True if this room has unread messages.
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,11 @@ data class RoomSync(
*/
@Json(name = "unread_notifications") val unreadNotifications: RoomSyncUnreadNotifications? = null,

/**
* The count of threads with unread notifications (not the total # of notifications in all threads)
*/
@Json(name = "unread_thread_notifications") val unreadThreadNotifications: Map<String, RoomSyncUnreadThreadNotifications>? = null,

/**
* The room summary.
*/
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
/*
* Copyright 2022 The Matrix.org Foundation C.I.C.
*
* 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 org.matrix.android.sdk.api.session.sync.model

import com.squareup.moshi.Json
import com.squareup.moshi.JsonClass

@JsonClass(generateAdapter = true)
data class RoomSyncUnreadThreadNotifications(
/**
* The number of threads with unread messages that match the push notification rules.
*/
@Json(name = "notification_count") val notificationCount: Int? = null,

/**
* The number of threads with highlighted unread messages (subset of notifications).
*/
@Json(name = "highlight_count") val highlightCount: Int? = null
)
Original file line number Diff line number Diff line change
Expand Up @@ -60,5 +60,6 @@ internal data class HomeServerVersion(
val r0_6_0 = HomeServerVersion(major = 0, minor = 6, patch = 0)
val r0_6_1 = HomeServerVersion(major = 0, minor = 6, patch = 1)
val v1_3_0 = HomeServerVersion(major = 1, minor = 3, patch = 0)
val v1_4_0 = HomeServerVersion(major = 1, minor = 4, patch = 0)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,8 @@ private const val FEATURE_SEPARATE_ADD_AND_BIND = "m.separate_add_and_bind"
private const val FEATURE_THREADS_MSC3440 = "org.matrix.msc3440"
private const val FEATURE_THREADS_MSC3440_STABLE = "org.matrix.msc3440.stable"
private const val FEATURE_QR_CODE_LOGIN = "org.matrix.msc3882"
private const val FEATURE_THREADS_MSC3771 = "org.matrix.msc3771"
private const val FEATURE_THREADS_MSC3773 = "org.matrix.msc3773"

/**
* Return true if the SDK supports this homeserver version.
Expand All @@ -79,6 +81,15 @@ internal fun Versions.doesServerSupportThreads(): Boolean {
return unstableFeatures?.get(FEATURE_THREADS_MSC3440_STABLE) ?: false
}

/**
* Indicate if the homeserver support MSC3771 and MSC3773 for threaded read receipts and unread notifications.
*/
internal fun Versions.doesServerSupportThreadUnreadNotifications(): Boolean {
val msc3771 = unstableFeatures?.get(FEATURE_THREADS_MSC3771) ?: false
val msc3773 = unstableFeatures?.get(FEATURE_THREADS_MSC3773) ?: false
return getMaxVersion() >= HomeServerVersion.v1_4_0 || (msc3771 && msc3773)
}

internal fun Versions.doesServerSupportQrCodeLogin(): Boolean {
return unstableFeatures?.get(FEATURE_QR_CODE_LOGIN) ?: false
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,8 @@ import org.matrix.android.sdk.internal.database.migration.MigrateSessionTo036
import org.matrix.android.sdk.internal.database.migration.MigrateSessionTo037
import org.matrix.android.sdk.internal.database.migration.MigrateSessionTo038
import org.matrix.android.sdk.internal.database.migration.MigrateSessionTo039
import org.matrix.android.sdk.internal.database.migration.MigrateSessionTo040
import org.matrix.android.sdk.internal.database.migration.MigrateSessionTo041
import org.matrix.android.sdk.internal.util.Normalizer
import org.matrix.android.sdk.internal.util.database.MatrixRealmMigration
import javax.inject.Inject
Expand All @@ -64,7 +66,7 @@ internal class RealmSessionStoreMigration @Inject constructor(
private val normalizer: Normalizer
) : MatrixRealmMigration(
dbName = "Session",
schemaVersion = 39L,
schemaVersion = 41L,
) {
/**
* Forces all RealmSessionStoreMigration instances to be equal.
Expand Down Expand Up @@ -113,5 +115,7 @@ internal class RealmSessionStoreMigration @Inject constructor(
if (oldVersion < 37) MigrateSessionTo037(realm).perform()
if (oldVersion < 38) MigrateSessionTo038(realm).perform()
if (oldVersion < 39) MigrateSessionTo039(realm).perform()
if (oldVersion < 40) MigrateSessionTo040(realm).perform()
if (oldVersion < 41) MigrateSessionTo041(realm).perform()
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ internal object HomeServerCapabilitiesMapper {
canUseThreading = entity.canUseThreading,
canControlLogoutDevices = entity.canControlLogoutDevices,
canLoginWithQrCode = entity.canLoginWithQrCode,
canUseThreadReadReceiptsAndNotifications = entity.canUseThreadReadReceiptsAndNotifications
)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,8 @@ internal class RoomSummaryMapper @Inject constructor(
otherMemberIds = roomSummaryEntity.otherMemberIds.toList(),
highlightCount = roomSummaryEntity.highlightCount,
notificationCount = roomSummaryEntity.notificationCount,
threadHighlightCount = roomSummaryEntity.threadHighlightCount,
threadNotificationCount = roomSummaryEntity.threadNotificationCount,
hasUnreadMessages = roomSummaryEntity.hasUnreadMessages,
tags = tags,
typingUsers = typingUsers,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
/*
* Copyright (c) 2022 The Matrix.org Foundation C.I.C.
*
* 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 org.matrix.android.sdk.internal.database.migration

import io.realm.DynamicRealm
import org.matrix.android.sdk.internal.database.model.HomeServerCapabilitiesEntityFields
import org.matrix.android.sdk.internal.extensions.forceRefreshOfHomeServerCapabilities
import org.matrix.android.sdk.internal.util.database.RealmMigrator

internal class MigrateSessionTo040(realm: DynamicRealm) : RealmMigrator(realm, 40) {

override fun doMigrate(realm: DynamicRealm) {
realm.schema.get("HomeServerCapabilitiesEntity")
?.addField(HomeServerCapabilitiesEntityFields.CAN_USE_THREAD_READ_RECEIPTS_AND_NOTIFICATIONS, Boolean::class.java)
?.transform { obj ->
obj.set(HomeServerCapabilitiesEntityFields.CAN_USE_THREAD_READ_RECEIPTS_AND_NOTIFICATIONS, false)
}
?.forceRefreshOfHomeServerCapabilities()
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
/*
* Copyright (c) 2022 The Matrix.org Foundation C.I.C.
*
* 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 org.matrix.android.sdk.internal.database.migration

import io.realm.DynamicRealm
import org.matrix.android.sdk.internal.database.model.RoomSummaryEntityFields
import org.matrix.android.sdk.internal.util.database.RealmMigrator

internal class MigrateSessionTo041(realm: DynamicRealm) : RealmMigrator(realm, 41) {

override fun doMigrate(realm: DynamicRealm) {
realm.schema.get("RoomSummaryEntity")
?.addField(RoomSummaryEntityFields.THREAD_HIGHLIGHT_COUNT, Int::class.java)
?.addField(RoomSummaryEntityFields.THREAD_NOTIFICATION_COUNT, Int::class.java)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ internal open class HomeServerCapabilitiesEntity(
var canUseThreading: Boolean = false,
var canControlLogoutDevices: Boolean = false,
var canLoginWithQrCode: Boolean = false,
var canUseThreadReadReceiptsAndNotifications: Boolean = false,
) : RealmObject() {

companion object
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,16 @@ internal open class RoomSummaryEntity(
if (value != field) field = value
}

var threadNotificationCount: Int = 0
set(value) {
if (value != field) field = value
}

var threadHighlightCount: Int = 0
set(value) {
if (value != field) field = value
}

var readMarkerId: String? = null
set(value) {
if (value != field) field = value
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -110,8 +110,7 @@ internal fun RealmQuery<TimelineEventEntity>.filterEvents(filters: TimelineEvent
endGroup()
}
if (filters.filterUseless) {
not()
.equalTo(TimelineEventEntityFields.ROOT.IS_USELESS, true)
not().equalTo(TimelineEventEntityFields.ROOT.IS_USELESS, true)
}
if (filters.filterEdits) {
not().like(TimelineEventEntityFields.ROOT.CONTENT, TimelineEventFilter.Content.EDIT)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ internal object FilterFactory {
limit = numberOfEvents,
// senders = listOf(userId),
// relationSenders = userId?.let { listOf(it) },
relationTypes = listOf(RelationType.THREAD)
relationTypes = listOf(RelationType.THREAD),
)
}

Expand All @@ -37,7 +37,7 @@ internal object FilterFactory {
limit = numberOfEvents,
containsUrl = true,
types = listOf(EventType.MESSAGE),
lazyLoadMembers = true
lazyLoadMembers = true,
)
}

Expand All @@ -55,30 +55,23 @@ internal object FilterFactory {
}

fun createDefaultRoomFilter(): RoomEventFilter {
return RoomEventFilter(
lazyLoadMembers = true
)
return RoomEventFilter(lazyLoadMembers = true)
}

fun createElementRoomFilter(): RoomEventFilter {
return RoomEventFilter(
lazyLoadMembers = true
lazyLoadMembers = true,
// TODO Enable this for optimization
// types = (listOfSupportedEventTypes + listOfSupportedStateEventTypes).toMutableList()
)
}

private fun createElementTimelineFilter(): RoomEventFilter? {
return null // RoomEventFilter().apply {
// TODO Enable this for optimization
// types = listOfSupportedEventTypes.toMutableList()
// }
return RoomEventFilter(enableUnreadThreadNotifications = true)
}

private fun createElementStateFilter(): RoomEventFilter {
return RoomEventFilter(
lazyLoadMembers = true
)
return RoomEventFilter(lazyLoadMembers = true)
}

// Get only managed types by Element
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ package org.matrix.android.sdk.internal.session.filter

import com.squareup.moshi.Json
import com.squareup.moshi.JsonClass
import org.matrix.android.sdk.api.session.sync.model.RoomSync
import org.matrix.android.sdk.internal.di.MoshiProvider

/**
Expand Down Expand Up @@ -76,7 +77,11 @@ internal data class RoomEventFilter(
/**
* If true, enables lazy-loading of membership events. See Lazy-loading room members for more information. Defaults to false.
*/
@Json(name = "lazy_load_members") val lazyLoadMembers: Boolean? = null
@Json(name = "lazy_load_members") val lazyLoadMembers: Boolean? = null,
/**
* If true, this will opt-in for the server to return unread threads notifications in [RoomSync]
*/
@Json(name = "unread_thread_notifications") val enableUnreadThreadNotifications: Boolean? = null,
) {

fun toJSONString(): String {
Expand All @@ -92,6 +97,7 @@ internal data class RoomEventFilter(
rooms != null ||
notRooms != null ||
containsUrl != null ||
lazyLoadMembers != null)
lazyLoadMembers != null ||
enableUnreadThreadNotifications != null)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import org.matrix.android.sdk.api.session.homeserver.HomeServerCapabilities
import org.matrix.android.sdk.internal.auth.version.Versions
import org.matrix.android.sdk.internal.auth.version.doesServerSupportLogoutDevices
import org.matrix.android.sdk.internal.auth.version.doesServerSupportQrCodeLogin
import org.matrix.android.sdk.internal.auth.version.doesServerSupportThreadUnreadNotifications
import org.matrix.android.sdk.internal.auth.version.doesServerSupportThreads
import org.matrix.android.sdk.internal.auth.version.isLoginAndRegistrationSupportedBySdk
import org.matrix.android.sdk.internal.database.model.HomeServerCapabilitiesEntity
Expand Down Expand Up @@ -144,6 +145,8 @@ internal class DefaultGetHomeServerCapabilitiesTask @Inject constructor(
homeServerCapabilitiesEntity.canControlLogoutDevices = getVersionResult.doesServerSupportLogoutDevices()
homeServerCapabilitiesEntity.canUseThreading = /* capabilities?.threads?.enabled.orFalse() || */
getVersionResult.doesServerSupportThreads()
homeServerCapabilitiesEntity.canUseThreadReadReceiptsAndNotifications =
getVersionResult.doesServerSupportThreadUnreadNotifications()
homeServerCapabilitiesEntity.canLoginWithQrCode = getVersionResult.doesServerSupportQrCodeLogin()
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ import org.matrix.android.sdk.api.session.room.powerlevels.PowerLevelsHelper
import org.matrix.android.sdk.api.session.room.send.SendState
import org.matrix.android.sdk.api.session.sync.model.RoomSyncSummary
import org.matrix.android.sdk.api.session.sync.model.RoomSyncUnreadNotifications
import org.matrix.android.sdk.api.session.sync.model.RoomSyncUnreadThreadNotifications
import org.matrix.android.sdk.internal.crypto.EventDecryptor
import org.matrix.android.sdk.internal.crypto.crosssigning.DefaultCrossSigningService
import org.matrix.android.sdk.internal.database.mapper.ContentMapper
Expand Down Expand Up @@ -91,6 +92,7 @@ internal class RoomSummaryUpdater @Inject constructor(
membership: Membership? = null,
roomSummary: RoomSyncSummary? = null,
unreadNotifications: RoomSyncUnreadNotifications? = null,
unreadThreadNotifications: Map<String, RoomSyncUnreadThreadNotifications>? = null,
updateMembers: Boolean = false,
inviterId: String? = null,
aggregator: SyncResponsePostTreatmentAggregator? = null
Expand All @@ -111,6 +113,14 @@ internal class RoomSummaryUpdater @Inject constructor(
roomSummaryEntity.highlightCount = unreadNotifications?.highlightCount ?: 0
roomSummaryEntity.notificationCount = unreadNotifications?.notificationCount ?: 0

roomSummaryEntity.threadHighlightCount = unreadThreadNotifications
?.count { (it.value.highlightCount ?: 0) > 0 }
?: 0

roomSummaryEntity.threadNotificationCount = unreadThreadNotifications
?.count { (it.value.notificationCount ?: 0) > 0 }
?: 0

if (membership != null) {
roomSummaryEntity.membership = membership
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -140,7 +140,7 @@ internal class DefaultSyncTask @Inject constructor(
executeRequest(globalErrorReceiver) {
syncAPI.sync(
params = requestParams,
readTimeOut = readTimeOut
readTimeOut = readTimeOut,
)
}
}
Expand Down Expand Up @@ -178,7 +178,7 @@ internal class DefaultSyncTask @Inject constructor(
syncRequestStateTracker.setSyncRequestState(
SyncRequestState.IncrementalSyncParsing(
rooms = nbRooms,
toDevice = nbToDevice
toDevice = nbToDevice,
)
)
syncResponseHandler.handleResponse(syncResponse, token, null)
Expand Down
Loading