Merge pull request from vector-im/feature/ons/live_location_stop_sharing

Live Location Sharing - Update beacon info state event when sharing is ended
This commit is contained in:
Onuray Sahin 2022-04-14 17:12:50 +03:00 committed by GitHub
commit aa55b1f6b3
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
8 changed files with 110 additions and 8 deletions
changelog.d
matrix-sdk-android/src/main/java/org/matrix/android/sdk
api/session/room/state
internal/session/room/state
vector/src/main/java/im/vector/app/features

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

@ -0,0 +1 @@
Live Location Sharing - Update beacon info state event when sharing is ended

View file

@ -66,6 +66,19 @@ interface StateService {
*/ */
suspend fun deleteAvatar() suspend fun deleteAvatar()
/**
* Stops sharing live location in the room
* @param userId user id
*/
suspend fun stopLiveLocation(userId: String)
/**
* Returns beacon info state event of a user
* @param userId user id who is sharing location
* @param filterOnlyLive filters only ongoing live location sharing beacons if true else ended event is included
*/
suspend fun getLiveLocationBeaconInfo(userId: String, filterOnlyLive: Boolean): Event?
/** /**
* Send a state event to the room * Send a state event to the room
* @param eventType The type of event to send. * @param eventType The type of event to send.

View file

@ -21,16 +21,20 @@ import androidx.lifecycle.LiveData
import dagger.assisted.Assisted import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject import dagger.assisted.AssistedInject
import org.matrix.android.sdk.api.extensions.orFalse
import org.matrix.android.sdk.api.query.QueryStringValue import org.matrix.android.sdk.api.query.QueryStringValue
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.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.room.model.GuestAccess import org.matrix.android.sdk.api.session.room.model.GuestAccess
import org.matrix.android.sdk.api.session.room.model.RoomCanonicalAliasContent import org.matrix.android.sdk.api.session.room.model.RoomCanonicalAliasContent
import org.matrix.android.sdk.api.session.room.model.RoomHistoryVisibility 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.BeaconInfo
import org.matrix.android.sdk.api.session.room.model.livelocation.LiveLocationBeaconContent
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
@ -186,4 +190,42 @@ internal class DefaultStateService @AssistedInject constructor(@Assisted private
} }
updateJoinRule(RoomJoinRules.RESTRICTED, null, allowEntries) updateJoinRule(RoomJoinRules.RESTRICTED, null, allowEntries)
} }
override suspend fun stopLiveLocation(userId: String) {
getLiveLocationBeaconInfo(userId, true)?.let { beaconInfoStateEvent ->
beaconInfoStateEvent.getClearContent()?.toModel<LiveLocationBeaconContent>()?.let { content ->
val beaconContent = LiveLocationBeaconContent(
unstableBeaconInfo = BeaconInfo(
description = content.getBestBeaconInfo()?.description,
timeout = content.getBestBeaconInfo()?.timeout,
isLive = false,
),
unstableTimestampAsMilliseconds = System.currentTimeMillis()
).toContent()
beaconInfoStateEvent.stateKey?.let {
sendStateEvent(
eventType = EventType.STATE_ROOM_BEACON_INFO.first(),
body = beaconContent,
stateKey = it
)
}
}
}
}
override suspend fun getLiveLocationBeaconInfo(userId: String, filterOnlyLive: Boolean): Event? {
return EventType.STATE_ROOM_BEACON_INFO
.mapNotNull {
stateEventDataSource.getStateEvent(
roomId = roomId,
eventType = it,
stateKey = QueryStringValue.Equals(userId)
)
}
.firstOrNull { beaconInfoEvent ->
!filterOnlyLive ||
beaconInfoEvent.getClearContent()?.toModel<LiveLocationBeaconContent>()?.getBestBeaconInfo()?.isLive.orFalse()
}
}
} }

View file

@ -111,4 +111,7 @@ sealed class RoomDetailAction : VectorViewModelAction {
// Poll // Poll
data class EndPoll(val eventId: String) : RoomDetailAction() data class EndPoll(val eventId: String) : RoomDetailAction()
// Live Location
object StopLiveLocationSharing : RoomDetailAction()
} }

View file

@ -385,6 +385,7 @@ class TimelineFragment @Inject constructor(
setupEmojiButton() setupEmojiButton()
setupRemoveJitsiWidgetView() setupRemoveJitsiWidgetView()
setupVoiceMessageView() setupVoiceMessageView()
setupLiveLocationIndicator()
views.includeRoomToolbar.roomToolbarContentView.debouncedClicks { views.includeRoomToolbar.roomToolbarContentView.debouncedClicks {
navigator.openRoomProfile(requireActivity(), timelineArgs.roomId) navigator.openRoomProfile(requireActivity(), timelineArgs.roomId)
@ -810,6 +811,12 @@ class TimelineFragment @Inject constructor(
} }
} }
private fun setupLiveLocationIndicator() {
views.locationLiveStatusIndicator.stopButton.debouncedClicks {
timelineViewModel.handle(RoomDetailAction.StopLiveLocationSharing)
}
}
private fun joinJitsiRoom(jitsiWidget: Widget, enableVideo: Boolean) { private fun joinJitsiRoom(jitsiWidget: Widget, enableVideo: Boolean) {
navigator.openRoomWidget(requireContext(), timelineArgs.roomId, jitsiWidget, mapOf(JitsiCallViewModel.ENABLE_VIDEO_OPTION to enableVideo)) navigator.openRoomWidget(requireContext(), timelineArgs.roomId, jitsiWidget, mapOf(JitsiCallViewModel.ENABLE_VIDEO_OPTION to enableVideo))
} }

View file

@ -444,6 +444,7 @@ class TimelineViewModel @AssistedInject constructor(
_viewEvents.post(RoomDetailViewEvents.OpenRoom(action.replacementRoomId, closeCurrentRoom = true)) _viewEvents.post(RoomDetailViewEvents.OpenRoom(action.replacementRoomId, closeCurrentRoom = true))
} }
is RoomDetailAction.EndPoll -> handleEndPoll(action.eventId) is RoomDetailAction.EndPoll -> handleEndPoll(action.eventId)
RoomDetailAction.StopLiveLocationSharing -> handleStopLiveLocationSharing()
} }
} }
@ -1093,6 +1094,10 @@ class TimelineViewModel @AssistedInject constructor(
} }
} }
private fun handleStopLiveLocationSharing() {
locationSharingServiceConnection.stopLiveLocationSharing(room.roomId)
}
private fun observeRoomSummary() { private fun observeRoomSummary() {
room.flow().liveRoomSummary() room.flow().liveRoomSummary()
.unwrap() .unwrap()

View file

@ -87,7 +87,7 @@ class LocationSharingService : VectorService(), LocationTracker.Callback {
.getSafeActiveSession() .getSafeActiveSession()
?.let { session -> ?.let { session ->
session.coroutineScope.launch(session.coroutineDispatchers.io) { session.coroutineScope.launch(session.coroutineDispatchers.io) {
sendBeaconInfo(session, roomArgs) sendLiveBeaconInfo(session, roomArgs)
} }
} }
} }
@ -95,7 +95,7 @@ class LocationSharingService : VectorService(), LocationTracker.Callback {
return START_STICKY return START_STICKY
} }
private suspend fun sendBeaconInfo(session: Session, roomArgs: RoomArgs) { private suspend fun sendLiveBeaconInfo(session: Session, roomArgs: RoomArgs) {
val beaconContent = LiveLocationBeaconContent( val beaconContent = LiveLocationBeaconContent(
unstableBeaconInfo = BeaconInfo( unstableBeaconInfo = BeaconInfo(
timeout = roomArgs.durationMillis, timeout = roomArgs.durationMillis,
@ -129,8 +129,12 @@ class LocationSharingService : VectorService(), LocationTracker.Callback {
} }
} }
private fun stopSharingLocation(roomId: String) { fun stopSharingLocation(roomId: String) {
Timber.i("### LocationSharingService.stopSharingLocation for $roomId") Timber.i("### LocationSharingService.stopSharingLocation for $roomId")
// Send a new beacon info state by setting live field as false
sendStoppedBeaconInfo(roomId)
synchronized(roomArgsList) { synchronized(roomArgsList) {
roomArgsList.removeAll { it.roomId == roomId } roomArgsList.removeAll { it.roomId == roomId }
if (roomArgsList.isEmpty()) { if (roomArgsList.isEmpty()) {
@ -140,19 +144,39 @@ class LocationSharingService : VectorService(), LocationTracker.Callback {
} }
} }
private fun sendStoppedBeaconInfo(roomId: String) {
activeSessionHolder
.getSafeActiveSession()
?.let { session ->
session.coroutineScope.launch(session.coroutineDispatchers.io) {
session.getRoom(roomId)?.stopLiveLocation(session.myUserId)
}
}
}
override fun onLocationUpdate(locationData: LocationData) { override fun onLocationUpdate(locationData: LocationData) {
Timber.i("### LocationSharingService.onLocationUpdate. Uncertainty: ${locationData.uncertainty}") Timber.i("### LocationSharingService.onLocationUpdate. Uncertainty: ${locationData.uncertainty}")
val session = activeSessionHolder.getSafeActiveSession()
// Emit location update to all rooms in which live location sharing is active // Emit location update to all rooms in which live location sharing is active
roomArgsList.toList().forEach { roomArg -> session?.coroutineScope?.launch(session.coroutineDispatchers.io) {
sendLiveLocation(roomArg.roomId, locationData) roomArgsList.toList().forEach { roomArg ->
sendLiveLocation(roomArg.roomId, locationData)
}
} }
} }
private fun sendLiveLocation(roomId: String, locationData: LocationData) { private suspend fun sendLiveLocation(roomId: String, locationData: LocationData) {
val room = activeSessionHolder.getSafeActiveSession()?.getRoom(roomId) val session = activeSessionHolder.getSafeActiveSession()
val room = session?.getRoom(roomId)
val userId = session?.myUserId
if (room == null || userId == null) {
return
}
room room
?.getStateEvent(EventType.STATE_ROOM_BEACON_INFO.first()) .getLiveLocationBeaconInfo(userId, true)
?.eventId ?.eventId
?.let { ?.let {
room.sendLiveLocation( room.sendLiveLocation(

View file

@ -34,6 +34,7 @@ class LocationSharingServiceConnection @Inject constructor(
private var callback: Callback? = null private var callback: Callback? = null
private var isBound = false private var isBound = false
private var locationSharingService: LocationSharingService? = null
fun bind(callback: Callback) { fun bind(callback: Callback) {
this.callback = callback this.callback = callback
@ -51,13 +52,19 @@ class LocationSharingServiceConnection @Inject constructor(
callback = null callback = null
} }
fun stopLiveLocationSharing(roomId: String) {
locationSharingService?.stopSharingLocation(roomId)
}
override fun onServiceConnected(className: ComponentName, binder: IBinder) { override fun onServiceConnected(className: ComponentName, binder: IBinder) {
locationSharingService = (binder as LocationSharingService.LocalBinder).getService()
isBound = true isBound = true
callback?.onLocationServiceRunning() callback?.onLocationServiceRunning()
} }
override fun onServiceDisconnected(className: ComponentName) { override fun onServiceDisconnected(className: ComponentName) {
isBound = false isBound = false
locationSharingService = null
callback?.onLocationServiceStopped() callback?.onLocationServiceStopped()
} }
} }