From 4ee733297322aa5737cc6dc7e85f829397749343 Mon Sep 17 00:00:00 2001 From: Onuray Sahin Date: Wed, 13 Apr 2022 16:59:42 +0300 Subject: [PATCH 1/5] Update beacon info state event by setting live as false. --- .../api/session/room/state/StateService.kt | 6 +++++ .../session/room/state/DefaultStateService.kt | 24 +++++++++++++++++++ 2 files changed, 30 insertions(+) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/state/StateService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/state/StateService.kt index e9b0e4f676..9d03b5d941 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/state/StateService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/state/StateService.kt @@ -66,6 +66,12 @@ interface StateService { */ suspend fun deleteAvatar() + /** + * Stops sharing live location in the room + * @param beaconInfoStateEvent Initial beacon info state event + */ + suspend fun stopLiveLocation(beaconInfoStateEvent: Event) + /** * Send a state event to the room * @param eventType The type of event to send. 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 417417f439..f9976a1559 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 @@ -25,12 +25,15 @@ 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.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.GuestAccess 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.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.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.util.JsonDict import org.matrix.android.sdk.api.util.MimeTypes @@ -186,4 +189,25 @@ internal class DefaultStateService @AssistedInject constructor(@Assisted private } updateJoinRule(RoomJoinRules.RESTRICTED, null, allowEntries) } + + override suspend fun stopLiveLocation(beaconInfoStateEvent: Event) { + beaconInfoStateEvent.getClearContent()?.toModel()?.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 + ) + } + } + } } From 33e735cbdd1c421484a75b8dc476f1dfbb8b6822 Mon Sep 17 00:00:00 2001 From: Onuray Sahin Date: Wed, 13 Apr 2022 17:00:37 +0300 Subject: [PATCH 2/5] Stop live location sharing. --- .../home/room/detail/RoomDetailAction.kt | 3 +++ .../home/room/detail/TimelineFragment.kt | 7 +++++++ .../home/room/detail/TimelineViewModel.kt | 18 ++++++++++++++++++ .../location/LocationSharingService.kt | 2 +- .../LocationSharingServiceConnection.kt | 6 ++++++ 5 files changed, 35 insertions(+), 1 deletion(-) diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailAction.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailAction.kt index d10b363519..f6ea8b76ef 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailAction.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailAction.kt @@ -111,4 +111,7 @@ sealed class RoomDetailAction : VectorViewModelAction { // Poll data class EndPoll(val eventId: String) : RoomDetailAction() + + // Live Location + object StopLiveLocationSharing : RoomDetailAction() } diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/TimelineFragment.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/TimelineFragment.kt index 8a90295967..226221d42c 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/TimelineFragment.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/TimelineFragment.kt @@ -385,6 +385,7 @@ class TimelineFragment @Inject constructor( setupEmojiButton() setupRemoveJitsiWidgetView() setupVoiceMessageView() + setupLiveLocationIndicator() views.includeRoomToolbar.roomToolbarContentView.debouncedClicks { 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) { navigator.openRoomWidget(requireContext(), timelineArgs.roomId, jitsiWidget, mapOf(JitsiCallViewModel.ENABLE_VIDEO_OPTION to enableVideo)) } diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/TimelineViewModel.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/TimelineViewModel.kt index 755df16c1e..a07afaa74b 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/TimelineViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/TimelineViewModel.kt @@ -444,6 +444,7 @@ class TimelineViewModel @AssistedInject constructor( _viewEvents.post(RoomDetailViewEvents.OpenRoom(action.replacementRoomId, closeCurrentRoom = true)) } is RoomDetailAction.EndPoll -> handleEndPoll(action.eventId) + RoomDetailAction.StopLiveLocationSharing -> handleStopLiveLocationSharing() } } @@ -1093,6 +1094,23 @@ class TimelineViewModel @AssistedInject constructor( } } + private fun handleStopLiveLocationSharing() { + viewModelScope.launch { + EventType + .STATE_ROOM_BEACON_INFO + .mapNotNull { + room.getStateEvent(it, QueryStringValue.Equals(session.myUserId)) + } + .firstOrNull() + ?.let { beaconInfoEvent -> + room.stopLiveLocation(beaconInfoEvent) + } + ?.also { + locationSharingServiceConnection.stopLiveLocationSharing(room.roomId) + } + } + } + private fun observeRoomSummary() { room.flow().liveRoomSummary() .unwrap() 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 85679e34a7..3256844bba 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 @@ -129,7 +129,7 @@ class LocationSharingService : VectorService(), LocationTracker.Callback { } } - private fun stopSharingLocation(roomId: String) { + fun stopSharingLocation(roomId: String) { Timber.i("### LocationSharingService.stopSharingLocation for $roomId") synchronized(roomArgsList) { roomArgsList.removeAll { it.roomId == roomId } diff --git a/vector/src/main/java/im/vector/app/features/location/LocationSharingServiceConnection.kt b/vector/src/main/java/im/vector/app/features/location/LocationSharingServiceConnection.kt index 9af6b1539a..6c1b72960d 100644 --- a/vector/src/main/java/im/vector/app/features/location/LocationSharingServiceConnection.kt +++ b/vector/src/main/java/im/vector/app/features/location/LocationSharingServiceConnection.kt @@ -34,6 +34,7 @@ class LocationSharingServiceConnection @Inject constructor( private var callback: Callback? = null private var isBound = false + private var locationSharingService: LocationSharingService? = null fun bind(callback: Callback) { this.callback = callback @@ -51,7 +52,12 @@ class LocationSharingServiceConnection @Inject constructor( callback = null } + fun stopLiveLocationSharing(roomId: String) { + locationSharingService?.stopSharingLocation(roomId) + } + override fun onServiceConnected(className: ComponentName, binder: IBinder) { + locationSharingService = (binder as LocationSharingService.LocalBinder).getService() isBound = true callback?.onLocationServiceRunning() } From 137d5e40936cd34654b61ffd2f2a6698bf959b4f Mon Sep 17 00:00:00 2001 From: Onuray Sahin Date: Wed, 13 Apr 2022 20:10:24 +0300 Subject: [PATCH 3/5] Set live as false when live location sharing is times up. --- .../home/room/detail/TimelineViewModel.kt | 15 +----------- .../location/LocationSharingService.kt | 24 +++++++++++++++++++ 2 files changed, 25 insertions(+), 14 deletions(-) diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/TimelineViewModel.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/TimelineViewModel.kt index a07afaa74b..76c6ecc86d 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/TimelineViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/TimelineViewModel.kt @@ -1095,20 +1095,7 @@ class TimelineViewModel @AssistedInject constructor( } private fun handleStopLiveLocationSharing() { - viewModelScope.launch { - EventType - .STATE_ROOM_BEACON_INFO - .mapNotNull { - room.getStateEvent(it, QueryStringValue.Equals(session.myUserId)) - } - .firstOrNull() - ?.let { beaconInfoEvent -> - room.stopLiveLocation(beaconInfoEvent) - } - ?.also { - locationSharingServiceConnection.stopLiveLocationSharing(room.roomId) - } - } + locationSharingServiceConnection.stopLiveLocationSharing(room.roomId) } private fun observeRoomSummary() { 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 3256844bba..f215a82c71 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 @@ -28,6 +28,7 @@ import im.vector.app.features.notifications.NotificationUtils import im.vector.app.features.session.coroutineScope import kotlinx.coroutines.launch import kotlinx.parcelize.Parcelize +import org.matrix.android.sdk.api.query.QueryStringValue 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 @@ -131,6 +132,10 @@ class LocationSharingService : VectorService(), LocationTracker.Callback { fun stopSharingLocation(roomId: String) { Timber.i("### LocationSharingService.stopSharingLocation for $roomId") + + // Send a new beacon info state by setting live field as false + updateStoppedBeaconInfo(roomId) + synchronized(roomArgsList) { roomArgsList.removeAll { it.roomId == roomId } if (roomArgsList.isEmpty()) { @@ -140,6 +145,25 @@ class LocationSharingService : VectorService(), LocationTracker.Callback { } } + private fun updateStoppedBeaconInfo(roomId: String) { + activeSessionHolder + .getSafeActiveSession() + ?.let { session -> + session.coroutineScope.launch(session.coroutineDispatchers.io) { + val room = session.getRoom(roomId) + EventType + .STATE_ROOM_BEACON_INFO + .mapNotNull { + room?.getStateEvent(it, QueryStringValue.Equals(session.myUserId)) + } + .firstOrNull() + ?.let { beaconInfoEvent -> + room?.stopLiveLocation(beaconInfoEvent) + } + } + } + } + override fun onLocationUpdate(locationData: LocationData) { Timber.i("### LocationSharingService.onLocationUpdate. Uncertainty: ${locationData.uncertainty}") From 023a00d1603f2d4dcc2d7c04c9c19c81a31f3c6b Mon Sep 17 00:00:00 2001 From: Onuray Sahin Date: Wed, 13 Apr 2022 20:13:22 +0300 Subject: [PATCH 4/5] Changelog added. --- changelog.d/5758.feature | 1 + 1 file changed, 1 insertion(+) create mode 100644 changelog.d/5758.feature diff --git a/changelog.d/5758.feature b/changelog.d/5758.feature new file mode 100644 index 0000000000..512b5c3868 --- /dev/null +++ b/changelog.d/5758.feature @@ -0,0 +1 @@ +Live Location Sharing - Update beacon info state event when sharing is ended \ No newline at end of file From f49e7d9619827de779292716e7f10d461cc14826 Mon Sep 17 00:00:00 2001 From: Onuray Sahin Date: Thu, 14 Apr 2022 16:23:43 +0300 Subject: [PATCH 5/5] Code review fixes. --- .../api/session/room/state/StateService.kt | 11 +++- .../session/room/state/DefaultStateService.kt | 50 +++++++++++++------ .../location/LocationSharingService.kt | 40 +++++++-------- .../LocationSharingServiceConnection.kt | 1 + 4 files changed, 64 insertions(+), 38 deletions(-) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/state/StateService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/state/StateService.kt index 9d03b5d941..f645f3ebf9 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/state/StateService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/state/StateService.kt @@ -68,9 +68,16 @@ interface StateService { /** * Stops sharing live location in the room - * @param beaconInfoStateEvent Initial beacon info state event + * @param userId user id */ - suspend fun stopLiveLocation(beaconInfoStateEvent: Event) + 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 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 f9976a1559..89d33f98d2 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 @@ -21,6 +21,7 @@ import androidx.lifecycle.LiveData import dagger.assisted.Assisted import dagger.assisted.AssistedFactory 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.session.events.model.Event import org.matrix.android.sdk.api.session.events.model.EventType @@ -190,24 +191,41 @@ internal class DefaultStateService @AssistedInject constructor(@Assisted private updateJoinRule(RoomJoinRules.RESTRICTED, null, allowEntries) } - override suspend fun stopLiveLocation(beaconInfoStateEvent: Event) { - beaconInfoStateEvent.getClearContent()?.toModel()?.let { content -> - val beaconContent = LiveLocationBeaconContent( - unstableBeaconInfo = BeaconInfo( - description = content.getBestBeaconInfo()?.description, - timeout = content.getBestBeaconInfo()?.timeout, - isLive = false, - ), - unstableTimestampAsMilliseconds = System.currentTimeMillis() - ).toContent() + override suspend fun stopLiveLocation(userId: String) { + getLiveLocationBeaconInfo(userId, true)?.let { beaconInfoStateEvent -> + beaconInfoStateEvent.getClearContent()?.toModel()?.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 - ) + 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()?.getBestBeaconInfo()?.isLive.orFalse() + } + } } 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 f215a82c71..2126cdac04 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 @@ -28,7 +28,6 @@ import im.vector.app.features.notifications.NotificationUtils import im.vector.app.features.session.coroutineScope import kotlinx.coroutines.launch import kotlinx.parcelize.Parcelize -import org.matrix.android.sdk.api.query.QueryStringValue 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 @@ -88,7 +87,7 @@ class LocationSharingService : VectorService(), LocationTracker.Callback { .getSafeActiveSession() ?.let { session -> session.coroutineScope.launch(session.coroutineDispatchers.io) { - sendBeaconInfo(session, roomArgs) + sendLiveBeaconInfo(session, roomArgs) } } } @@ -96,7 +95,7 @@ class LocationSharingService : VectorService(), LocationTracker.Callback { return START_STICKY } - private suspend fun sendBeaconInfo(session: Session, roomArgs: RoomArgs) { + private suspend fun sendLiveBeaconInfo(session: Session, roomArgs: RoomArgs) { val beaconContent = LiveLocationBeaconContent( unstableBeaconInfo = BeaconInfo( timeout = roomArgs.durationMillis, @@ -134,7 +133,7 @@ class LocationSharingService : VectorService(), LocationTracker.Callback { Timber.i("### LocationSharingService.stopSharingLocation for $roomId") // Send a new beacon info state by setting live field as false - updateStoppedBeaconInfo(roomId) + sendStoppedBeaconInfo(roomId) synchronized(roomArgsList) { roomArgsList.removeAll { it.roomId == roomId } @@ -145,21 +144,12 @@ class LocationSharingService : VectorService(), LocationTracker.Callback { } } - private fun updateStoppedBeaconInfo(roomId: String) { + private fun sendStoppedBeaconInfo(roomId: String) { activeSessionHolder .getSafeActiveSession() ?.let { session -> session.coroutineScope.launch(session.coroutineDispatchers.io) { - val room = session.getRoom(roomId) - EventType - .STATE_ROOM_BEACON_INFO - .mapNotNull { - room?.getStateEvent(it, QueryStringValue.Equals(session.myUserId)) - } - .firstOrNull() - ?.let { beaconInfoEvent -> - room?.stopLiveLocation(beaconInfoEvent) - } + session.getRoom(roomId)?.stopLiveLocation(session.myUserId) } } } @@ -167,16 +157,26 @@ class LocationSharingService : VectorService(), LocationTracker.Callback { override fun onLocationUpdate(locationData: LocationData) { Timber.i("### LocationSharingService.onLocationUpdate. Uncertainty: ${locationData.uncertainty}") + val session = activeSessionHolder.getSafeActiveSession() // Emit location update to all rooms in which live location sharing is active - roomArgsList.toList().forEach { roomArg -> - sendLiveLocation(roomArg.roomId, locationData) + session?.coroutineScope?.launch(session.coroutineDispatchers.io) { + roomArgsList.toList().forEach { roomArg -> + sendLiveLocation(roomArg.roomId, locationData) + } } } - private fun sendLiveLocation(roomId: String, locationData: LocationData) { - val room = activeSessionHolder.getSafeActiveSession()?.getRoom(roomId) + private suspend fun sendLiveLocation(roomId: String, locationData: LocationData) { + val session = activeSessionHolder.getSafeActiveSession() + val room = session?.getRoom(roomId) + val userId = session?.myUserId + + if (room == null || userId == null) { + return + } + room - ?.getStateEvent(EventType.STATE_ROOM_BEACON_INFO.first()) + .getLiveLocationBeaconInfo(userId, true) ?.eventId ?.let { room.sendLiveLocation( diff --git a/vector/src/main/java/im/vector/app/features/location/LocationSharingServiceConnection.kt b/vector/src/main/java/im/vector/app/features/location/LocationSharingServiceConnection.kt index 6c1b72960d..e72f77531b 100644 --- a/vector/src/main/java/im/vector/app/features/location/LocationSharingServiceConnection.kt +++ b/vector/src/main/java/im/vector/app/features/location/LocationSharingServiceConnection.kt @@ -64,6 +64,7 @@ class LocationSharingServiceConnection @Inject constructor( override fun onServiceDisconnected(className: ComponentName) { isBound = false + locationSharingService = null callback?.onLocationServiceStopped() } }