diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/EventRelationsAggregationProcessor.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/EventRelationsAggregationProcessor.kt index 8bbe3a9ac6..ed7fc5b721 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/EventRelationsAggregationProcessor.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/EventRelationsAggregationProcessor.kt @@ -32,8 +32,10 @@ import org.matrix.android.sdk.api.session.room.model.PowerLevelsContent import org.matrix.android.sdk.api.session.room.model.ReferencesAggregatedContent import org.matrix.android.sdk.api.session.room.model.VoteInfo import org.matrix.android.sdk.api.session.room.model.VoteSummary +import org.matrix.android.sdk.api.session.room.model.livelocation.LiveLocationBeaconContent import org.matrix.android.sdk.api.session.room.model.message.MessageContent import org.matrix.android.sdk.api.session.room.model.message.MessageEndPollContent +import org.matrix.android.sdk.api.session.room.model.message.MessageLiveLocationContent import org.matrix.android.sdk.api.session.room.model.message.MessagePollContent import org.matrix.android.sdk.api.session.room.model.message.MessagePollResponseContent import org.matrix.android.sdk.api.session.room.model.message.MessageRelationContent @@ -47,6 +49,7 @@ import org.matrix.android.sdk.internal.crypto.verification.toState import org.matrix.android.sdk.internal.database.helper.findRootThreadEvent import org.matrix.android.sdk.internal.database.mapper.ContentMapper import org.matrix.android.sdk.internal.database.mapper.EventMapper +import org.matrix.android.sdk.internal.database.model.CurrentStateEventEntity import org.matrix.android.sdk.internal.database.model.EditAggregatedSummaryEntity import org.matrix.android.sdk.internal.database.model.EditionOfEvent import org.matrix.android.sdk.internal.database.model.EventAnnotationsSummaryEntity @@ -60,7 +63,9 @@ import org.matrix.android.sdk.internal.database.model.TimelineEventEntity import org.matrix.android.sdk.internal.database.model.TimelineEventEntityFields import org.matrix.android.sdk.internal.database.query.create import org.matrix.android.sdk.internal.database.query.getOrCreate +import org.matrix.android.sdk.internal.database.query.getOrNull import org.matrix.android.sdk.internal.database.query.where +import org.matrix.android.sdk.internal.database.query.whereStateKey import org.matrix.android.sdk.internal.di.SessionId import org.matrix.android.sdk.internal.di.UserId import org.matrix.android.sdk.internal.session.EventInsertLiveProcessor @@ -88,7 +93,7 @@ internal class EventRelationsAggregationProcessor @Inject constructor( // EventType.KEY_VERIFICATION_READY, EventType.KEY_VERIFICATION_KEY, EventType.ENCRYPTED - ) + EventType.POLL_START + EventType.POLL_RESPONSE + EventType.POLL_END + ) + EventType.POLL_START + EventType.POLL_RESPONSE + EventType.POLL_END + EventType.BEACON_LOCATION_DATA override fun shouldProcess(eventId: String, eventType: String, insertType: EventInsertType): Boolean { return allowedTypes.contains(eventType) @@ -103,12 +108,12 @@ internal class EventRelationsAggregationProcessor @Inject constructor( } val isLocalEcho = LocalEcho.isLocalEchoId(event.eventId ?: "") when (event.type) { - EventType.REACTION -> { + EventType.REACTION -> { // we got a reaction!! Timber.v("###REACTION in room $roomId , reaction eventID ${event.eventId}") handleReaction(realm, event, roomId, isLocalEcho) } - EventType.MESSAGE -> { + EventType.MESSAGE -> { if (event.unsignedData?.relations?.annotations != null) { Timber.v("###REACTION Aggregation in room $roomId for event ${event.eventId}") handleInitialAggregatedRelations(realm, event, roomId, event.unsignedData.relations.annotations) @@ -134,7 +139,7 @@ internal class EventRelationsAggregationProcessor @Inject constructor( EventType.KEY_VERIFICATION_START, EventType.KEY_VERIFICATION_MAC, EventType.KEY_VERIFICATION_READY, - EventType.KEY_VERIFICATION_KEY -> { + EventType.KEY_VERIFICATION_KEY -> { Timber.v("## SAS REF in room $roomId for event ${event.eventId}") event.content.toModel()?.relatesTo?.let { if (it.type == RelationType.REFERENCE && it.eventId != null) { @@ -143,7 +148,7 @@ internal class EventRelationsAggregationProcessor @Inject constructor( } } - EventType.ENCRYPTED -> { + EventType.ENCRYPTED -> { // Relation type is in clear val encryptedEventContent = event.content.toModel() if (encryptedEventContent?.relatesTo?.type == RelationType.REPLACE || @@ -169,22 +174,27 @@ internal class EventRelationsAggregationProcessor @Inject constructor( EventType.KEY_VERIFICATION_START, EventType.KEY_VERIFICATION_MAC, EventType.KEY_VERIFICATION_READY, - EventType.KEY_VERIFICATION_KEY -> { + EventType.KEY_VERIFICATION_KEY -> { Timber.v("## SAS REF in room $roomId for event ${event.eventId}") encryptedEventContent.relatesTo.eventId?.let { handleVerification(realm, event, roomId, isLocalEcho, it) } } - in EventType.POLL_RESPONSE -> { + in EventType.POLL_RESPONSE -> { event.getClearContent().toModel(catchError = true)?.let { handleResponse(realm, event, it, roomId, isLocalEcho, event.getRelationContent()?.eventId) } } - in EventType.POLL_END -> { + in EventType.POLL_END -> { event.content.toModel(catchError = true)?.let { handleEndPoll(realm, event, it, roomId, isLocalEcho) } } + in EventType.BEACON_LOCATION_DATA -> { + event.content.toModel(catchError = true)?.let { + handleLiveLocation(realm, event, it, roomId, isLocalEcho) + } + } } } else if (encryptedEventContent?.relatesTo?.type == RelationType.ANNOTATION) { // Reaction @@ -205,7 +215,7 @@ internal class EventRelationsAggregationProcessor @Inject constructor( // } // } } - EventType.REDACTION -> { + EventType.REDACTION -> { val eventToPrune = event.redacts?.let { EventEntity.where(realm, eventId = it).findFirst() } ?: return when (eventToPrune.type) { @@ -225,7 +235,7 @@ internal class EventRelationsAggregationProcessor @Inject constructor( } } } - in EventType.POLL_START -> { + in EventType.POLL_START -> { val content: MessagePollContent? = event.content.toModel() if (content?.relatesTo?.type == RelationType.REPLACE) { Timber.v("###REPLACE in room $roomId for event ${event.eventId}") @@ -233,17 +243,22 @@ internal class EventRelationsAggregationProcessor @Inject constructor( handleReplace(realm, event, content, roomId, isLocalEcho) } } - in EventType.POLL_RESPONSE -> { + in EventType.POLL_RESPONSE -> { event.content.toModel(catchError = true)?.let { handleResponse(realm, event, it, roomId, isLocalEcho) } } - in EventType.POLL_END -> { + in EventType.POLL_END -> { event.content.toModel(catchError = true)?.let { handleEndPoll(realm, event, it, roomId, isLocalEcho) } } - else -> Timber.v("UnHandled event ${event.eventId}") + in EventType.BEACON_LOCATION_DATA -> { + event.content.toModel(catchError = true)?.let { + handleLiveLocation(realm, event, it, roomId, isLocalEcho) + } + } + else -> Timber.v("UnHandled event ${event.eventId}") } } catch (t: Throwable) { Timber.e(t, "## Should not happen ") @@ -532,6 +547,46 @@ internal class EventRelationsAggregationProcessor @Inject constructor( } } + private fun handleLiveLocation(realm: Realm, + event: Event, + content: MessageLiveLocationContent, + roomId: String, + isLocalEcho: Boolean) { + val beaconInfoEventId = event.getRelationContent()?.eventId ?: return + val locationSenderId = event.senderId ?: return + + // We shouldn't process local echos + if (isLocalEcho) { + return + } + + // A beacon info state event has to be sent before sending location + val beaconInfoEntity = CurrentStateEventEntity.getOrNull(realm, roomId, locationSenderId, EventType.STATE_ROOM_BEACON_INFO.first()) + if (beaconInfoEntity == null) { + Timber.v("## LIVE LOCATION. There is not any beacon info which should be emitted before sending location updates") + return + } + val beaconInfoContent = ContentMapper.map(beaconInfoEntity.root?.content)?.toModel(catchError = true) + if (beaconInfoContent == null) { + Timber.v("## LIVE LOCATION. Beacon info content is invalid") + return + } + + // Check if beacon info is outdated + if (isBeaconInfoOutdated(beaconInfoContent, content)) { + Timber.v("## LIVE LOCATION. Beacon info content is invalid") + return + } + } + + private fun isBeaconInfoOutdated(beaconInfoContent: LiveLocationBeaconContent, + liveLocationContent: MessageLiveLocationContent): Boolean { + val beaconInfoStartTime = beaconInfoContent.getBestTimestampAsMilliseconds() ?: 0 + val liveLocationEventTime = liveLocationContent.getBestTs() ?: 0 + val timeout = beaconInfoContent.getBestBeaconInfo()?.timeout ?: 0 + return liveLocationEventTime - beaconInfoStartTime > timeout + } + private fun handleInitialAggregatedRelations(realm: Realm, event: Event, roomId: String,