Merge pull request #5697 from vector-im/feature/ons/live_location_pulse

Live Location Sharing - Send location data
This commit is contained in:
Onuray Sahin 2022-04-06 15:35:02 +03:00 committed by GitHub
commit 2a42eb8c87
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
9 changed files with 124 additions and 14 deletions

1
changelog.d/5697.feature Normal file
View file

@ -0,0 +1 @@
Live Location Sharing - Send location data

View file

@ -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"
}
}

View file

@ -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.

View file

@ -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
}

View file

@ -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"
}

View file

@ -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

View file

@ -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)

View file

@ -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,

View file

@ -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() {