Merge pull request #5869 from vector-im/feature/mna/PSF-884-live-location-aggregation

[Live location sharing] Improve aggregation process of events
This commit is contained in:
Benoit Marty 2022-05-04 11:11:39 +02:00 committed by GitHub
commit b5a0c944d1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
25 changed files with 414 additions and 152 deletions

1
changelog.d/5862.wip Normal file
View File

@ -0,0 +1 @@
[Live location sharing] Improve aggregation process of events

View File

@ -15,9 +15,12 @@
*/ */
package org.matrix.android.sdk.api.session.room.model package org.matrix.android.sdk.api.session.room.model
import org.matrix.android.sdk.api.session.room.model.livelocation.LiveLocationShareAggregatedSummary
data class EventAnnotationsSummary( data class EventAnnotationsSummary(
val reactionsSummary: List<ReactionAggregatedSummary> = emptyList(), val reactionsSummary: List<ReactionAggregatedSummary> = emptyList(),
val editSummary: EditAggregatedSummary? = null, val editSummary: EditAggregatedSummary? = null,
val pollResponseSummary: PollResponseAggregatedSummary? = null, val pollResponseSummary: PollResponseAggregatedSummary? = null,
val referencesAggregatedSummary: ReferencesAggregatedSummary? = null val referencesAggregatedSummary: ReferencesAggregatedSummary? = null,
val liveLocationShareAggregatedSummary: LiveLocationShareAggregatedSummary? = null,
) )

View File

@ -0,0 +1,28 @@
/*
* 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.api.session.room.model.livelocation
import org.matrix.android.sdk.api.session.room.model.message.MessageBeaconLocationDataContent
/**
* Aggregation info concerning a live location share.
*/
data class LiveLocationShareAggregatedSummary(
val isActive: Boolean?,
val endOfLiveTimestampMillis: Long?,
val lastLocationDataContent: MessageBeaconLocationDataContent?,
)

View File

