Fix issues on Notification Event resolver

This commit is contained in:
Valere 2019-06-22 17:08:45 +02:00 committed by Benoit Marty
parent 6743dc6273
commit 4e6b34b9d1
11 changed files with 151 additions and 131 deletions

View File

@ -23,7 +23,7 @@ import io.reactivex.Observable
class RxRoom(private val room: Room) {

fun liveRoomSummary(): Observable<RoomSummary> {
return room.roomSummary.asObservable()
return room.liveRoomSummary.asObservable()
}

fun liveRoomMemberIds(): Observable<List<String>> {

View File

@ -47,6 +47,8 @@ interface Room :
* A live [RoomSummary] associated with the room
* You can observe this summary to get dynamic data from this room.
*/
val roomSummary: LiveData<RoomSummary>
val liveRoomSummary: LiveData<RoomSummary>

val roomSummary: RoomSummary?

}

View File

@ -39,4 +39,6 @@ data class CallInviteContent(
}
}


fun isVideo(): Boolean = offer.sdp.contains(Offer.SDP_VIDEO)
}

View File

@ -52,7 +52,7 @@ internal class DefaultRoom(
RelationService by relationService,
MembershipService by roomMembersService {

override val roomSummary: LiveData<RoomSummary> by lazy {
override val liveRoomSummary: LiveData<RoomSummary> by lazy {
val liveRealmData = RealmLiveData<RoomSummaryEntity>(monarchy.realmConfiguration) { realm ->
RoomSummaryEntity.where(realm, roomId).isNotEmpty(RoomSummaryEntityFields.DISPLAY_NAME)
}
@ -68,6 +68,15 @@ internal class DefaultRoom(
}
}

override val roomSummary: RoomSummary?
get() {
var sum: RoomSummaryEntity? = null
monarchy.runTransactionSync {
sum = RoomSummaryEntity.where(it, roomId).isNotEmpty(RoomSummaryEntityFields.DISPLAY_NAME).findFirst()
}
return sum?.asDomain()
}

override fun isEncrypted(): Boolean {
return cryptoService.isRoomEncrypted(roomId)
}

View File

@ -175,7 +175,10 @@ class PushrulesConditionTest {
return _numberOfJoinedMembers
}

override val roomSummary: LiveData<RoomSummary>
override val liveRoomSummary: LiveData<RoomSummary>
get() = TODO("not implemented") //To change initializer of created properties use File | Settings | File Templates.

override val roomSummary: RoomSummary?
get() = TODO("not implemented") //To change initializer of created properties use File | Settings | File Templates.

override fun createTimeline(eventId: String?, allowedTypes: List<String>?): Timeline {

View File

@ -49,10 +49,7 @@ class VectorFirebaseMessagingService : FirebaseMessagingService() {
private val notificationDrawerManager by inject<NotificationDrawerManager>()
private val pusherManager by inject<PushersManager>()

private val notifiableEventResolver by lazy {
NotifiableEventResolver(this)
}

private val notifiableEventResolver by inject<NotifiableEventResolver>()
// UI handler
private val mUIHandler by lazy {
Handler(Looper.getMainLooper())
@ -69,8 +66,8 @@ class VectorFirebaseMessagingService : FirebaseMessagingService() {
return
}
if (BuildConfig.LOW_PRIVACY_LOG_ENABLE) {
Timber.i("## onMessageReceived()" + message.data.toString())
Timber.i("## onMessageReceived() from FCM with priority " + message.priority)
Timber.i("## onMessageReceived() %s", message.data.toString())
Timber.i("## onMessageReceived() from FCM with priority %s", message.priority)
}
mUIHandler.post {
if (ProcessLifecycleOwner.get().lifecycle.currentState.isAtLeast(Lifecycle.State.STARTED)) {
@ -168,7 +165,7 @@ class VectorFirebaseMessagingService : FirebaseMessagingService() {
val room = session.getRoom(roomId) ?: return false
return room.getTimeLineEvent(eventId) != null
} catch (e: Exception) {
Timber.e(e, "## isEventAlreadyKnown() : failed to check if the event was already defined " + e.message)
Timber.e(e, "## isEventAlreadyKnown() : failed to check if the event was already defined")
}

}
@ -209,44 +206,39 @@ class VectorFirebaseMessagingService : FirebaseMessagingService() {
return
} else {

val event = parseEvent(data)
if (event?.roomId == null) {
//unsupported event
Timber.e("Received an event with no room id")
return
val event = parseEvent(data) ?: return

val notifiableEvent = notifiableEventResolver.resolveEvent(event, session)

if (notifiableEvent == null) {
Timber.e("Unsupported notifiable event ${eventId}")
if (BuildConfig.LOW_PRIVACY_LOG_ENABLE) {
Timber.e("--> ${event}")
}
} else {

var notifiableEvent = notifiableEventResolver.resolveEvent(event, session)

if (notifiableEvent == null) {
Timber.e("Unsupported notifiable event ${eventId}")
if (BuildConfig.LOW_PRIVACY_LOG_ENABLE) {
Timber.e("--> ${event}")
if (notifiableEvent is NotifiableMessageEvent) {
if (TextUtils.isEmpty(notifiableEvent.senderName)) {
notifiableEvent.senderName = data["sender_display_name"]
?: data["sender"] ?: ""
}
} else {


if (notifiableEvent is NotifiableMessageEvent) {
if (TextUtils.isEmpty(notifiableEvent.senderName)) {
notifiableEvent.senderName = data["sender_display_name"]
?: data["sender"] ?: ""
}
if (TextUtils.isEmpty(notifiableEvent.roomName)) {
notifiableEvent.roomName = findRoomNameBestEffort(data, session) ?: ""
}
if (TextUtils.isEmpty(notifiableEvent.roomName)) {
notifiableEvent.roomName = findRoomNameBestEffort(data, session) ?: ""
}

notifiableEvent.isPushGatewayEvent = true
notifiableEvent.matrixID = session.sessionParams.credentials.userId
notificationDrawerManager.onNotifiableEventReceived(notifiableEvent)
notificationDrawerManager.refreshNotificationDrawer()
}

notifiableEvent.isPushGatewayEvent = true
notifiableEvent.matrixID = session.sessionParams.credentials.userId
notificationDrawerManager.onNotifiableEventReceived(notifiableEvent)
notificationDrawerManager.refreshNotificationDrawer()
}
}

}

private fun findRoomNameBestEffort(data: Map<String, String>, session: Session?): String? {
var roomName: String? = data["room_name"]
val roomName: String? = data["room_name"]
val roomId = data["room_id"]
if (null == roomName && null != roomId) {
// Try to get the room name from our store
@ -281,7 +273,7 @@ class VectorFirebaseMessagingService : FirebaseMessagingService() {
// TODO content = data.getValue("content"),
originServerTs = System.currentTimeMillis())
} catch (e: Exception) {
Timber.e(e, "buildEvent fails " + e.localizedMessage)
Timber.e(e, "buildEvent fails ")
}

return null

View File

@ -90,7 +90,7 @@ class AppModule(private val context: Context) {
}

single {
OutdatedEventDetector(context)
OutdatedEventDetector()
}

single {
@ -98,7 +98,7 @@ class AppModule(private val context: Context) {
}

single {
NotifiableEventResolver(context)
NotifiableEventResolver(get(), get())
}

factory {

View File

@ -46,6 +46,22 @@ class NoticeEventFormatter(private val stringProvider: StringProvider) {
}
}

fun format(event: Event, senderName: String?): CharSequence? {
return when (val type = event.getClearType()) {
EventType.STATE_ROOM_NAME -> formatRoomNameEvent(event, senderName)
EventType.STATE_ROOM_TOPIC -> formatRoomTopicEvent(event, senderName)
EventType.STATE_ROOM_MEMBER -> formatRoomMemberEvent(event, senderName)
EventType.STATE_HISTORY_VISIBILITY -> formatRoomHistoryVisibilityEvent(event, senderName)
EventType.CALL_INVITE,
EventType.CALL_HANGUP,
EventType.CALL_ANSWER -> formatCallEvent(event, senderName)
else -> {
Timber.v("Type $type not handled by this formatter")
null
}
}
}

private fun formatRoomNameEvent(event: Event, senderName: String?): CharSequence? {
val content = event.getClearContent().toModel<RoomNameContent>() ?: return null
return if (!TextUtils.isEmpty(content.name)) {

View File

@ -15,16 +15,19 @@
*/
package im.vector.riotredesign.features.notifications

import android.content.Context
import im.vector.matrix.android.api.pushrules.rest.PushRule
import androidx.core.app.NotificationCompat
import im.vector.matrix.android.api.session.Session
import im.vector.matrix.android.api.session.events.model.Event
import im.vector.matrix.android.api.session.events.model.EventType
import im.vector.matrix.android.api.session.events.model.toContent
import im.vector.matrix.android.api.session.events.model.toModel
import im.vector.matrix.android.api.session.room.model.Membership
import im.vector.matrix.android.api.session.room.model.RoomMember
import im.vector.matrix.android.api.session.room.model.message.MessageContent
import im.vector.matrix.android.api.session.room.timeline.TimelineEvent
import im.vector.riotredesign.BuildConfig
import im.vector.riotredesign.R
import im.vector.riotredesign.core.preference.BingRule
import im.vector.riotredesign.core.resources.StringProvider
import im.vector.riotredesign.features.home.room.detail.timeline.format.NoticeEventFormatter
import timber.log.Timber

// TODO Remove
@ -39,46 +42,44 @@ class RoomState {
* The NotifiableEventResolver is the only aware of session/store, the NotificationDrawerManager has no knowledge of that,
* this pattern allow decoupling between the object responsible of displaying notifications and the matrix sdk.
*/
class NotifiableEventResolver(val context: Context) {
class NotifiableEventResolver(val stringProvider: StringProvider,
val noticeEventFormatter: NoticeEventFormatter) {

//private val eventDisplay = RiotEventDisplay(context)

fun resolveEvent(event: Event/*, roomState: RoomState?, bingRule: PushRule?*/, session: Session): NotifiableEvent? {


// val store = session.dataHandler.store
// if (store == null) {
// Log.e("## NotifiableEventResolver, unable to get store")
// //TODO notify somehow that something did fail?
// return null
// }
val roomID = event.roomId ?: return null
val eventId = event.eventId ?: return null
val timelineEvent = session.getRoom(roomID)?.getTimeLineEvent(eventId) ?: return null

when (event.getClearType()) {
EventType.MESSAGE -> {
return resolveMessageEvent(event, session)
return resolveMessageEvent(timelineEvent, session)
}
// EventType.ENCRYPTED -> {
// val messageEvent = resolveMessageEvent(event, bingRule, session, store)
// messageEvent?.lockScreenVisibility = NotificationCompat.VISIBILITY_PRIVATE
// return messageEvent
// }
// EventType.STATE_ROOM_MEMBER -> {
// return resolveStateRoomEvent(event, bingRule, session, store)
// }
else -> {
EventType.ENCRYPTED -> {
val messageEvent = resolveMessageEvent(timelineEvent, session)
messageEvent?.lockScreenVisibility = NotificationCompat.VISIBILITY_PRIVATE
return messageEvent
}
EventType.STATE_ROOM_MEMBER -> {
return resolveStateRoomEvent(event, session)
}
else -> {

//If the event can be displayed, display it as is
// eventDisplay.getTextualDisplay(event, roomState)?.toString()?.let { body ->
// return SimpleNotifiableEvent(
// session.myUserId,
// eventId = event.eventId,
// noisy = bingRule?.notificationSound != null,
// timestamp = event.originServerTs,
// description = body,
// soundName = bingRule?.notificationSound,
// title = context.getString(R.string.notification_unknown_new_event),
// type = event.type)
// }
//TODO Better event text display
val bodyPreview = event.type

return SimpleNotifiableEvent(
session.sessionParams.credentials.userId,
eventId = event.eventId!!,
noisy = false,//will be updated
timestamp = event.originServerTs ?: System.currentTimeMillis(),
description = bodyPreview,
title = stringProvider.getString(R.string.notification_unknown_new_event),
soundName = null,
type = event.type)


//Unsupported event
Timber.w("NotifiableEventResolver Received an unsupported event matching a bing rule")
@ -88,57 +89,55 @@ class NotifiableEventResolver(val context: Context) {
}


private fun resolveMessageEvent(event: Event, session: Session): NotifiableEvent? {
//If we are here, that means that the event should be notified to the user, we check now how it should be presented (sound)
// val soundName = pushRule?.notificationSound
private fun resolveMessageEvent(event: TimelineEvent, session: Session): 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.roomId!! /*roomID cannot be null (see Matrix SDK code)*/)
val room = session.getRoom(event.root.roomId!! /*roomID cannot be null*/)


if (room == null) {
Timber.e("## Unable to resolve room for eventId [${event.eventId}] and roomID [${event.roomId}]")
Timber.e("## Unable to resolve room for eventId [${event}]")
// Ok room is not known in store, but we can still display something
val body = event.content?.toModel<MessageContent>()?.body
?: context.getString(R.string.notification_unknown_new_event)
val roomName = context.getString(R.string.notification_unknown_room_name)
val senderDisplayName = event.senderId ?: ""
val body =
event.annotations?.editSummary?.aggregatedContent?.toModel<MessageContent>()?.body
?: event.root.getClearContent().toModel<MessageContent>()?.body
?: stringProvider.getString(R.string.notification_unknown_new_event)
val roomName = stringProvider.getString(R.string.notification_unknown_room_name)
val senderDisplayName = event.senderName

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

notifiableEvent.matrixID = session.sessionParams.credentials.userId
// notifiableEvent.soundName = soundName

return notifiableEvent
} else {
val tEvent = room.getTimeLineEvent(event.eventId!!)
val body = event.content?.toModel<MessageContent>()?.body
?: context.getString(R.string.notification_unknown_new_event)
val roomName = event.roomId ?: "room"
val senderDisplayName = tEvent?.senderName ?: "?"
val body = event.root.getClearContent().toModel<MessageContent>()?.body
?: stringProvider.getString(R.string.notification_unknown_new_event)
val roomName = room.roomSummary?.displayName ?: ""
val senderDisplayName = event.senderName ?: ""

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

notifiableEvent.matrixID = session.sessionParams.credentials.userId
notifiableEvent.soundName = null

//TODO get the avatar?

// val roomAvatarPath = session.mediaCache?.thumbnailCacheFile(room.avatarUrl, 50)
// if (roomAvatarPath != null) {
@ -162,21 +161,24 @@ class NotifiableEventResolver(val context: Context) {
return notifiableEvent
}
}
/*
private fun resolveStateRoomEvent(event: Event, bingRule: BingRule?, session: MXSession, store: IMXStore): NotifiableEvent? {
if (RoomMember.MEMBERSHIP_INVITE == event.contentAsJsonObject?.getAsJsonPrimitive("membership")?.asString) {
val room = store.getRoom(event.roomId /*roomID cannot be null (see Matrix SDK code)*/)
val body = eventDisplay.getTextualDisplay(event, room.state)?.toString()
?: context.getString(R.string.notification_new_invitation)


private fun resolveStateRoomEvent(event: Event, session: Session): NotifiableEvent? {
val content = event.content?.toModel<RoomMember>() ?: return null
val roomId = event.roomId ?: return null
val dName = event.senderId?.let { session.getUser(it)?.displayName }
if (Membership.INVITE == content.membership) {
val body = noticeEventFormatter.format(event, dName)
?: stringProvider.getString(R.string.notification_new_invitation)
return InviteNotifiableEvent(
session.myUserId,
eventId = event.eventId,
roomId = event.roomId,
timestamp = event.originServerTs,
noisy = bingRule?.notificationSound != null,
title = context.getString(R.string.notification_new_invitation),
description = body,
soundName = bingRule?.notificationSound,
session.sessionParams.credentials.userId,
eventId = event.eventId!!,
roomId = roomId,
timestamp = event.originServerTs ?: 0,
noisy = false,//will be set later
title = stringProvider.getString(R.string.notification_new_invitation),
description = body.toString(),
soundName = null, //will be set later
type = event.getClearType(),
isPushGatewayEvent = false)
} else {
@ -187,6 +189,6 @@ class NotifiableEventResolver(val context: Context) {
//TODO generic handling?
}
return null
} */
}
}


View File

@ -22,7 +22,8 @@ import android.graphics.BitmapFactory
import android.text.TextUtils
import androidx.core.app.NotificationCompat
import androidx.core.app.Person
import im.vector.matrix.android.api.session.Session
import im.vector.matrix.android.api.Matrix
import im.vector.matrix.android.api.session.content.ContentUrlResolver
import im.vector.riotredesign.BuildConfig
import im.vector.riotredesign.R
import im.vector.riotredesign.core.utils.SecretStoringUtils
@ -36,14 +37,14 @@ import java.io.FileOutputStream
* organise them in order to display them in the notification drawer.
* Events can be grouped into the same notification, old (already read) events can be removed to do some cleaning.
*/
class NotificationDrawerManager(val context: Context, private val outdatedDetector: OutdatedEventDetector?) {
class NotificationDrawerManager(val context: Context,
private val outdatedDetector: OutdatedEventDetector?) {


//The first time the notification drawer is refreshed, we force re-render of all notifications
private var firstTime = true

private var eventList = loadEventInfo()
private var myUserDisplayName: String = "Todo"
private var myUserAvatarUrl: String = ""

private val avatarSize = context.resources.getDimensionPixelSize(R.dimen.profile_avatar_size)

@ -156,17 +157,11 @@ class NotificationDrawerManager(val context: Context, private val outdatedDetect


fun refreshNotificationDrawer() {
if (myUserDisplayName.isBlank()) {
// TODO
// initWithSession(Matrix.getInstance(context).defaultSession)
}

if (myUserDisplayName.isBlank()) {
// Should not happen, but myUserDisplayName cannot be blank if used to create a Person
//TODO
// return
}

val session = Matrix.getInstance().currentSession ?: return
val user = session.getUser(session.sessionParams.credentials.userId)
val myUserDisplayName = user?.displayName ?: ""
val myUserAvatarUrl = session.contentUrlResolver().resolveThumbnail(user?.avatarUrl, avatarSize, avatarSize, ContentUrlResolver.ThumbnailMethod.SCALE)
synchronized(eventList) {

Timber.v("%%%%%%%% REFRESH NOTIFICATION DRAWER ")

View File

@ -15,10 +15,9 @@
*/
package im.vector.riotredesign.features.notifications

import android.content.Context
import im.vector.matrix.android.api.Matrix

class OutdatedEventDetector(val context: Context) {
class OutdatedEventDetector() {

/**
* Returns true if the given event is outdated.