diff --git a/changelog.d/5862.wip b/changelog.d/5862.wip new file mode 100644 index 0000000000..303a054c5e --- /dev/null +++ b/changelog.d/5862.wip @@ -0,0 +1 @@ +[Live location sharing] Improve aggregation process of events diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/EventAnnotationsSummary.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/EventAnnotationsSummary.kt index a8140f3fc9..0aded20339 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/EventAnnotationsSummary.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/EventAnnotationsSummary.kt @@ -15,9 +15,12 @@ */ package org.matrix.android.sdk.api.session.room.model +import org.matrix.android.sdk.api.session.room.model.livelocation.LiveLocationShareAggregatedSummary + data class EventAnnotationsSummary( val reactionsSummary: List = emptyList(), val editSummary: EditAggregatedSummary? = null, val pollResponseSummary: PollResponseAggregatedSummary? = null, - val referencesAggregatedSummary: ReferencesAggregatedSummary? = null + val referencesAggregatedSummary: ReferencesAggregatedSummary? = null, + val liveLocationShareAggregatedSummary: LiveLocationShareAggregatedSummary? = null, ) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/livelocation/LiveLocationShareAggregatedSummary.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/livelocation/LiveLocationShareAggregatedSummary.kt new file mode 100644 index 0000000000..0b28d62f56 --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/livelocation/LiveLocationShareAggregatedSummary.kt @@ -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?, +) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/livelocation/LiveLocationBeaconContent.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/MessageBeaconInfoContent.kt similarity index 66% rename from matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/livelocation/LiveLocationBeaconContent.kt rename to matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/MessageBeaconInfoContent.kt index a7c78f6e80..f75704a891 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/livelocation/LiveLocationBeaconContent.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/MessageBeaconInfoContent.kt @@ -14,25 +14,28 @@ * 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.JsonClass 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 +/** + * 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) -data class LiveLocationBeaconContent( +data class MessageBeaconInfoContent( /** * Local message type, not from server */ @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 = "m.relates_to") override val relatesTo: RelationDefaultContent? = null, @@ -54,26 +57,16 @@ data class LiveLocationBeaconContent( /** * Beacon creation timestamp. */ - @Json(name = "org.matrix.msc3488.ts") val unstableTimestampAsMilliseconds: Long? = null, - @Json(name = "m.ts") val timestampAsMilliseconds: Long? = null, + @Json(name = "org.matrix.msc3488.ts") val unstableTimestampMillis: Long? = null, + @Json(name = "m.ts") val timestampMillis: Long? = null, /** * Live location asset type. */ @Json(name = "org.matrix.msc3488.asset") val unstableLocationAsset: LocationAsset = LocationAsset(LocationAssetType.SELF), @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 { - fun getBestTimestampAsMilliseconds() = timestampAsMilliseconds ?: unstableTimestampAsMilliseconds + fun getBestTimestampMillis() = timestampMillis ?: unstableTimestampMillis fun getBestLocationAsset() = locationAsset ?: unstableLocationAsset } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/MessageLiveLocationContent.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/MessageBeaconLocationDataContent.kt similarity index 72% rename from matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/MessageLiveLocationContent.kt rename to matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/MessageBeaconLocationDataContent.kt index 548dc85369..4a4ef46bc8 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/MessageLiveLocationContent.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/MessageBeaconLocationDataContent.kt @@ -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.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) -data class MessageLiveLocationContent( +data class MessageBeaconLocationDataContent( /** * Local message type, not from server */ @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 = "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) */ - @Json(name = "org.matrix.msc3488.ts") val unstableTimestampAsMilliseconds: Long? = null, - @Json(name = "m.ts") val timestampAsMilliseconds: Long? = null + @Json(name = "org.matrix.msc3488.ts") val unstableTimestampMillis: Long? = null, + @Json(name = "m.ts") val timestampMillis: Long? = null ) : MessageContent { fun getBestLocationInfo() = locationInfo ?: unstableLocationInfo - fun getBestTimestampAsMilliseconds() = timestampAsMilliseconds ?: unstableTimestampAsMilliseconds + fun getBestTimestampMillis() = timestampMillis ?: unstableTimestampMillis } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/MessageLocationContent.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/MessageLocationContent.kt index 2052133b06..19cb20430d 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/MessageLocationContent.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/MessageLocationContent.kt @@ -49,8 +49,8 @@ data class MessageLocationContent( /** * 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 = "m.ts") val ts: Long? = null, + @Json(name = "org.matrix.msc3488.ts") val unstableTimestampMillis: Long? = null, + @Json(name = "m.ts") val timestampMillis: Long? = null, @Json(name = "org.matrix.msc1767.text") val unstableText: String? = null, @Json(name = "m.text") val text: String? = null, /** @@ -66,7 +66,7 @@ data class MessageLocationContent( fun getBestLocationInfo() = locationInfo ?: unstableLocationInfo - fun getBestTs() = ts ?: unstableTs + fun getBestTimestampMillis() = timestampMillis ?: unstableTimestampMillis fun getBestText() = text ?: unstableText diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/MessageType.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/MessageType.kt index 106bf2e030..b12d9ed6c8 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/MessageType.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/MessageType.kt @@ -41,6 +41,6 @@ object MessageType { const val MSGTYPE_SNOWFALL = "io.element.effect.snowfall" // 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_LIVE_LOCATION = "org.matrix.android.sdk.livelocation" + const val MSGTYPE_BEACON_INFO = "org.matrix.android.sdk.beacon.info" + const val MSGTYPE_BEACON_LOCATION_DATA = "org.matrix.android.sdk.beacon.location.data" } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/timeline/TimelineEvent.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/timeline/TimelineEvent.kt index a2ae8bfeb5..adbc8ab12a 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/timeline/TimelineEvent.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/timeline/TimelineEvent.kt @@ -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.room.model.EventAnnotationsSummary 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.MessagePollContent import org.matrix.android.sdk.api.session.room.model.message.MessageStickerContent @@ -139,7 +139,7 @@ fun TimelineEvent.getLastMessageContent(): MessageContent? { return when (root.getClearType()) { EventType.STICKER -> root.getClearContent().toModel() in EventType.POLL_START -> (annotations?.editSummary?.latestContent ?: root.getClearContent()).toModel() - in EventType.STATE_ROOM_BEACON_INFO -> (annotations?.editSummary?.latestContent ?: root.getClearContent()).toModel() + in EventType.STATE_ROOM_BEACON_INFO -> (annotations?.editSummary?.latestContent ?: root.getClearContent()).toModel() else -> (annotations?.editSummary?.latestContent ?: root.getClearContent()).toModel() } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/RealmSessionStoreMigration.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/RealmSessionStoreMigration.kt index a57397dad5..24ac310653 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/RealmSessionStoreMigration.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/RealmSessionStoreMigration.kt @@ -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.MigrateSessionTo025 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 timber.log.Timber import javax.inject.Inject @@ -58,7 +59,7 @@ internal class RealmSessionStoreMigration @Inject constructor( override fun equals(other: Any?) = other is RealmSessionStoreMigration override fun hashCode() = 1000 - val schemaVersion = 26L + val schemaVersion = 27L override fun migrate(realm: DynamicRealm, oldVersion: Long, newVersion: Long) { 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 < 25) MigrateSessionTo025(realm).perform() if (oldVersion < 26) MigrateSessionTo026(realm).perform() + if (oldVersion < 27) MigrateSessionTo027(realm).perform() } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/mapper/EventAnnotationsSummaryMapper.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/mapper/EventAnnotationsSummaryMapper.kt index b32d486128..c747ad334f 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/mapper/EventAnnotationsSummaryMapper.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/mapper/EventAnnotationsSummaryMapper.kt @@ -56,8 +56,10 @@ internal object EventAnnotationsSummaryMapper { }, pollResponseSummary = annotationsSummary.pollResponseSummary?.let { PollResponseAggregatedSummaryEntityMapper.map(it) + }, + liveLocationShareAggregatedSummary = annotationsSummary.liveLocationShareAggregatedSummary?.let { + LiveLocationShareAggregatedSummaryMapper.map(it) } - ) } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/mapper/LiveLocationShareAggregatedSummaryMapper.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/mapper/LiveLocationShareAggregatedSummaryMapper.kt new file mode 100644 index 0000000000..71b36f88bd --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/mapper/LiveLocationShareAggregatedSummaryMapper.kt @@ -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() + ) + } +} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/migration/MigrateSessionTo027.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/migration/MigrateSessionTo027.kt new file mode 100644 index 0000000000..fdd8c46d02 --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/migration/MigrateSessionTo027.kt @@ -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) + } +} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/EventAnnotationsSummaryEntity.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/EventAnnotationsSummaryEntity.kt index 3e88130420..c3abd8b028 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/EventAnnotationsSummaryEntity.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/EventAnnotationsSummaryEntity.kt @@ -18,6 +18,7 @@ package org.matrix.android.sdk.internal.database.model import io.realm.RealmList import io.realm.RealmObject import io.realm.annotations.PrimaryKey +import org.matrix.android.sdk.internal.database.model.livelocation.LiveLocationShareAggregatedSummaryEntity import timber.log.Timber internal open class EventAnnotationsSummaryEntity( @@ -27,7 +28,8 @@ internal open class EventAnnotationsSummaryEntity( var reactionsSummary: RealmList = RealmList(), var editSummary: EditAggregatedSummaryEntity? = null, var referencesSummaryEntity: ReferencesAggregatedSummaryEntity? = null, - var pollResponseSummary: PollResponseAggregatedSummaryEntity? = null + var pollResponseSummary: PollResponseAggregatedSummaryEntity? = null, + var liveLocationShareAggregatedSummary: LiveLocationShareAggregatedSummaryEntity? = null, ) : RealmObject() { /** diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/SessionRealmModule.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/SessionRealmModule.kt index d0d23dd491..9a92b14510 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/SessionRealmModule.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/SessionRealmModule.kt @@ -17,6 +17,7 @@ package org.matrix.android.sdk.internal.database.model 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.threads.ThreadSummaryEntity @@ -47,6 +48,7 @@ import org.matrix.android.sdk.internal.database.model.threads.ThreadSummaryEntit EditAggregatedSummaryEntity::class, EditionOfEvent::class, PollResponseAggregatedSummaryEntity::class, + LiveLocationShareAggregatedSummaryEntity::class, ReferencesAggregatedSummaryEntity::class, PushRulesEntity::class, PushRuleEntity::class, diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/livelocation/LiveLocationShareAggregatedSummaryEntity.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/livelocation/LiveLocationShareAggregatedSummaryEntity.kt new file mode 100644 index 0000000000..1376839f93 --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/model/livelocation/LiveLocationShareAggregatedSummaryEntity.kt @@ -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 +} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/query/LiveLocationShareAggregatedSummaryEntityQuery.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/query/LiveLocationShareAggregatedSummaryEntityQuery.kt new file mode 100644 index 0000000000..2e2e939fa2 --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/query/LiveLocationShareAggregatedSummaryEntityQuery.kt @@ -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 { + return realm.where() + .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) +} 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 ac944ea8a7..753dd149ae 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 @@ -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.VoteInfo 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.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 @@ -91,7 +92,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.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 { return allowedTypes.contains(eventType) @@ -106,12 +107,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) @@ -137,7 +138,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) { @@ -146,7 +147,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 || @@ -189,8 +190,8 @@ internal class EventRelationsAggregationProcessor @Inject constructor( } } in EventType.BEACON_LOCATION_DATA -> { - event.content.toModel(catchError = true)?.let { - liveLocationAggregationProcessor.handleLiveLocation(realm, event, it, roomId, isLocalEcho) + event.getClearContent().toModel(catchError = true)?.let { + 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() } ?: return 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() if (content?.relatesTo?.type == RelationType.REPLACE) { 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) } } - 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) } } - in EventType.BEACON_LOCATION_DATA -> { - event.content.toModel(catchError = true)?.let { - liveLocationAggregationProcessor.handleLiveLocation(realm, event, it, roomId, isLocalEcho) + in EventType.STATE_ROOM_BEACON_INFO -> { + event.content.toModel(catchError = true)?.let { + liveLocationAggregationProcessor.handleBeaconInfo(realm, event, it, roomId, isLocalEcho) } } - else -> Timber.v("UnHandled event ${event.eventId}") + else -> Timber.v("UnHandled event ${event.eventId}") } } catch (t: Throwable) { Timber.e(t, "## Should not happen ") @@ -325,7 +326,8 @@ internal class EventRelationsAggregationProcessor @Inject constructor( totalVotes = 0, winnerVoteCount = 0, ) - .toContent()) + .toContent() + ) } val txId = event.unsignedData?.transactionId @@ -729,11 +731,13 @@ internal class EventRelationsAggregationProcessor @Inject constructor( EventType.KEY_VERIFICATION_READY, EventType.KEY_VERIFICATION_KEY, EventType.KEY_VERIFICATION_MAC -> currentState.toState(VerificationState.WAITING) - EventType.KEY_VERIFICATION_CANCEL -> currentState.toState(if (event.senderId == userId) { - VerificationState.CANCELED_BY_ME - } else { - VerificationState.CANCELED_BY_OTHER - }) + EventType.KEY_VERIFICATION_CANCEL -> currentState.toState( + if (event.senderId == userId) { + VerificationState.CANCELED_BY_ME + } else { + VerificationState.CANCELED_BY_OTHER + } + ) EventType.KEY_VERIFICATION_DONE -> currentState.toState(VerificationState.DONE) else -> VerificationState.REQUEST } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/aggregation/livelocation/DefaultLiveLocationAggregationProcessor.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/aggregation/livelocation/DefaultLiveLocationAggregationProcessor.kt index 8de0965b40..997e31a109 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/aggregation/livelocation/DefaultLiveLocationAggregationProcessor.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/aggregation/livelocation/DefaultLiveLocationAggregationProcessor.kt @@ -17,69 +17,78 @@ package org.matrix.android.sdk.internal.session.room.aggregation.livelocation 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.EventType 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.room.model.livelocation.LiveLocationBeaconContent -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 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.query.getOrNull +import org.matrix.android.sdk.internal.database.model.livelocation.LiveLocationShareAggregatedSummaryEntity +import org.matrix.android.sdk.internal.database.query.getOrCreate import timber.log.Timber import javax.inject.Inject internal class DefaultLiveLocationAggregationProcessor @Inject constructor() : LiveLocationAggregationProcessor { - override fun handleLiveLocation(realm: Realm, event: Event, content: MessageLiveLocationContent, roomId: String, isLocalEcho: Boolean) { - val locationSenderId = event.senderId ?: return - - // We shouldn't process local echos - if (isLocalEcho) { + override fun handleBeaconInfo(realm: Realm, event: Event, content: MessageBeaconInfoContent, roomId: String, isLocalEcho: Boolean) { + if (event.senderId.isNullOrEmpty() || isLocalEcho) { return } - // A beacon info state event has to be sent before sending location - // TODO handle missing check of m_relatesTo field - 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(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 + val targetEventId = if (content.isLive.orTrue()) { + event.eventId } 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, - liveLocationContent: MessageLiveLocationContent): Boolean { - val beaconInfoStartTime = beaconInfoContent.getBestTimestampAsMilliseconds() ?: 0 - val liveLocationEventTime = liveLocationContent.getBestTimestampAsMilliseconds() ?: 0 - val timeout = beaconInfoContent.timeout ?: 0 - return liveLocationEventTime - beaconInfoStartTime > timeout + override fun handleBeaconLocationData(realm: Realm, event: Event, content: MessageBeaconLocationDataContent, roomId: String, isLocalEcho: Boolean) { + if (event.senderId.isNullOrEmpty() || isLocalEcho) { + return + } + + 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() + ?.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 } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/aggregation/livelocation/LiveLocationAggregationProcessor.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/aggregation/livelocation/LiveLocationAggregationProcessor.kt index 7b5f23e243..c0be96f83d 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/aggregation/livelocation/LiveLocationAggregationProcessor.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/aggregation/livelocation/LiveLocationAggregationProcessor.kt @@ -18,12 +18,23 @@ package org.matrix.android.sdk.internal.session.room.aggregation.livelocation import io.realm.Realm 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 { - fun handleLiveLocation(realm: Realm, - event: Event, - content: MessageLiveLocationContent, - roomId: String, - isLocalEcho: Boolean) + fun handleBeaconInfo( + realm: Realm, + event: Event, + content: MessageBeaconInfoContent, + roomId: String, + isLocalEcho: Boolean, + ) + + fun handleBeaconLocationData( + realm: Realm, + event: Event, + content: MessageBeaconLocationDataContent, + roomId: String, + isLocalEcho: Boolean, + ) } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/LocalEchoEventFactory.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/LocalEchoEventFactory.kt index bb16563f96..0d75a375cc 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/LocalEchoEventFactory.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/LocalEchoEventFactory.kt @@ -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.LocationInfo 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.MessageContentWithFormattedBody 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.MessageFormat 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.MessagePollContent 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.room.send.pills.TextPillsUtils import java.util.UUID -import java.util.concurrent.TimeUnit import javax.inject.Inject /** @@ -124,7 +123,8 @@ internal class LocalEchoEventFactory @Inject constructor( newBodyAutoMarkdown: Boolean, msgType: String, compatibilityText: String): Event { - return createMessageEvent(roomId, + return createMessageEvent( + roomId, MessageTextContent( msgType = msgType, body = compatibilityText, @@ -132,7 +132,8 @@ internal class LocalEchoEventFactory @Inject constructor( newContent = createTextContent(newBodyText, newBodyAutoMarkdown) .toMessageTextContent(msgType) .toContent() - )) + ) + ) } private fun createPollContent(question: String, @@ -188,7 +189,8 @@ internal class LocalEchoEventFactory @Inject constructor( eventId = localId, type = EventType.POLL_RESPONSE.first(), content = content.toContent(), - unsignedData = UnsignedData(age = null, transactionId = localId)) + unsignedData = UnsignedData(age = null, transactionId = localId) + ) } fun createPollEvent(roomId: String, @@ -204,7 +206,8 @@ internal class LocalEchoEventFactory @Inject constructor( eventId = localId, type = EventType.POLL_START.first(), content = content.toContent(), - unsignedData = UnsignedData(age = null, transactionId = localId)) + unsignedData = UnsignedData(age = null, transactionId = localId) + ) } fun createEndPollEvent(roomId: String, @@ -223,7 +226,8 @@ internal class LocalEchoEventFactory @Inject constructor( eventId = localId, type = EventType.POLL_END.first(), content = content.toContent(), - unsignedData = UnsignedData(age = null, transactionId = localId)) + unsignedData = UnsignedData(age = null, transactionId = localId) + ) } fun createLocationEvent(roomId: String, @@ -238,7 +242,7 @@ internal class LocalEchoEventFactory @Inject constructor( body = geoUri, unstableLocationInfo = LocationInfo(geoUri = geoUri, description = geoUri), unstableLocationAsset = LocationAsset(type = assetType), - unstableTs = TimeUnit.MILLISECONDS.toSeconds(System.currentTimeMillis()), + unstableTimestampMillis = System.currentTimeMillis(), unstableText = geoUri ) return createMessageEvent(roomId, content) @@ -250,14 +254,14 @@ internal class LocalEchoEventFactory @Inject constructor( longitude: Double, uncertainty: Double?): Event { val geoUri = buildGeoUri(latitude, longitude, uncertainty) - val content = MessageLiveLocationContent( + val content = MessageBeaconLocationDataContent( body = geoUri, relatesTo = RelationDefaultContent( type = RelationType.REFERENCE, eventId = beaconInfoEventId ), unstableLocationInfo = LocationInfo(geoUri = geoUri, description = geoUri), - unstableTimestampAsMilliseconds = TimeUnit.MILLISECONDS.toSeconds(System.currentTimeMillis()), + unstableTimestampMillis = System.currentTimeMillis(), ) val localId = LocalEcho.createLocalEchoId() return Event( @@ -267,7 +271,8 @@ internal class LocalEchoEventFactory @Inject constructor( eventId = localId, type = EventType.BEACON_LOCATION_DATA.first(), content = content.toContent(), - unsignedData = UnsignedData(age = null, transactionId = localId)) + unsignedData = UnsignedData(age = null, transactionId = localId) + ) } fun createReplaceTextOfReply(roomId: String, @@ -297,7 +302,8 @@ internal class LocalEchoEventFactory @Inject constructor( // val replyFallback = buildReplyFallback(body, originalEvent.root.senderId ?: "", newBodyText) - return createMessageEvent(roomId, + return createMessageEvent( + roomId, MessageTextContent( msgType = msgType, body = compatibilityText, @@ -309,7 +315,8 @@ internal class LocalEchoEventFactory @Inject constructor( formattedBody = replyFormatted ) .toContent() - )) + ) + ) } fun createMediaEvent(roomId: String, @@ -341,7 +348,8 @@ internal class LocalEchoEventFactory @Inject constructor( eventId = localId, type = EventType.REACTION, 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 { @@ -532,8 +540,10 @@ internal class LocalEchoEventFactory @Inject constructor( content.toThreadTextContent( rootThreadEventId = rootThreadEventId, latestThreadEventId = localEchoRepository.getLatestThreadEvent(rootThreadEventId), - msgType = msgType) - .toContent()) + msgType = msgType + ) + .toContent() + ) } private fun dummyOriginServerTs(): Long { @@ -582,7 +592,9 @@ internal class LocalEchoEventFactory @Inject constructor( relatesTo = generateReplyRelationContent( eventId = eventId, rootThreadEventId = rootThreadEventId, - showInThread = showInThread)) + showInThread = showInThread + ) + ) return createMessageEvent(roomId, content) } @@ -605,7 +617,8 @@ internal class LocalEchoEventFactory @Inject constructor( eventId = it, isFallingBack = showInThread, // 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)) private fun buildFormattedReply(permalink: String, userLink: String, userId: String, bodyFormatted: String, newBodyFormatted: String): String { @@ -740,13 +753,15 @@ internal class LocalEchoEventFactory @Inject constructor( .toThreadTextContent( rootThreadEventId = rootThreadEventId, latestThreadEventId = localEchoRepository.getLatestThreadEvent(rootThreadEventId), - msgType = MessageType.MSGTYPE_TEXT) + msgType = MessageType.MSGTYPE_TEXT + ) ) } else { createFormattedTextEvent( roomId, markdownParser.parse(quoteText, force = true, advanced = autoMarkdown), - MessageType.MSGTYPE_TEXT) + MessageType.MSGTYPE_TEXT + ) } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/state/DefaultStateService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/state/DefaultStateService.kt index a8b155272b..e5c7a75cb6 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/state/DefaultStateService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/state/DefaultStateService.kt @@ -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.RoomJoinRulesAllowEntry 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.util.JsonDict 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) { getLiveLocationBeaconInfo(userId, true)?.let { beaconInfoStateEvent -> - beaconInfoStateEvent.getClearContent()?.toModel()?.let { content -> + beaconInfoStateEvent.getClearContent()?.toModel()?.let { content -> val updatedContent = content.copy(isLive = false).toContent() beaconInfoStateEvent.stateKey?.let { @@ -217,7 +217,7 @@ internal class DefaultStateService @AssistedInject constructor(@Assisted private } .firstOrNull { beaconInfoEvent -> !filterOnlyLive || - beaconInfoEvent.getClearContent()?.toModel()?.isLive.orFalse() + beaconInfoEvent.getClearContent()?.toModel()?.isLive.orFalse() } } } diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/LiveLocationMessageItemFactory.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/LiveLocationMessageItemFactory.kt index c417038935..d233deffb8 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/LiveLocationMessageItemFactory.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/LiveLocationMessageItemFactory.kt @@ -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 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 class LiveLocationMessageItemFactory @Inject constructor( @@ -34,19 +34,20 @@ class LiveLocationMessageItemFactory @Inject constructor( ) { fun create( - liveLocationContent: LiveLocationBeaconContent, + beaconInfoContent: MessageBeaconInfoContent, highlight: Boolean, attributes: AbsMessageItem.Attributes, ): VectorEpoxyModel<*>? { // TODO handle location received and stopped states return when { - isLiveRunning(liveLocationContent) -> buildStartLiveItem(highlight, attributes) - else -> null + isLiveRunning(beaconInfoContent) -> buildStartLiveItem(highlight, attributes) + else -> null } } - private fun isLiveRunning(liveLocationContent: LiveLocationBeaconContent): Boolean { - return liveLocationContent.isLive.orFalse() && liveLocationContent.hasTimedOut.not() + private fun isLiveRunning(beaconInfoContent: MessageBeaconInfoContent): Boolean { + // TODO when we will use aggregatedSummary, check if the live has timed out as well + return beaconInfoContent.isLive.orFalse() } private fun buildStartLiveItem( diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/MessageItemFactory.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/MessageItemFactory.kt index 186b34dc29..e4522b260e 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/MessageItemFactory.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/MessageItemFactory.kt @@ -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.isThread 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.MessageBeaconInfoContent 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.MessageEmoteContent @@ -207,15 +207,15 @@ class MessageItemFactory @Inject constructor( is MessageAudioContent -> buildAudioContent(params, messageContent, informationData, highlight, attributes) is MessageVerificationRequestContent -> buildVerificationRequestMessageItem(messageContent, informationData, highlight, callback, attributes) is MessagePollContent -> buildPollItem(messageContent, informationData, highlight, callback, attributes) - is MessageLocationContent -> { + is MessageLocationContent -> { if (vectorPreferences.labsRenderLocationsInTimeline()) { buildLocationItem(messageContent, informationData, highlight, attributes) } else { buildMessageTextItem(messageContent.body, false, informationData, highlight, callback, attributes) } } - is LiveLocationBeaconContent -> liveLocationMessageItemFactory.create(messageContent, highlight, attributes) - else -> buildNotHandledMessageItem(messageContent, informationData, highlight, callback, attributes) + is MessageBeaconInfoContent -> liveLocationMessageItemFactory.create(messageContent, highlight, attributes) + else -> buildNotHandledMessageItem(messageContent, informationData, highlight, callback, attributes) } return messageItem?.apply { layout(informationData.messageLayout.layoutRes) diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/style/TimelineMessageLayoutFactory.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/style/TimelineMessageLayoutFactory.kt index f2334e5a4f..ff3fd7b637 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/style/TimelineMessageLayoutFactory.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/style/TimelineMessageLayoutFactory.kt @@ -59,12 +59,12 @@ class TimelineMessageLayoutFactory @Inject constructor(private val session: Sess MessageType.MSGTYPE_VIDEO, MessageType.MSGTYPE_STICKER_LOCAL, MessageType.MSGTYPE_EMOTE, - MessageType.MSGTYPE_LIVE_LOCATION_STATE, + MessageType.MSGTYPE_BEACON_INFO, ) private val MSG_TYPES_WITH_TIMESTAMP_INSIDE_MESSAGE = setOf( MessageType.MSGTYPE_IMAGE, 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 { return when { - this == null || msgType == MessageType.MSGTYPE_LIVE_LOCATION_STATE -> false - msgType == MessageType.MSGTYPE_LOCATION -> vectorPreferences.labsRenderLocationsInTimeline() + this == null || msgType == MessageType.MSGTYPE_BEACON_INFO -> false + msgType == MessageType.MSGTYPE_LOCATION -> vectorPreferences.labsRenderLocationsInTimeline() else -> msgType in MSG_TYPES_WITH_TIMESTAMP_INSIDE_MESSAGE } } diff --git a/vector/src/main/java/im/vector/app/features/location/LocationSharingService.kt b/vector/src/main/java/im/vector/app/features/location/LocationSharingService.kt index 938cef6825..c1f64d6559 100644 --- a/vector/src/main/java/im/vector/app/features/location/LocationSharingService.kt +++ b/vector/src/main/java/im/vector/app/features/location/LocationSharingService.kt @@ -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.toContent 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 java.util.Timer import java.util.TimerTask @@ -96,10 +96,10 @@ class LocationSharingService : VectorService(), LocationTracker.Callback { } private suspend fun sendLiveBeaconInfo(session: Session, roomArgs: RoomArgs) { - val beaconContent = LiveLocationBeaconContent( + val beaconContent = MessageBeaconInfoContent( timeout = roomArgs.durationMillis, isLive = true, - unstableTimestampAsMilliseconds = clock.epochMillis() + unstableTimestampMillis = clock.epochMillis() ).toContent() val stateKey = session.myUserId