@ -14,25 +14,28 @@
* limitations under the License. * limitations under the License.
*/ */
package org.matrix.android.sdk.api.session.room.model.livelocation package org.matrix.android.sdk.api.session.room.model.message
import com.squareup.moshi.Json import com.squareup.moshi.Json
import com.squareup.moshi.JsonClass import com.squareup.moshi.JsonClass
import org.matrix.android.sdk.api.session.events.model.Content import org.matrix.android.sdk.api.session.events.model.Content
import org.matrix.android.sdk.api.session.room.model.message.LocationAsset
import org.matrix.android.sdk.api.session.room.model.message.LocationAssetType
import org.matrix.android.sdk.api.session.room.model.message.MessageContent
import org.matrix.android.sdk.api.session.room.model.message.MessageLiveLocationContent
import org.matrix.android.sdk.api.session.room.model.message.MessageType
import org.matrix.android.sdk.api.session.room.model.relation.RelationDefaultContent import org.matrix.android.sdk.api.session.room.model.relation.RelationDefaultContent
/**
* Content of the state event of type
* [EventType.STATE_ROOM_BEACON_INFO][org.matrix.android.sdk.api.session.events.model.EventType.STATE_ROOM_BEACON_INFO]
*
* It contains general info related to a live location share.
* Locations are sent in a different message related to the state event.
* See [MessageBeaconLocationDataContent][org.matrix.android.sdk.api.session.room.model.message.MessageBeaconLocationDataContent]
*/
@JsonClass(generateAdapter = true) @JsonClass(generateAdapter = true)
data class LiveLocationBeaconContent( data class MessageBeaconInfoContent(
/** /**
* Local message type, not from server * Local message type, not from server
*/ */
@Transient @Transient
override val msgType: String = MessageType.MSGTYPE_LIVE_LOCATION_STATE, override val msgType: String = MessageType.MSGTYPE_BEACON_INFO,
@Json(name = "body") override val body: String = "", @Json(name = "body") override val body: String = "",
@Json(name = "m.relates_to") override val relatesTo: RelationDefaultContent? = null, @Json(name = "m.relates_to") override val relatesTo: RelationDefaultContent? = null,
@ -54,26 +57,16 @@ data class LiveLocationBeaconContent(
/** /**
* Beacon creation timestamp. * Beacon creation timestamp.
*/ */
@Json(name = "org.matrix.msc3488.ts") val unstableTimestampAsMilliseconds: Long? = null, @Json(name = "org.matrix.msc3488.ts") val unstableTimestampMillis: Long? = null,
@Json(name = "m.ts") val timestampAsMilliseconds: Long? = null, @Json(name = "m.ts") val timestampMillis: Long? = null,
/** /**
* Live location asset type. * Live location asset type.
*/ */
@Json(name = "org.matrix.msc3488.asset") val unstableLocationAsset: LocationAsset = LocationAsset(LocationAssetType.SELF), @Json(name = "org.matrix.msc3488.asset") val unstableLocationAsset: LocationAsset = LocationAsset(LocationAssetType.SELF),
@Json(name = "m.asset") val locationAsset: LocationAsset? = null, @Json(name = "m.asset") val locationAsset: LocationAsset? = null,
/**
* Client side tracking of the last location
*/
var lastLocationContent: MessageLiveLocationContent? = null,
/**
* Client side tracking of whether the beacon has timed out.
*/
var hasTimedOut: Boolean = false
) : MessageContent { ) : MessageContent {
fun getBestTimestampAsMilliseconds() = timestampAsMilliseconds ?: unstableTimestampAsMilliseconds fun getBestTimestampMillis() = timestampMillis ?: unstableTimestampMillis
fun getBestLocationAsset() = locationAsset ?: unstableLocationAsset fun getBestLocationAsset() = locationAsset ?: unstableLocationAsset
} }

View File

@ -21,13 +21,21 @@ import com.squareup.moshi.JsonClass
import org.matrix.android.sdk.api.session.events.model.Content import org.matrix.android.sdk.api.session.events.model.Content
import org.matrix.android.sdk.api.session.room.model.relation.RelationDefaultContent import org.matrix.android.sdk.api.session.room.model.relation.RelationDefaultContent
/**
* Content of the state event of type
* [EventType.BEACON_LOCATION_DATA][org.matrix.android.sdk.api.session.events.model.EventType.BEACON_LOCATION_DATA]
*
* It contains location data related to a live location share.
* It is related to the state event that originally started the live.
* See [MessageBeaconInfoContent][org.matrix.android.sdk.api.session.room.model.message.MessageBeaconInfoContent]
*/
@JsonClass(generateAdapter = true) @JsonClass(generateAdapter = true)
data class MessageLiveLocationContent( data class MessageBeaconLocationDataContent(
/** /**
* Local message type, not from server * Local message type, not from server
*/ */
@Transient @Transient
override val msgType: String = MessageType.MSGTYPE_LIVE_LOCATION, override val msgType: String = MessageType.MSGTYPE_BEACON_LOCATION_DATA,
@Json(name = "body") override val body: String = "", @Json(name = "body") override val body: String = "",
@Json(name = "m.relates_to") override val relatesTo: RelationDefaultContent? = null, @Json(name = "m.relates_to") override val relatesTo: RelationDefaultContent? = null,
@ -42,11 +50,11 @@ data class MessageLiveLocationContent(
/** /**
* Exact time that the data in the event refers to (milliseconds since the UNIX epoch) * Exact time that the data in the event refers to (milliseconds since the UNIX epoch)
*/ */
@Json(name = "org.matrix.msc3488.ts") val unstableTimestampAsMilliseconds: Long? = null, @Json(name = "org.matrix.msc3488.ts") val unstableTimestampMillis: Long? = null,
@Json(name = "m.ts") val timestampAsMilliseconds: Long? = null @Json(name = "m.ts") val timestampMillis: Long? = null
) : MessageContent { ) : MessageContent {
fun getBestLocationInfo() = locationInfo ?: unstableLocationInfo fun getBestLocationInfo() = locationInfo ?: unstableLocationInfo
fun getBestTimestampAsMilliseconds() = timestampAsMilliseconds ?: unstableTimestampAsMilliseconds fun getBestTimestampMillis() = timestampMillis ?: unstableTimestampMillis
} }

View File

@ -49,8 +49,8 @@ data class MessageLocationContent(
/** /**
* Exact time that the data in the event refers to (milliseconds since the UNIX epoch) * Exact time that the data in the event refers to (milliseconds since the UNIX epoch)
*/ */
@Json(name = "org.matrix.msc3488.ts") val unstableTs: Long? = null, @Json(name = "org.matrix.msc3488.ts") val unstableTimestampMillis: Long? = null,
@Json(name = "m.ts") val ts: Long? = null, @Json(name = "m.ts") val timestampMillis: Long? = null,
@Json(name = "org.matrix.msc1767.text") val unstableText: String? = null, @Json(name = "org.matrix.msc1767.text") val unstableText: String? = null,
@Json(name = "m.text") val text: String? = null, @Json(name = "m.text") val text: String? = null,
/** /**
@ -66,7 +66,7 @@ data class MessageLocationContent(
fun getBestLocationInfo() = locationInfo ?: unstableLocationInfo fun getBestLocationInfo() = locationInfo ?: unstableLocationInfo
fun getBestTs() = ts ?: unstableTs fun getBestTimestampMillis() = timestampMillis ?: unstableTimestampMillis
fun getBestText() = text ?: unstableText fun getBestText() = text ?: unstableText

View File

@ -41,6 +41,6 @@ object MessageType {
const val MSGTYPE_SNOWFALL = "io.element.effect.snowfall" const val MSGTYPE_SNOWFALL = "io.element.effect.snowfall"
// Fake message types for live location events to be able to inherit them from MessageContent // Fake message types for live location events to be able to inherit them from MessageContent
const val MSGTYPE_LIVE_LOCATION_STATE = "org.matrix.android.sdk.livelocation.state" const val MSGTYPE_BEACON_INFO = "org.matrix.android.sdk.beacon.info"
const val MSGTYPE_LIVE_LOCATION = "org.matrix.android.sdk.livelocation" const val MSGTYPE_BEACON_LOCATION_DATA = "org.matrix.android.sdk.beacon.location.data"
} }

View File

@ -29,7 +29,7 @@ import org.matrix.android.sdk.api.session.events.model.isSticker
import org.matrix.android.sdk.api.session.events.model.toModel import org.matrix.android.sdk.api.session.events.model.toModel
import org.matrix.android.sdk.api.session.room.model.EventAnnotationsSummary import org.matrix.android.sdk.api.session.room.model.EventAnnotationsSummary
import org.matrix.android.sdk.api.session.room.model.ReadReceipt import org.matrix.android.sdk.api.session.room.model.ReadReceipt
import org.matrix.android.sdk.api.session.room.model.livelocation.LiveLocationBeaconContent import org.matrix.android.sdk.api.session.room.model.message.MessageBeaconInfoContent
import org.matrix.android.sdk.api.session.room.model.message.MessageContent import org.matrix.android.sdk.api.session.room.model.message.MessageContent
import org.matrix.android.sdk.api.session.room.model.message.MessagePollContent import org.matrix.android.sdk.api.session.room.model.message.MessagePollContent
import org.matrix.android.sdk.api.session.room.model.message.MessageStickerContent import org.matrix.android.sdk.api.session.room.model.message.MessageStickerContent
@ -139,7 +139,7 @@ fun TimelineEvent.getLastMessageContent(): MessageContent? {
return when (root.getClearType()) { return when (root.getClearType()) {
EventType.STICKER -> root.getClearContent().toModel<MessageStickerContent>() EventType.STICKER -> root.getClearContent().toModel<MessageStickerContent>()
in EventType.POLL_START -> (annotations?.editSummary?.latestContent ?: root.getClearContent()).toModel<MessagePollContent>() in EventType.POLL_START -> (annotations?.editSummary?.latestContent ?: root.getClearContent()).toModel<MessagePollContent>()
in EventType.STATE_ROOM_BEACON_INFO -> (annotations?.editSummary?.latestContent ?: root.getClearContent()).toModel<LiveLocationBeaconContent>() in EventType.STATE_ROOM_BEACON_INFO -> (annotations?.editSummary?.latestContent ?: root.getClearContent()).toModel<MessageBeaconInfoContent>()
else -> (annotations?.editSummary?.latestContent ?: root.getClearContent()).toModel() else -> (annotations?.editSummary?.latestContent ?: root.getClearContent()).toModel()
} }
} }

View File

@ -44,6 +44,7 @@ import org.matrix.android.sdk.internal.database.migration.MigrateSessionTo023
import org.matrix.android.sdk.internal.database.migration.MigrateSessionTo024 import org.matrix.android.sdk.internal.database.migration.MigrateSessionTo024
import org.matrix.android.sdk.internal.database.migration.MigrateSessionTo025 import org.matrix.android.sdk.internal.database.migration.MigrateSessionTo025
import org.matrix.android.sdk.internal.database.migration.MigrateSessionTo026 import org.matrix.android.sdk.internal.database.migration.MigrateSessionTo026
import org.matrix.android.sdk.internal.database.migration.MigrateSessionTo027
import org.matrix.android.sdk.internal.util.Normalizer import org.matrix.android.sdk.internal.util.Normalizer
import timber.log.Timber import timber.log.Timber
import javax.inject.Inject import javax.inject.Inject
@ -58,7 +59,7 @@ internal class RealmSessionStoreMigration @Inject constructor(
override fun equals(other: Any?) = other is RealmSessionStoreMigration override fun equals(other: Any?) = other is RealmSessionStoreMigration
override fun hashCode() = 1000 override fun hashCode() = 1000
val schemaVersion = 26L val schemaVersion = 27L
override fun migrate(realm: DynamicRealm, oldVersion: Long, newVersion: Long) { override fun migrate(realm: DynamicRealm, oldVersion: Long, newVersion: Long) {
Timber.d("Migrating Realm Session from $oldVersion to $newVersion") Timber.d("Migrating Realm Session from $oldVersion to $newVersion")
@ -89,5 +90,6 @@ internal class RealmSessionStoreMigration @Inject constructor(
if (oldVersion < 24) MigrateSessionTo024(realm).perform() if (oldVersion < 24) MigrateSessionTo024(realm).perform()
if (oldVersion < 25) MigrateSessionTo025(realm).perform() if (oldVersion < 25) MigrateSessionTo025(realm).perform()
if (oldVersion < 26) MigrateSessionTo026(realm).perform() if (oldVersion < 26) MigrateSessionTo026(realm).perform()
if (oldVersion < 27) MigrateSessionTo027(realm).perform()
} }
} }

View File

@ -56,8 +56,10 @@ internal object EventAnnotationsSummaryMapper {
}, },
pollResponseSummary = annotationsSummary.pollResponseSummary?.let { pollResponseSummary = annotationsSummary.pollResponseSummary?.let {
PollResponseAggregatedSummaryEntityMapper.map(it) PollResponseAggregatedSummaryEntityMapper.map(it)
},
liveLocationShareAggregatedSummary = annotationsSummary.liveLocationShareAggregatedSummary?.let {
LiveLocationShareAggregatedSummaryMapper.map(it)
} }
) )
} }
} }

View File

@ -0,0 +1,33 @@
/*
* 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.mapper
import org.matrix.android.sdk.api.session.events.model.toModel
import org.matrix.android.sdk.api.session.room.model.livelocation.LiveLocationShareAggregatedSummary
import org.matrix.android.sdk.api.session.room.model.message.MessageBeaconLocationDataContent
import org.matrix.android.sdk.internal.database.model.livelocation.LiveLocationShareAggregatedSummaryEntity
internal object LiveLocationShareAggregatedSummaryMapper {
fun map(entity: LiveLocationShareAggregatedSummaryEntity): LiveLocationShareAggregatedSummary {
return LiveLocationShareAggregatedSummary(
isActive = entity.isActive,
endOfLiveTimestampMillis = entity.endOfLiveTimestampMillis,
lastLocationDataContent = ContentMapper.map(entity.lastLocationContent).toModel<MessageBeaconLocationDataContent>()
)
}
}

View File

@ -0,0 +1,46 @@
/*
* 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 io.realm.FieldAttribute
import org.matrix.android.sdk.internal.database.model.EventAnnotationsSummaryEntityFields
import org.matrix.android.sdk.internal.database.model.livelocation.LiveLocationShareAggregatedSummaryEntityFields
import org.matrix.android.sdk.internal.util.database.RealmMigrator
/**
* Migrating to:
* Live location sharing aggregated summary
*/
internal class MigrateSessionTo027(realm: DynamicRealm) : RealmMigrator(realm, 27) {
override fun doMigrate(realm: DynamicRealm) {
val liveLocationSummaryEntity = realm.schema.get("LiveLocationShareAggregatedSummaryEntity")
?: realm.schema.create("LiveLocationShareAggregatedSummaryEntity")
.addField(LiveLocationShareAggregatedSummaryEntityFields.EVENT_ID, String::class.java, FieldAttribute.REQUIRED)
.addField(LiveLocationShareAggregatedSummaryEntityFields.ROOM_ID, String::class.java, FieldAttribute.REQUIRED)
.addField(LiveLocationShareAggregatedSummaryEntityFields.IS_ACTIVE, Boolean::class.java)
.setNullable(LiveLocationShareAggregatedSummaryEntityFields.IS_ACTIVE, true)
.addField(LiveLocationShareAggregatedSummaryEntityFields.END_OF_LIVE_TIMESTAMP_MILLIS, Long::class.java)
.setNullable(LiveLocationShareAggregatedSummaryEntityFields.END_OF_LIVE_TIMESTAMP_MILLIS, true)
.addField(LiveLocationShareAggregatedSummaryEntityFields.LAST_LOCATION_CONTENT, String::class.java)
?: return
realm.schema.get("EventAnnotationsSummaryEntity")
?.addRealmObjectField(EventAnnotationsSummaryEntityFields.LIVE_LOCATION_SHARE_AGGREGATED_SUMMARY.`$`, liveLocationSummaryEntity)
}
}

View File

@ -18,6 +18,7 @@ package org.matrix.android.sdk.internal.database.model
import io.realm.RealmList import io.realm.RealmList
import io.realm.RealmObject import io.realm.RealmObject
import io.realm.annotations.PrimaryKey import io.realm.annotations.PrimaryKey
import org.matrix.android.sdk.internal.database.model.livelocation.LiveLocationShareAggregatedSummaryEntity
import timber.log.Timber import timber.log.Timber
internal open class EventAnnotationsSummaryEntity( internal open class EventAnnotationsSummaryEntity(
@ -27,7 +28,8 @@ internal open class EventAnnotationsSummaryEntity(
var reactionsSummary: RealmList<ReactionAggregatedSummaryEntity> = RealmList(), var reactionsSummary: RealmList<ReactionAggregatedSummaryEntity> = RealmList(),
var editSummary: EditAggregatedSummaryEntity? = null, var editSummary: EditAggregatedSummaryEntity? = null,
var referencesSummaryEntity: ReferencesAggregatedSummaryEntity? = null, var referencesSummaryEntity: ReferencesAggregatedSummaryEntity? = null,
var pollResponseSummary: PollResponseAggregatedSummaryEntity? = null var pollResponseSummary: PollResponseAggregatedSummaryEntity? = null,
var liveLocationShareAggregatedSummary: LiveLocationShareAggregatedSummaryEntity? = null,
) : RealmObject() { ) : RealmObject() {
/** /**

View File

@ -17,6 +17,7 @@
package org.matrix.android.sdk.internal.database.model package org.matrix.android.sdk.internal.database.model
import io.realm.annotations.RealmModule import io.realm.annotations.RealmModule
import org.matrix.android.sdk.internal.database.model.livelocation.LiveLocationShareAggregatedSummaryEntity
import org.matrix.android.sdk.internal.database.model.presence.UserPresenceEntity import org.matrix.android.sdk.internal.database.model.presence.UserPresenceEntity
import org.matrix.android.sdk.internal.database.model.threads.ThreadSummaryEntity import org.matrix.android.sdk.internal.database.model.threads.ThreadSummaryEntity
@ -47,6 +48,7 @@ import org.matrix.android.sdk.internal.database.model.threads.ThreadSummaryEntit
EditAggregatedSummaryEntity::class, EditAggregatedSummaryEntity::class,
EditionOfEvent::class, EditionOfEvent::class,
PollResponseAggregatedSummaryEntity::class, PollResponseAggregatedSummaryEntity::class,
LiveLocationShareAggregatedSummaryEntity::class,
ReferencesAggregatedSummaryEntity::class, ReferencesAggregatedSummaryEntity::class,
PushRulesEntity::class, PushRulesEntity::class,
PushRuleEntity::class, PushRuleEntity::class,

View File

@ -0,0 +1,45 @@
/*
* 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.model.livelocation
import io.realm.RealmObject
import io.realm.annotations.PrimaryKey
/**
* Aggregation info concerning a live location share.
*/
internal open class LiveLocationShareAggregatedSummaryEntity(
/**
* Event id of the event that started the live.
*/
@PrimaryKey
var eventId: String = "",
var roomId: String = "",
var isActive: Boolean? = null,
var endOfLiveTimestampMillis: Long? = null,
/**
* For now we persist this as a JSON for greater flexibility
* @see [org.matrix.android.sdk.api.session.room.model.message.MessageBeaconLocationDataContent]
*/
var lastLocationContent: String? = null,
) : RealmObject() {
companion object
}

View File

@ -0,0 +1,57 @@
/*
* 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.query
import io.realm.Realm
import io.realm.RealmQuery
import io.realm.kotlin.where
import org.matrix.android.sdk.internal.database.model.EventAnnotationsSummaryEntity
import org.matrix.android.sdk.internal.database.model.livelocation.LiveLocationShareAggregatedSummaryEntity
import org.matrix.android.sdk.internal.database.model.livelocation.LiveLocationShareAggregatedSummaryEntityFields
internal fun LiveLocationShareAggregatedSummaryEntity.Companion.where(
realm: Realm,
roomId: String,
eventId: String,
): RealmQuery<LiveLocationShareAggregatedSummaryEntity> {
return realm.where<LiveLocationShareAggregatedSummaryEntity>()
.equalTo(LiveLocationShareAggregatedSummaryEntityFields.ROOM_ID, roomId)
.equalTo(LiveLocationShareAggregatedSummaryEntityFields.EVENT_ID, eventId)
}
internal fun LiveLocationShareAggregatedSummaryEntity.Companion.create(
realm: Realm,
roomId: String,
eventId: String,
): LiveLocationShareAggregatedSummaryEntity {
val obj = realm.createObject(LiveLocationShareAggregatedSummaryEntity::class.java, eventId).apply {
this.roomId = roomId
}
val annotationSummary = EventAnnotationsSummaryEntity.getOrCreate(realm, roomId = roomId, eventId = eventId)
annotationSummary.liveLocationShareAggregatedSummary = obj
return obj
}
internal fun LiveLocationShareAggregatedSummaryEntity.Companion.getOrCreate(
realm: Realm,
roomId: String,
eventId: String,
): LiveLocationShareAggregatedSummaryEntity {
return LiveLocationShareAggregatedSummaryEntity.where(realm, roomId, eventId).findFirst()
?: LiveLocationShareAggregatedSummaryEntity.create(realm, roomId, eventId)
}

View File

@ -33,9 +33,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.ReferencesAggregatedContent
import org.matrix.android.sdk.api.session.room.model.VoteInfo 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.VoteSummary
import org.matrix.android.sdk.api.session.room.model.message.MessageBeaconInfoContent
import org.matrix.android.sdk.api.session.room.model.message.MessageBeaconLocationDataContent
import org.matrix.android.sdk.api.session.room.model.message.MessageContent 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.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.MessagePollContent
import org.matrix.android.sdk.api.session.room.model.message.MessagePollResponseContent import org.matrix.android.sdk.api.session.room.model.message.MessagePollResponseContent
import org.matrix.android.sdk.api.session.room.model.message.MessageRelationContent import org.matrix.android.sdk.api.session.room.model.message.MessageRelationContent
@ -91,7 +92,7 @@ internal class EventRelationsAggregationProcessor @Inject constructor(
// EventType.KEY_VERIFICATION_READY, // EventType.KEY_VERIFICATION_READY,
EventType.KEY_VERIFICATION_KEY, EventType.KEY_VERIFICATION_KEY,
EventType.ENCRYPTED EventType.ENCRYPTED
) + EventType.POLL_START + EventType.POLL_RESPONSE + EventType.POLL_END + EventType.BEACON_LOCATION_DATA ) + EventType.POLL_START + EventType.POLL_RESPONSE + EventType.POLL_END + EventType.STATE_ROOM_BEACON_INFO + EventType.BEACON_LOCATION_DATA
override fun shouldProcess(eventId: String, eventType: String, insertType: EventInsertType): Boolean { override fun shouldProcess(eventId: String, eventType: String, insertType: EventInsertType): Boolean {
return allowedTypes.contains(eventType) return allowedTypes.contains(eventType)
@ -106,12 +107,12 @@ internal class EventRelationsAggregationProcessor @Inject constructor(
} }
val isLocalEcho = LocalEcho.isLocalEchoId(event.eventId ?: "") val isLocalEcho = LocalEcho.isLocalEchoId(event.eventId ?: "")
when (event.type) { when (event.type) {
EventType.REACTION -> { EventType.REACTION -> {
// we got a reaction!! // we got a reaction!!
Timber.v("###REACTION in room $roomId , reaction eventID ${event.eventId}") Timber.v("###REACTION in room $roomId , reaction eventID ${event.eventId}")
handleReaction(realm, event, roomId, isLocalEcho) handleReaction(realm, event, roomId, isLocalEcho)
} }
EventType.MESSAGE -> { EventType.MESSAGE -> {
if (event.unsignedData?.relations?.annotations != null) { if (event.unsignedData?.relations?.annotations != null) {
Timber.v("###REACTION Aggregation in room $roomId for event ${event.eventId}") Timber.v("###REACTION Aggregation in room $roomId for event ${event.eventId}")
handleInitialAggregatedRelations(realm, event, roomId, event.unsignedData.relations.annotations) handleInitialAggregatedRelations(realm, event, roomId, event.unsignedData.relations.annotations)
@ -137,7 +138,7 @@ internal class EventRelationsAggregationProcessor @Inject constructor(
EventType.KEY_VERIFICATION_START, EventType.KEY_VERIFICATION_START,
EventType.KEY_VERIFICATION_MAC, EventType.KEY_VERIFICATION_MAC,
EventType.KEY_VERIFICATION_READY, EventType.KEY_VERIFICATION_READY,
EventType.KEY_VERIFICATION_KEY -> { EventType.KEY_VERIFICATION_KEY -> {
Timber.v("## SAS REF in room $roomId for event ${event.eventId}") Timber.v("## SAS REF in room $roomId for event ${event.eventId}")
event.content.toModel<MessageRelationContent>()?.relatesTo?.let { event.content.toModel<MessageRelationContent>()?.relatesTo?.let {
if (it.type == RelationType.REFERENCE && it.eventId != null) { if (it.type == RelationType.REFERENCE && it.eventId != null) {
@ -146,7 +147,7 @@ internal class EventRelationsAggregationProcessor @Inject constructor(
} }
} }
EventType.ENCRYPTED -> { EventType.ENCRYPTED -> {
// Relation type is in clear // Relation type is in clear
val encryptedEventContent = event.content.toModel<EncryptedEventContent>() val encryptedEventContent = event.content.toModel<EncryptedEventContent>()
if (encryptedEventContent?.relatesTo?.type == RelationType.REPLACE || if (encryptedEventContent?.relatesTo?.type == RelationType.REPLACE ||
@ -189,8 +190,8 @@ internal class EventRelationsAggregationProcessor @Inject constructor(
} }
} }
in EventType.BEACON_LOCATION_DATA -> { in EventType.BEACON_LOCATION_DATA -> {
event.content.toModel<MessageLiveLocationContent>(catchError = true)?.let { event.getClearContent().toModel<MessageBeaconLocationDataContent>(catchError = true)?.let {
liveLocationAggregationProcessor.handleLiveLocation(realm, event, it, roomId, isLocalEcho) liveLocationAggregationProcessor.handleBeaconLocationData(realm, event, it, roomId, isLocalEcho)
} }
} }
} }
@ -213,7 +214,7 @@ internal class EventRelationsAggregationProcessor @Inject constructor(
// } // }
// } // }
} }
EventType.REDACTION -> { EventType.REDACTION -> {
val eventToPrune = event.redacts?.let { EventEntity.where(realm, eventId = it).findFirst() } val eventToPrune = event.redacts?.let { EventEntity.where(realm, eventId = it).findFirst() }
?: return ?: return
when (eventToPrune.type) { when (eventToPrune.type) {
@ -233,7 +234,7 @@ internal class EventRelationsAggregationProcessor @Inject constructor(
} }
} }
} }
in EventType.POLL_START -> { in EventType.POLL_START -> {
val content: MessagePollContent? = event.content.toModel() val content: MessagePollContent? = event.content.toModel()
if (content?.relatesTo?.type == RelationType.REPLACE) { if (content?.relatesTo?.type == RelationType.REPLACE) {
Timber.v("###REPLACE in room $roomId for event ${event.eventId}") Timber.v("###REPLACE in room $roomId for event ${event.eventId}")
@ -241,22 +242,22 @@ internal class EventRelationsAggregationProcessor @Inject constructor(
handleReplace(realm, event, content, roomId, isLocalEcho) handleReplace(realm, event, content, roomId, isLocalEcho)
} }
} }
in EventType.POLL_RESPONSE -> { in EventType.POLL_RESPONSE -> {
event.content.toModel<MessagePollResponseContent>(catchError = true)?.let { event.content.toModel<MessagePollResponseContent>(catchError = true)?.let {
handleResponse(realm, event, it, roomId, isLocalEcho) handleResponse(realm, event, it, roomId, isLocalEcho)
} }
} }
in EventType.POLL_END -> { in EventType.POLL_END -> {
event.content.toModel<MessageEndPollContent>(catchError = true)?.let { event.content.toModel<MessageEndPollContent>(catchError = true)?.let {
handleEndPoll(realm, event, it, roomId, isLocalEcho) handleEndPoll(realm, event, it, roomId, isLocalEcho)
} }
} }
in EventType.BEACON_LOCATION_DATA -> { in EventType.STATE_ROOM_BEACON_INFO -> {
event.content.toModel<MessageLiveLocationContent>(catchError = true)?.let { event.content.toModel<MessageBeaconInfoContent>(catchError = true)?.let {
liveLocationAggregationProcessor.handleLiveLocation(realm, event, it, roomId, isLocalEcho) liveLocationAggregationProcessor.handleBeaconInfo(realm, event, it, roomId, isLocalEcho)
} }
} }
else -> Timber.v("UnHandled event ${event.eventId}") else -> Timber.v("UnHandled event ${event.eventId}")
} }
} catch (t: Throwable) { } catch (t: Throwable) {
Timber.e(t, "## Should not happen ") Timber.e(t, "## Should not happen ")
@ -325,7 +326,8 @@ internal class EventRelationsAggregationProcessor @Inject constructor(
totalVotes = 0, totalVotes = 0,
winnerVoteCount = 0, winnerVoteCount = 0,
) )
.toContent()) .toContent()
)
} }
val txId = event.unsignedData?.transactionId val txId = event.unsignedData?.transactionId
@ -729,11 +731,13 @@ internal class EventRelationsAggregationProcessor @Inject constructor(
EventType.KEY_VERIFICATION_READY, EventType.KEY_VERIFICATION_READY,
EventType.KEY_VERIFICATION_KEY, EventType.KEY_VERIFICATION_KEY,
EventType.KEY_VERIFICATION_MAC -> currentState.toState(VerificationState.WAITING) EventType.KEY_VERIFICATION_MAC -> currentState.toState(VerificationState.WAITING)
EventType.KEY_VERIFICATION_CANCEL -> currentState.toState(if (event.senderId == userId) { EventType.KEY_VERIFICATION_CANCEL -> currentState.toState(
VerificationState.CANCELED_BY_ME if (event.senderId == userId) {
} else { VerificationState.CANCELED_BY_ME
VerificationState.CANCELED_BY_OTHER } else {
}) VerificationState.CANCELED_BY_OTHER
}
)
EventType.KEY_VERIFICATION_DONE -> currentState.toState(VerificationState.DONE) EventType.KEY_VERIFICATION_DONE -> currentState.toState(VerificationState.DONE)
else -> VerificationState.REQUEST else -> VerificationState.REQUEST
} }

View File

@ -17,69 +17,78 @@
package org.matrix.android.sdk.internal.session.room.aggregation.livelocation package org.matrix.android.sdk.internal.session.room.aggregation.livelocation
import io.realm.Realm import io.realm.Realm
import org.matrix.android.sdk.api.extensions.orFalse import org.matrix.android.sdk.api.extensions.orTrue
import org.matrix.android.sdk.api.session.events.model.Event 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.toContent import org.matrix.android.sdk.api.session.events.model.toContent
import org.matrix.android.sdk.api.session.events.model.toModel import org.matrix.android.sdk.api.session.events.model.toModel
import org.matrix.android.sdk.api.session.room.model.livelocation.LiveLocationBeaconContent import org.matrix.android.sdk.api.session.room.model.message.MessageBeaconInfoContent
import org.matrix.android.sdk.api.session.room.model.message.MessageLiveLocationContent import org.matrix.android.sdk.api.session.room.model.message.MessageBeaconLocationDataContent
import org.matrix.android.sdk.internal.database.mapper.ContentMapper import org.matrix.android.sdk.internal.database.mapper.ContentMapper
import org.matrix.android.sdk.internal.database.model.CurrentStateEventEntity import org.matrix.android.sdk.internal.database.model.livelocation.LiveLocationShareAggregatedSummaryEntity
import org.matrix.android.sdk.internal.database.query.getOrNull import org.matrix.android.sdk.internal.database.query.getOrCreate
import timber.log.Timber import timber.log.Timber
import javax.inject.Inject import javax.inject.Inject
internal class DefaultLiveLocationAggregationProcessor @Inject constructor() : LiveLocationAggregationProcessor { internal class DefaultLiveLocationAggregationProcessor @Inject constructor() : LiveLocationAggregationProcessor {
override fun handleLiveLocation(realm: Realm, event: Event, content: MessageLiveLocationContent, roomId: String, isLocalEcho: Boolean) { override fun handleBeaconInfo(realm: Realm, event: Event, content: MessageBeaconInfoContent, roomId: String, isLocalEcho: Boolean) {
val locationSenderId = event.senderId ?: return if (event.senderId.isNullOrEmpty() || isLocalEcho) {
// We shouldn't process local echos
if (isLocalEcho) {
return return
} }
// A beacon info state event has to be sent before sending location val targetEventId = if (content.isLive.orTrue()) {
// TODO handle missing check of m_relatesTo field event.eventId
var beaconInfoEntity: CurrentStateEventEntity? = null
val eventTypesIterator = EventType.STATE_ROOM_BEACON_INFO.iterator()
while (beaconInfoEntity == null && eventTypesIterator.hasNext()) {
beaconInfoEntity = CurrentStateEventEntity.getOrNull(realm, roomId, locationSenderId, eventTypesIterator.next())
}
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<LiveLocationBeaconContent>(catchError = true)
if (beaconInfoContent == null) {
Timber.v("## LIVE LOCATION. Beacon info content is invalid")
return
}
// Check if live location is ended
if (!beaconInfoContent.isLive.orFalse()) {
Timber.v("## LIVE LOCATION. Beacon info is not live anymore")
return
}
// Check if beacon info is outdated
if (isBeaconInfoOutdated(beaconInfoContent, content)) {
Timber.v("## LIVE LOCATION. Beacon info has timeout")
beaconInfoContent.hasTimedOut = true
} else { } else {
beaconInfoContent.lastLocationContent = content // when live is set to false, we use the id of the event that should have been replaced
event.unsignedData?.replacesState
} }
beaconInfoEntity.root?.content = ContentMapper.map(beaconInfoContent.toContent()) if (targetEventId.isNullOrEmpty()) {
Timber.w("no target event id found for the beacon content")
return
}
val aggregatedSummary = LiveLocationShareAggregatedSummaryEntity.getOrCreate(
realm = realm,
roomId = roomId,
eventId = targetEventId
)
Timber.d("updating summary of id=$targetEventId with isLive=${content.isLive}")
aggregatedSummary.endOfLiveTimestampMillis = content.getBestTimestampMillis()?.let { it + (content.timeout ?: 0) }
aggregatedSummary.isActive = content.isLive
} }
private fun isBeaconInfoOutdated(beaconInfoContent: LiveLocationBeaconContent, override fun handleBeaconLocationData(realm: Realm, event: Event, content: MessageBeaconLocationDataContent, roomId: String, isLocalEcho: Boolean) {
liveLocationContent: MessageLiveLocationContent): Boolean { if (event.senderId.isNullOrEmpty() || isLocalEcho) {
val beaconInfoStartTime = beaconInfoContent.getBestTimestampAsMilliseconds() ?: 0 return
val liveLocationEventTime = liveLocationContent.getBestTimestampAsMilliseconds() ?: 0 }
val timeout = beaconInfoContent.timeout ?: 0
return liveLocationEventTime - beaconInfoStartTime > timeout val targetEventId = content.relatesTo?.eventId
if (targetEventId.isNullOrEmpty()) {
Timber.w("no target event id found for the live location content")
return
}
val aggregatedSummary = LiveLocationShareAggregatedSummaryEntity.getOrCreate(
realm = realm,
roomId = roomId,
eventId = targetEventId
)
val updatedLocationTimestamp = content.getBestTimestampMillis() ?: 0
val currentLocationTimestamp = ContentMapper
.map(aggregatedSummary.lastLocationContent)
.toModel<MessageBeaconLocationDataContent>()
?.getBestTimestampMillis()
?: 0
if (updatedLocationTimestamp.isMoreRecentThan(currentLocationTimestamp)) {
Timber.d("updating last location of the summary of id=$targetEventId")
aggregatedSummary.lastLocationContent = ContentMapper.map(content.toContent())
}
} }
private fun Long.isMoreRecentThan(timestamp: Long) = this > timestamp
} }

View File

@ -18,12 +18,23 @@ package org.matrix.android.sdk.internal.session.room.aggregation.livelocation
import io.realm.Realm import io.realm.Realm
import org.matrix.android.sdk.api.session.events.model.Event import org.matrix.android.sdk.api.session.events.model.Event
import org.matrix.android.sdk.api.session.room.model.message.MessageLiveLocationContent import org.matrix.android.sdk.api.session.room.model.message.MessageBeaconInfoContent
import org.matrix.android.sdk.api.session.room.model.message.MessageBeaconLocationDataContent
internal interface LiveLocationAggregationProcessor { internal interface LiveLocationAggregationProcessor {
fun handleLiveLocation(realm: Realm, fun handleBeaconInfo(
event: Event, realm: Realm,
content: MessageLiveLocationContent, event: Event,
roomId: String, content: MessageBeaconInfoContent,
isLocalEcho: Boolean) roomId: String,
isLocalEcho: Boolean,
)
fun handleBeaconLocationData(
realm: Realm,
event: Event,
content: MessageBeaconLocationDataContent,
roomId: String,
isLocalEcho: Boolean,
)
} }

View File

@ -37,13 +37,13 @@ import org.matrix.android.sdk.api.session.room.model.message.LocationAsset
import org.matrix.android.sdk.api.session.room.model.message.LocationAssetType import org.matrix.android.sdk.api.session.room.model.message.LocationAssetType
import org.matrix.android.sdk.api.session.room.model.message.LocationInfo import org.matrix.android.sdk.api.session.room.model.message.LocationInfo
import org.matrix.android.sdk.api.session.room.model.message.MessageAudioContent import org.matrix.android.sdk.api.session.room.model.message.MessageAudioContent
import org.matrix.android.sdk.api.session.room.model.message.MessageBeaconLocationDataContent
import org.matrix.android.sdk.api.session.room.model.message.MessageContent import org.matrix.android.sdk.api.session.room.model.message.MessageContent
import org.matrix.android.sdk.api.session.room.model.message.MessageContentWithFormattedBody import org.matrix.android.sdk.api.session.room.model.message.MessageContentWithFormattedBody
import org.matrix.android.sdk.api.session.room.model.message.MessageEndPollContent import org.matrix.android.sdk.api.session.room.model.message.MessageEndPollContent
import org.matrix.android.sdk.api.session.room.model.message.MessageFileContent import org.matrix.android.sdk.api.session.room.model.message.MessageFileContent
import org.matrix.android.sdk.api.session.room.model.message.MessageFormat import org.matrix.android.sdk.api.session.room.model.message.MessageFormat
import org.matrix.android.sdk.api.session.room.model.message.MessageImageContent import org.matrix.android.sdk.api.session.room.model.message.MessageImageContent
import org.matrix.android.sdk.api.session.room.model.message.MessageLiveLocationContent
import org.matrix.android.sdk.api.session.room.model.message.MessageLocationContent import org.matrix.android.sdk.api.session.room.model.message.MessageLocationContent
import org.matrix.android.sdk.api.session.room.model.message.MessagePollContent 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.MessagePollResponseContent
@ -71,7 +71,6 @@ import org.matrix.android.sdk.internal.session.content.ThumbnailExtractor
import org.matrix.android.sdk.internal.session.permalinks.PermalinkFactory import org.matrix.android.sdk.internal.session.permalinks.PermalinkFactory
import org.matrix.android.sdk.internal.session.room.send.pills.TextPillsUtils import org.matrix.android.sdk.internal.session.room.send.pills.TextPillsUtils
import java.util.UUID import java.util.UUID
import java.util.concurrent.TimeUnit
import javax.inject.Inject import javax.inject.Inject
/** /**
@ -124,7 +123,8 @@ internal class LocalEchoEventFactory @Inject constructor(
newBodyAutoMarkdown: Boolean, newBodyAutoMarkdown: Boolean,
msgType: String, msgType: String,
compatibilityText: String): Event { compatibilityText: String): Event {
return createMessageEvent(roomId, return createMessageEvent(
roomId,
MessageTextContent( MessageTextContent(
msgType = msgType, msgType = msgType,
body = compatibilityText, body = compatibilityText,
@ -132,7 +132,8 @@ internal class LocalEchoEventFactory @Inject constructor(
newContent = createTextContent(newBodyText, newBodyAutoMarkdown) newContent = createTextContent(newBodyText, newBodyAutoMarkdown)
.toMessageTextContent(msgType) .toMessageTextContent(msgType)
.toContent() .toContent()
)) )
)
} }
private fun createPollContent(question: String, private fun createPollContent(question: String,
@ -188,7 +189,8 @@ internal class LocalEchoEventFactory @Inject constructor(
eventId = localId, eventId = localId,
type = EventType.POLL_RESPONSE.first(), type = EventType.POLL_RESPONSE.first(),
content = content.toContent(), content = content.toContent(),
unsignedData = UnsignedData(age = null, transactionId = localId)) unsignedData = UnsignedData(age = null, transactionId = localId)
)
} }
fun createPollEvent(roomId: String, fun createPollEvent(roomId: String,
@ -204,7 +206,8 @@ internal class LocalEchoEventFactory @Inject constructor(
eventId = localId, eventId = localId,
type = EventType.POLL_START.first(), type = EventType.POLL_START.first(),
content = content.toContent(), content = content.toContent(),
unsignedData = UnsignedData(age = null, transactionId = localId)) unsignedData = UnsignedData(age = null, transactionId = localId)
)
} }
fun createEndPollEvent(roomId: String, fun createEndPollEvent(roomId: String,
@ -223,7 +226,8 @@ internal class LocalEchoEventFactory @Inject constructor(
eventId = localId, eventId = localId,
type = EventType.POLL_END.first(), type = EventType.POLL_END.first(),
content = content.toContent(), content = content.toContent(),
unsignedData = UnsignedData(age = null, transactionId = localId)) unsignedData = UnsignedData(age = null, transactionId = localId)
)
} }
fun createLocationEvent(roomId: String, fun createLocationEvent(roomId: String,
@ -238,7 +242,7 @@ internal class LocalEchoEventFactory @Inject constructor(
body = geoUri, body = geoUri,
unstableLocationInfo = LocationInfo(geoUri = geoUri, description = geoUri), unstableLocationInfo = LocationInfo(geoUri = geoUri, description = geoUri),
unstableLocationAsset = LocationAsset(type = assetType), unstableLocationAsset = LocationAsset(type = assetType),
unstableTs = TimeUnit.MILLISECONDS.toSeconds(System.currentTimeMillis()), unstableTimestampMillis = System.currentTimeMillis(),
unstableText = geoUri unstableText = geoUri
) )
return createMessageEvent(roomId, content) return createMessageEvent(roomId, content)
@ -250,14 +254,14 @@ internal class LocalEchoEventFactory @Inject constructor(
longitude: Double, longitude: Double,
uncertainty: Double?): Event { uncertainty: Double?): Event {
val geoUri = buildGeoUri(latitude, longitude, uncertainty) val geoUri = buildGeoUri(latitude, longitude, uncertainty)
val content = MessageLiveLocationContent( val content = MessageBeaconLocationDataContent(
body = geoUri, body = geoUri,
relatesTo = RelationDefaultContent( relatesTo = RelationDefaultContent(
type = RelationType.REFERENCE, type = RelationType.REFERENCE,
eventId = beaconInfoEventId eventId = beaconInfoEventId
), ),
unstableLocationInfo = LocationInfo(geoUri = geoUri, description = geoUri), unstableLocationInfo = LocationInfo(geoUri = geoUri, description = geoUri),
unstableTimestampAsMilliseconds = TimeUnit.MILLISECONDS.toSeconds(System.currentTimeMillis()), unstableTimestampMillis = System.currentTimeMillis(),
) )
val localId = LocalEcho.createLocalEchoId() val localId = LocalEcho.createLocalEchoId()
return Event( return Event(
@ -267,7 +271,8 @@ internal class LocalEchoEventFactory @Inject constructor(
eventId = localId, eventId = localId,
type = EventType.BEACON_LOCATION_DATA.first(), type = EventType.BEACON_LOCATION_DATA.first(),
content = content.toContent(), content = content.toContent(),
unsignedData = UnsignedData(age = null, transactionId = localId)) unsignedData = UnsignedData(age = null, transactionId = localId)
)
} }
fun createReplaceTextOfReply(roomId: String, fun createReplaceTextOfReply(roomId: String,
@ -297,7 +302,8 @@ internal class LocalEchoEventFactory @Inject constructor(
// //
val replyFallback = buildReplyFallback(body, originalEvent.root.senderId ?: "", newBodyText) val replyFallback = buildReplyFallback(body, originalEvent.root.senderId ?: "", newBodyText)
return createMessageEvent(roomId, return createMessageEvent(
roomId,
MessageTextContent( MessageTextContent(
msgType = msgType, msgType = msgType,
body = compatibilityText, body = compatibilityText,
@ -309,7 +315,8 @@ internal class LocalEchoEventFactory @Inject constructor(
formattedBody = replyFormatted formattedBody = replyFormatted
) )
.toContent() .toContent()
)) )
)
} }
fun createMediaEvent(roomId: String, fun createMediaEvent(roomId: String,
@ -341,7 +348,8 @@ internal class LocalEchoEventFactory @Inject constructor(
eventId = localId, eventId = localId,
type = EventType.REACTION, type = EventType.REACTION,
content = content.toContent(), content = content.toContent(),
unsignedData = UnsignedData(age = null, transactionId = localId)) unsignedData = UnsignedData(age = null, transactionId = localId)
)
} }
private fun createImageEvent(roomId: String, attachment: ContentAttachmentData, rootThreadEventId: String?): Event { private fun createImageEvent(roomId: String, attachment: ContentAttachmentData, rootThreadEventId: String?): Event {
@ -532,8 +540,10 @@ internal class LocalEchoEventFactory @Inject constructor(
content.toThreadTextContent( content.toThreadTextContent(
rootThreadEventId = rootThreadEventId, rootThreadEventId = rootThreadEventId,
latestThreadEventId = localEchoRepository.getLatestThreadEvent(rootThreadEventId), latestThreadEventId = localEchoRepository.getLatestThreadEvent(rootThreadEventId),
msgType = msgType) msgType = msgType
.toContent()) )
.toContent()
)
} }
private fun dummyOriginServerTs(): Long { private fun dummyOriginServerTs(): Long {
@ -582,7 +592,9 @@ internal class LocalEchoEventFactory @Inject constructor(
relatesTo = generateReplyRelationContent( relatesTo = generateReplyRelationContent(
eventId = eventId, eventId = eventId,
rootThreadEventId = rootThreadEventId, rootThreadEventId = rootThreadEventId,
showInThread = showInThread)) showInThread = showInThread
)
)
return createMessageEvent(roomId, content) return createMessageEvent(roomId, content)
} }
@ -605,7 +617,8 @@ internal class LocalEchoEventFactory @Inject constructor(
eventId = it, eventId = it,
isFallingBack = showInThread, isFallingBack = showInThread,
// False when is a rich reply from within a thread, and true when is a reply that should be visible from threads // False when is a rich reply from within a thread, and true when is a reply that should be visible from threads
inReplyTo = ReplyToContent(eventId = eventId)) inReplyTo = ReplyToContent(eventId = eventId)
)
} ?: RelationDefaultContent(null, null, ReplyToContent(eventId = eventId)) } ?: RelationDefaultContent(null, null, ReplyToContent(eventId = eventId))
private fun buildFormattedReply(permalink: String, userLink: String, userId: String, bodyFormatted: String, newBodyFormatted: String): String { private fun buildFormattedReply(permalink: String, userLink: String, userId: String, bodyFormatted: String, newBodyFormatted: String): String {
@ -740,13 +753,15 @@ internal class LocalEchoEventFactory @Inject constructor(
.toThreadTextContent( .toThreadTextContent(
rootThreadEventId = rootThreadEventId, rootThreadEventId = rootThreadEventId,
latestThreadEventId = localEchoRepository.getLatestThreadEvent(rootThreadEventId), latestThreadEventId = localEchoRepository.getLatestThreadEvent(rootThreadEventId),
msgType = MessageType.MSGTYPE_TEXT) msgType = MessageType.MSGTYPE_TEXT
)
) )
} else { } else {
createFormattedTextEvent( createFormattedTextEvent(
roomId, roomId,
markdownParser.parse(quoteText, force = true, advanced = autoMarkdown), markdownParser.parse(quoteText, force = true, advanced = autoMarkdown),
MessageType.MSGTYPE_TEXT) MessageType.MSGTYPE_TEXT
)
} }
} }

View File

@ -33,7 +33,7 @@ import org.matrix.android.sdk.api.session.room.model.RoomHistoryVisibility
import org.matrix.android.sdk.api.session.room.model.RoomJoinRules import org.matrix.android.sdk.api.session.room.model.RoomJoinRules
import org.matrix.android.sdk.api.session.room.model.RoomJoinRulesAllowEntry import org.matrix.android.sdk.api.session.room.model.RoomJoinRulesAllowEntry
import org.matrix.android.sdk.api.session.room.model.RoomJoinRulesContent import org.matrix.android.sdk.api.session.room.model.RoomJoinRulesContent
import org.matrix.android.sdk.api.session.room.model.livelocation.LiveLocationBeaconContent import org.matrix.android.sdk.api.session.room.model.message.MessageBeaconInfoContent
import org.matrix.android.sdk.api.session.room.state.StateService import org.matrix.android.sdk.api.session.room.state.StateService
import org.matrix.android.sdk.api.util.JsonDict import org.matrix.android.sdk.api.util.JsonDict
import org.matrix.android.sdk.api.util.MimeTypes import org.matrix.android.sdk.api.util.MimeTypes
@ -192,7 +192,7 @@ internal class DefaultStateService @AssistedInject constructor(@Assisted private
override suspend fun stopLiveLocation(userId: String) { override suspend fun stopLiveLocation(userId: String) {
getLiveLocationBeaconInfo(userId, true)?.let { beaconInfoStateEvent -> getLiveLocationBeaconInfo(userId, true)?.let { beaconInfoStateEvent ->
beaconInfoStateEvent.getClearContent()?.toModel<LiveLocationBeaconContent>()?.let { content -> beaconInfoStateEvent.getClearContent()?.toModel<MessageBeaconInfoContent>()?.let { content ->
val updatedContent = content.copy(isLive = false).toContent() val updatedContent = content.copy(isLive = false).toContent()
beaconInfoStateEvent.stateKey?.let { beaconInfoStateEvent.stateKey?.let {
@ -217,7 +217,7 @@ internal class DefaultStateService @AssistedInject constructor(@Assisted private
} }
.firstOrNull { beaconInfoEvent -> .firstOrNull { beaconInfoEvent ->
!filterOnlyLive || !filterOnlyLive ||
beaconInfoEvent.getClearContent()?.toModel<LiveLocationBeaconContent>()?.isLive.orFalse() beaconInfoEvent.getClearContent()?.toModel<MessageBeaconInfoContent>()?.isLive.orFalse()
} }
} }
} }

View File

@ -24,7 +24,7 @@ import im.vector.app.features.home.room.detail.timeline.item.AbsMessageItem
import im.vector.app.features.home.room.detail.timeline.item.MessageLiveLocationStartItem import im.vector.app.features.home.room.detail.timeline.item.MessageLiveLocationStartItem
import im.vector.app.features.home.room.detail.timeline.item.MessageLiveLocationStartItem_ import im.vector.app.features.home.room.detail.timeline.item.MessageLiveLocationStartItem_
import org.matrix.android.sdk.api.extensions.orFalse import org.matrix.android.sdk.api.extensions.orFalse
import org.matrix.android.sdk.api.session.room.model.livelocation.LiveLocationBeaconContent import org.matrix.android.sdk.api.session.room.model.message.MessageBeaconInfoContent
import javax.inject.Inject import javax.inject.Inject
class LiveLocationMessageItemFactory @Inject constructor( class LiveLocationMessageItemFactory @Inject constructor(
@ -34,19 +34,20 @@ class LiveLocationMessageItemFactory @Inject constructor(
) { ) {
fun create( fun create(
liveLocationContent: LiveLocationBeaconContent, beaconInfoContent: MessageBeaconInfoContent,
highlight: Boolean, highlight: Boolean,
attributes: AbsMessageItem.Attributes, attributes: AbsMessageItem.Attributes,
): VectorEpoxyModel<*>? { ): VectorEpoxyModel<*>? {
// TODO handle location received and stopped states // TODO handle location received and stopped states
return when { return when {
isLiveRunning(liveLocationContent) -> buildStartLiveItem(highlight, attributes) isLiveRunning(beaconInfoContent) -> buildStartLiveItem(highlight, attributes)
else -> null else -> null
} }
} }
private fun isLiveRunning(liveLocationContent: LiveLocationBeaconContent): Boolean { private fun isLiveRunning(beaconInfoContent: MessageBeaconInfoContent): Boolean {
return liveLocationContent.isLive.orFalse() && liveLocationContent.hasTimedOut.not() // TODO when we will use aggregatedSummary, check if the live has timed out as well
return beaconInfoContent.isLive.orFalse()
} }
private fun buildStartLiveItem( private fun buildStartLiveItem(

View File

@ -98,8 +98,8 @@ import org.matrix.android.sdk.api.session.events.model.RelationType
import org.matrix.android.sdk.api.session.events.model.content.EncryptedEventContent import org.matrix.android.sdk.api.session.events.model.content.EncryptedEventContent
import org.matrix.android.sdk.api.session.events.model.isThread import org.matrix.android.sdk.api.session.events.model.isThread
import org.matrix.android.sdk.api.session.events.model.toModel import org.matrix.android.sdk.api.session.events.model.toModel
import org.matrix.android.sdk.api.session.room.model.livelocation.LiveLocationBeaconContent
import org.matrix.android.sdk.api.session.room.model.message.MessageAudioContent import org.matrix.android.sdk.api.session.room.model.message.MessageAudioContent
import org.matrix.android.sdk.api.session.room.model.message.MessageBeaconInfoContent
import org.matrix.android.sdk.api.session.room.model.message.MessageContent import org.matrix.android.sdk.api.session.room.model.message.MessageContent
import org.matrix.android.sdk.api.session.room.model.message.MessageContentWithFormattedBody import org.matrix.android.sdk.api.session.room.model.message.MessageContentWithFormattedBody
import org.matrix.android.sdk.api.session.room.model.message.MessageEmoteContent import org.matrix.android.sdk.api.session.room.model.message.MessageEmoteContent
@ -207,15 +207,15 @@ class MessageItemFactory @Inject constructor(
is MessageAudioContent -> buildAudioContent(params, messageContent, informationData, highlight, attributes) is MessageAudioContent -> buildAudioContent(params, messageContent, informationData, highlight, attributes)
is MessageVerificationRequestContent -> buildVerificationRequestMessageItem(messageContent, informationData, highlight, callback, attributes) is MessageVerificationRequestContent -> buildVerificationRequestMessageItem(messageContent, informationData, highlight, callback, attributes)
is MessagePollContent -> buildPollItem(messageContent, informationData, highlight, callback, attributes) is MessagePollContent -> buildPollItem(messageContent, informationData, highlight, callback, attributes)
is MessageLocationContent -> { is MessageLocationContent -> {
if (vectorPreferences.labsRenderLocationsInTimeline()) { if (vectorPreferences.labsRenderLocationsInTimeline()) {
buildLocationItem(messageContent, informationData, highlight, attributes) buildLocationItem(messageContent, informationData, highlight, attributes)
} else { } else {
buildMessageTextItem(messageContent.body, false, informationData, highlight, callback, attributes) buildMessageTextItem(messageContent.body, false, informationData, highlight, callback, attributes)
} }
} }
is LiveLocationBeaconContent -> liveLocationMessageItemFactory.create(messageContent, highlight, attributes) is MessageBeaconInfoContent -> liveLocationMessageItemFactory.create(messageContent, highlight, attributes)
else -> buildNotHandledMessageItem(messageContent, informationData, highlight, callback, attributes) else -> buildNotHandledMessageItem(messageContent, informationData, highlight, callback, attributes)
} }
return messageItem?.apply { return messageItem?.apply {
layout(informationData.messageLayout.layoutRes) layout(informationData.messageLayout.layoutRes)

View File

@ -59,12 +59,12 @@ class TimelineMessageLayoutFactory @Inject constructor(private val session: Sess
MessageType.MSGTYPE_VIDEO, MessageType.MSGTYPE_VIDEO,
MessageType.MSGTYPE_STICKER_LOCAL, MessageType.MSGTYPE_STICKER_LOCAL,
MessageType.MSGTYPE_EMOTE, MessageType.MSGTYPE_EMOTE,
MessageType.MSGTYPE_LIVE_LOCATION_STATE, MessageType.MSGTYPE_BEACON_INFO,
) )
private val MSG_TYPES_WITH_TIMESTAMP_INSIDE_MESSAGE = setOf( private val MSG_TYPES_WITH_TIMESTAMP_INSIDE_MESSAGE = setOf(
MessageType.MSGTYPE_IMAGE, MessageType.MSGTYPE_IMAGE,
MessageType.MSGTYPE_VIDEO, MessageType.MSGTYPE_VIDEO,
MessageType.MSGTYPE_LIVE_LOCATION_STATE, MessageType.MSGTYPE_BEACON_INFO,
) )
} }
@ -151,8 +151,8 @@ class TimelineMessageLayoutFactory @Inject constructor(private val session: Sess
private fun MessageContent?.shouldAddMessageOverlay(): Boolean { private fun MessageContent?.shouldAddMessageOverlay(): Boolean {
return when { return when {
this == null || msgType == MessageType.MSGTYPE_LIVE_LOCATION_STATE -> false this == null || msgType == MessageType.MSGTYPE_BEACON_INFO -> false
msgType == MessageType.MSGTYPE_LOCATION -> vectorPreferences.labsRenderLocationsInTimeline() msgType == MessageType.MSGTYPE_LOCATION -> vectorPreferences.labsRenderLocationsInTimeline()
else -> msgType in MSG_TYPES_WITH_TIMESTAMP_INSIDE_MESSAGE else -> msgType in MSG_TYPES_WITH_TIMESTAMP_INSIDE_MESSAGE
} }
} }

View File

@ -32,7 +32,7 @@ import org.matrix.android.sdk.api.session.Session
import org.matrix.android.sdk.api.session.events.model.EventType import org.matrix.android.sdk.api.session.events.model.EventType
import org.matrix.android.sdk.api.session.events.model.toContent import org.matrix.android.sdk.api.session.events.model.toContent
import org.matrix.android.sdk.api.session.getRoom import org.matrix.android.sdk.api.session.getRoom
import org.matrix.android.sdk.api.session.room.model.livelocation.LiveLocationBeaconContent import org.matrix.android.sdk.api.session.room.model.message.MessageBeaconInfoContent
import timber.log.Timber import timber.log.Timber
import java.util.Timer import java.util.Timer
import java.util.TimerTask import java.util.TimerTask
@ -96,10 +96,10 @@ class LocationSharingService : VectorService(), LocationTracker.Callback {
} }
private suspend fun sendLiveBeaconInfo(session: Session, roomArgs: RoomArgs) { private suspend fun sendLiveBeaconInfo(session: Session, roomArgs: RoomArgs) {
val beaconContent = LiveLocationBeaconContent( val beaconContent = MessageBeaconInfoContent(
timeout = roomArgs.durationMillis, timeout = roomArgs.durationMillis,
isLive = true, isLive = true,
unstableTimestampAsMilliseconds = clock.epochMillis() unstableTimestampMillis = clock.epochMillis()
).toContent() ).toContent()
val stateKey = session.myUserId val stateKey = session.myUserId