diff --git a/changelog.d/5697.feature b/changelog.d/5697.feature new file mode 100644 index 0000000000..47504084f4 --- /dev/null +++ b/changelog.d/5697.feature @@ -0,0 +1 @@ +Live Location Sharing - Send location data \ No newline at end of file diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/events/model/EventType.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/events/model/EventType.kt index 855801e79e..fa3a9f6acd 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/events/model/EventType.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/events/model/EventType.kt @@ -49,7 +49,8 @@ object EventType { const val STATE_ROOM_JOIN_RULES = "m.room.join_rules" const val STATE_ROOM_GUEST_ACCESS = "m.room.guest_access" const val STATE_ROOM_POWER_LEVELS = "m.room.power_levels" - private const val STATE_ROOM_BEACON_INFO_PREFIX = "org.matrix.msc3489.beacon_info." + val STATE_ROOM_BEACON_INFO = listOf("org.matrix.msc3672.beacon_info", "m.beacon_info") + val BEACON_LOCATION_DATA = listOf("org.matrix.msc3672.beacon", "m.beacon") const val STATE_SPACE_CHILD = "m.space.child" @@ -121,12 +122,4 @@ object EventType { type == CALL_REJECT || type == CALL_REPLACES } - - /** - * Returns an event type like org.matrix.msc3489.beacon_info.@userid:matrix.org.1648814272273 - */ - fun generateBeaconInfoStateEventType(userId: String): String { - val uniqueId = System.currentTimeMillis() - return "$STATE_ROOM_BEACON_INFO_PREFIX$userId.$uniqueId" - } } 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/livelocation/LiveLocationBeaconContent.kt index e08d5b629b..a4551d462e 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/livelocation/LiveLocationBeaconContent.kt @@ -26,7 +26,7 @@ data class LiveLocationBeaconContent( /** * Indicates user's intent to share ephemeral location. */ - @Json(name = "org.matrix.msc3489.beacon_info") val unstableBeaconInfo: BeaconInfo? = null, + @Json(name = "org.matrix.msc3672.beacon_info") val unstableBeaconInfo: BeaconInfo? = null, @Json(name = "m.beacon_info") val beaconInfo: BeaconInfo? = null, /** * Beacon creation timestamp. 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/MessageLiveLocationContent.kt new file mode 100644 index 0000000000..e5de46b3ef --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/MessageLiveLocationContent.kt @@ -0,0 +1,52 @@ +/* + * Copyright 2022 The Matrix.org Foundation C.I.C. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.matrix.android.sdk.api.session.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.relation.RelationDefaultContent + +@JsonClass(generateAdapter = true) +data class MessageLiveLocationContent( + /** + * Local message type, not from server + */ + @Transient + override val msgType: String = MessageType.MSGTYPE_LIVE_LOCATION, + + @Json(name = "body") override val body: String = "", + @Json(name = "m.relates_to") override val relatesTo: RelationDefaultContent? = null, + @Json(name = "m.new_content") override val newContent: Content? = null, + + /** + * See [MSC3488](https://github.com/matrix-org/matrix-doc/blob/matthew/location/proposals/3488-location.md) + */ + @Json(name = "org.matrix.msc3488.location") val unstableLocationInfo: LocationInfo? = null, + @Json(name = "m.location") val locationInfo: LocationInfo? = null, + + /** + * 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 +) : MessageContent { + + fun getBestLocationInfo() = locationInfo ?: unstableLocationInfo + + fun getBestTs() = ts ?: unstableTs +} 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 2a6138ae60..280699cde8 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 @@ -33,10 +33,13 @@ object MessageType { const val MSGTYPE_STICKER_LOCAL = "org.matrix.android.sdk.sticker" // Fake message types for poll events to be able to inherit them from MessageContent - // Because poll events are not message events and they don't hanve msgtype field + // Because poll events are not message events and they don't have msgtype field const val MSGTYPE_POLL_START = "org.matrix.android.sdk.poll.start" const val MSGTYPE_POLL_RESPONSE = "org.matrix.android.sdk.poll.response" const val MSGTYPE_CONFETTI = "nic.custom.confetti" 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 = "org.matrix.android.sdk.livelocation" } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/send/SendService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/send/SendService.kt index 9f8b1d93d7..af7ab11df1 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/send/SendService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/send/SendService.kt @@ -146,6 +146,15 @@ interface SendService { */ fun sendLocation(latitude: Double, longitude: Double, uncertainty: Double?, isUserLocation: Boolean): Cancelable + /** + * Send a live location event to the room. beacon_info state event has to be sent before sending live location updates. + * @param beaconInfoEventId event id of the initial beacon info state event + * @param latitude required latitude of the location + * @param longitude required longitude of the location + * @param uncertainty Accuracy of the location in meters + */ + fun sendLiveLocation(beaconInfoEventId: String, latitude: Double, longitude: Double, uncertainty: Double?): Cancelable + /** * Remove this failed message from the timeline * @param localEcho the unsent local echo diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/DefaultSendService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/DefaultSendService.kt index 31c7254ed5..c315bf9f7a 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/DefaultSendService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/DefaultSendService.kt @@ -134,6 +134,12 @@ internal class DefaultSendService @AssistedInject constructor( .let { sendEvent(it) } } + override fun sendLiveLocation(beaconInfoEventId: String, latitude: Double, longitude: Double, uncertainty: Double?): Cancelable { + return localEchoEventFactory.createLiveLocationEvent(beaconInfoEventId, roomId, latitude, longitude, uncertainty) + .also { createLocalEcho(it) } + .let { sendEvent(it) } + } + override fun redactEvent(event: Event, reason: String?): Cancelable { // TODO manage media/attachements? val redactionEcho = localEchoEventFactory.createRedactEvent(roomId, event.eventId!!, reason) 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 0ba95cc1fb..d7d74ca601 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 @@ -43,6 +43,7 @@ import org.matrix.android.sdk.api.session.room.model.message.MessageEndPollConte 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 @@ -242,6 +243,32 @@ internal class LocalEchoEventFactory @Inject constructor( return createMessageEvent(roomId, content) } + fun createLiveLocationEvent(beaconInfoEventId: String, + roomId: String, + latitude: Double, + longitude: Double, + uncertainty: Double?): Event { + val geoUri = buildGeoUri(latitude, longitude, uncertainty) + val content = MessageLiveLocationContent( + body = geoUri, + relatesTo = RelationDefaultContent( + type = RelationType.REFERENCE, + eventId = beaconInfoEventId + ), + unstableLocationInfo = LocationInfo(geoUri = geoUri, description = geoUri), + unstableTs = TimeUnit.MILLISECONDS.toSeconds(System.currentTimeMillis()), + ) + val localId = LocalEcho.createLocalEchoId() + return Event( + roomId = roomId, + originServerTs = dummyOriginServerTs(), + senderId = userId, + eventId = localId, + type = EventType.BEACON_LOCATION_DATA.first(), + content = content.toContent(), + unsignedData = UnsignedData(age = null, transactionId = localId)) + } + fun createReplaceTextOfReply(roomId: String, eventReplaced: TimelineEvent, originalEvent: TimelineEvent, 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 063cc3caa6..85679e34a7 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 @@ -29,7 +29,7 @@ import im.vector.app.features.session.coroutineScope import kotlinx.coroutines.launch import kotlinx.parcelize.Parcelize import org.matrix.android.sdk.api.session.Session -import org.matrix.android.sdk.api.session.events.model.EventType.generateBeaconInfoStateEventType +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.room.model.livelocation.BeaconInfo import org.matrix.android.sdk.api.session.room.model.livelocation.LiveLocationBeaconContent @@ -104,12 +104,11 @@ class LocationSharingService : VectorService(), LocationTracker.Callback { unstableTimestampAsMilliseconds = clock.epochMillis() ).toContent() - val eventType = generateBeaconInfoStateEventType(session.myUserId) val stateKey = session.myUserId session .getRoom(roomArgs.roomId) ?.sendStateEvent( - eventType = eventType, + eventType = EventType.STATE_ROOM_BEACON_INFO.first(), stateKey = stateKey, body = beaconContent ) @@ -143,6 +142,26 @@ class LocationSharingService : VectorService(), LocationTracker.Callback { override fun onLocationUpdate(locationData: LocationData) { Timber.i("### LocationSharingService.onLocationUpdate. Uncertainty: ${locationData.uncertainty}") + + // Emit location update to all rooms in which live location sharing is active + roomArgsList.toList().forEach { roomArg -> + sendLiveLocation(roomArg.roomId, locationData) + } + } + + private fun sendLiveLocation(roomId: String, locationData: LocationData) { + val room = activeSessionHolder.getSafeActiveSession()?.getRoom(roomId) + room + ?.getStateEvent(EventType.STATE_ROOM_BEACON_INFO.first()) + ?.eventId + ?.let { + room.sendLiveLocation( + beaconInfoEventId = it, + latitude = locationData.latitude, + longitude = locationData.longitude, + uncertainty = locationData.uncertainty + ) + } } override fun onLocationProviderIsNotAvailable() {