diff --git a/changelog.d/6315.bugfix b/changelog.d/6315.bugfix new file mode 100644 index 0000000000..0b5eb6064d --- /dev/null +++ b/changelog.d/6315.bugfix @@ -0,0 +1 @@ +[Location sharing] Fix crash when starting/stopping a live when offline diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/location/LocationSharingService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/location/LocationSharingService.kt index 11b74ecd7f..0f88f891cc 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/location/LocationSharingService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/location/LocationSharingService.kt @@ -46,14 +46,15 @@ interface LocationSharingService { /** * Starts sharing live location in the room. * @param timeoutMillis timeout of the live in milliseconds - * @return the id of the created beacon info event + * @return the result of the update of the live */ - suspend fun startLiveLocationShare(timeoutMillis: Long): String + suspend fun startLiveLocationShare(timeoutMillis: Long): UpdateLiveLocationShareResult /** * Stops sharing live location in the room. + * @return the result of the update of the live */ - suspend fun stopLiveLocationShare() + suspend fun stopLiveLocationShare(): UpdateLiveLocationShareResult /** * Returns a LiveData on the list of current running live location shares. diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/location/UpdateLiveLocationShareResult.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/location/UpdateLiveLocationShareResult.kt new file mode 100644 index 0000000000..6f8c03be46 --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/location/UpdateLiveLocationShareResult.kt @@ -0,0 +1,25 @@ +/* + * 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.location + +/** + * Represents the result of an update of live location share like a start or a stop. + */ +sealed interface UpdateLiveLocationShareResult { + data class Success(val beaconEventId: String) : UpdateLiveLocationShareResult + data class Failure(val error: Throwable) : UpdateLiveLocationShareResult +} diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/location/DefaultLocationSharingService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/location/DefaultLocationSharingService.kt index 3fa00fa077..015c1cca0b 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/location/DefaultLocationSharingService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/location/DefaultLocationSharingService.kt @@ -22,6 +22,7 @@ import dagger.assisted.Assisted import dagger.assisted.AssistedFactory import dagger.assisted.AssistedInject import org.matrix.android.sdk.api.session.room.location.LocationSharingService +import org.matrix.android.sdk.api.session.room.location.UpdateLiveLocationShareResult import org.matrix.android.sdk.api.session.room.model.livelocation.LiveLocationShareAggregatedSummary import org.matrix.android.sdk.api.util.Cancelable import org.matrix.android.sdk.internal.database.mapper.LiveLocationShareAggregatedSummaryMapper @@ -66,7 +67,7 @@ internal class DefaultLocationSharingService @AssistedInject constructor( return sendLiveLocationTask.execute(params) } - override suspend fun startLiveLocationShare(timeoutMillis: Long): String { + override suspend fun startLiveLocationShare(timeoutMillis: Long): UpdateLiveLocationShareResult { val params = StartLiveLocationShareTask.Params( roomId = roomId, timeoutMillis = timeoutMillis @@ -74,7 +75,7 @@ internal class DefaultLocationSharingService @AssistedInject constructor( return startLiveLocationShareTask.execute(params) } - override suspend fun stopLiveLocationShare() { + override suspend fun stopLiveLocationShare(): UpdateLiveLocationShareResult { val params = StopLiveLocationShareTask.Params( roomId = roomId, ) diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/location/StartLiveLocationShareTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/location/StartLiveLocationShareTask.kt index 7da67d7539..b943c27977 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/location/StartLiveLocationShareTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/location/StartLiveLocationShareTask.kt @@ -18,6 +18,7 @@ package org.matrix.android.sdk.internal.session.room.location 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.location.UpdateLiveLocationShareResult import org.matrix.android.sdk.api.session.room.model.message.MessageBeaconInfoContent import org.matrix.android.sdk.internal.di.UserId import org.matrix.android.sdk.internal.session.room.state.SendStateTask @@ -25,7 +26,7 @@ import org.matrix.android.sdk.internal.task.Task import org.matrix.android.sdk.internal.util.time.Clock import javax.inject.Inject -internal interface StartLiveLocationShareTask : Task { +internal interface StartLiveLocationShareTask : Task { data class Params( val roomId: String, val timeoutMillis: Long, @@ -38,7 +39,7 @@ internal class DefaultStartLiveLocationShareTask @Inject constructor( private val sendStateTask: SendStateTask, ) : StartLiveLocationShareTask { - override suspend fun execute(params: StartLiveLocationShareTask.Params): String { + override suspend fun execute(params: StartLiveLocationShareTask.Params): UpdateLiveLocationShareResult { val beaconContent = MessageBeaconInfoContent( timeout = params.timeoutMillis, isLive = true, @@ -51,6 +52,15 @@ internal class DefaultStartLiveLocationShareTask @Inject constructor( eventType = eventType, body = beaconContent ) - return sendStateTask.executeRetry(sendStateTaskParams, 3) + return try { + val eventId = sendStateTask.executeRetry(sendStateTaskParams, 3) + if (eventId.isNotEmpty()) { + UpdateLiveLocationShareResult.Success(eventId) + } else { + UpdateLiveLocationShareResult.Failure(Exception("empty event id for new state event")) + } + } catch (error: Throwable) { + UpdateLiveLocationShareResult.Failure(error) + } } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/location/StopLiveLocationShareTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/location/StopLiveLocationShareTask.kt index 1c282684a4..d0e7ff3f82 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/location/StopLiveLocationShareTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/location/StopLiveLocationShareTask.kt @@ -22,6 +22,7 @@ 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.location.UpdateLiveLocationShareResult import org.matrix.android.sdk.api.session.room.model.message.MessageBeaconInfoContent import org.matrix.android.sdk.internal.di.UserId import org.matrix.android.sdk.internal.session.room.state.SendStateTask @@ -29,7 +30,7 @@ import org.matrix.android.sdk.internal.session.room.state.StateEventDataSource import org.matrix.android.sdk.internal.task.Task import javax.inject.Inject -internal interface StopLiveLocationShareTask : Task { +internal interface StopLiveLocationShareTask : Task { data class Params( val roomId: String, ) @@ -41,10 +42,10 @@ internal class DefaultStopLiveLocationShareTask @Inject constructor( private val stateEventDataSource: StateEventDataSource, ) : StopLiveLocationShareTask { - override suspend fun execute(params: StopLiveLocationShareTask.Params) { - val beaconInfoStateEvent = getLiveLocationBeaconInfoForUser(userId, params.roomId) ?: return - val stateKey = beaconInfoStateEvent.stateKey ?: return - val content = beaconInfoStateEvent.getClearContent()?.toModel() ?: return + override suspend fun execute(params: StopLiveLocationShareTask.Params): UpdateLiveLocationShareResult { + val beaconInfoStateEvent = getLiveLocationBeaconInfoForUser(userId, params.roomId) ?: return getResultForIncorrectBeaconInfoEvent() + val stateKey = beaconInfoStateEvent.stateKey ?: return getResultForIncorrectBeaconInfoEvent() + val content = beaconInfoStateEvent.getClearContent()?.toModel() ?: return getResultForIncorrectBeaconInfoEvent() val updatedContent = content.copy(isLive = false).toContent() val sendStateTaskParams = SendStateTask.Params( roomId = params.roomId, @@ -52,9 +53,21 @@ internal class DefaultStopLiveLocationShareTask @Inject constructor( eventType = EventType.STATE_ROOM_BEACON_INFO.first(), body = updatedContent ) - sendStateTask.executeRetry(sendStateTaskParams, 3) + return try { + val eventId = sendStateTask.executeRetry(sendStateTaskParams, 3) + if (eventId.isNotEmpty()) { + UpdateLiveLocationShareResult.Success(eventId) + } else { + UpdateLiveLocationShareResult.Failure(Exception("empty event id for new state event")) + } + } catch (error: Throwable) { + UpdateLiveLocationShareResult.Failure(error) + } } + private fun getResultForIncorrectBeaconInfoEvent() = + UpdateLiveLocationShareResult.Failure(Exception("incorrect last beacon info event")) + private fun getLiveLocationBeaconInfoForUser(userId: String, roomId: String): Event? { return EventType.STATE_ROOM_BEACON_INFO .mapNotNull { diff --git a/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/session/room/location/DefaultLocationSharingServiceTest.kt b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/session/room/location/DefaultLocationSharingServiceTest.kt index 003842be28..30a9671733 100644 --- a/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/session/room/location/DefaultLocationSharingServiceTest.kt +++ b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/session/room/location/DefaultLocationSharingServiceTest.kt @@ -18,15 +18,14 @@ package org.matrix.android.sdk.internal.session.room.location import io.mockk.coEvery import io.mockk.coVerify -import io.mockk.just import io.mockk.mockk -import io.mockk.runs import io.mockk.unmockkAll import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.test.runTest import org.amshove.kluent.shouldBeEqualTo import org.junit.After import org.junit.Test +import org.matrix.android.sdk.api.session.room.location.UpdateLiveLocationShareResult import org.matrix.android.sdk.api.session.room.model.livelocation.LiveLocationShareAggregatedSummary import org.matrix.android.sdk.api.util.Cancelable import org.matrix.android.sdk.internal.database.mapper.LiveLocationShareAggregatedSummaryMapper @@ -119,11 +118,11 @@ internal class DefaultLocationSharingServiceTest { @Test fun `live location share can be started with a given timeout`() = runTest { - coEvery { startLiveLocationShareTask.execute(any()) } returns AN_EVENT_ID + coEvery { startLiveLocationShareTask.execute(any()) } returns UpdateLiveLocationShareResult.Success(AN_EVENT_ID) - val eventId = defaultLocationSharingService.startLiveLocationShare(A_TIMEOUT) + val result = defaultLocationSharingService.startLiveLocationShare(A_TIMEOUT) - eventId shouldBeEqualTo AN_EVENT_ID + result shouldBeEqualTo UpdateLiveLocationShareResult.Success(AN_EVENT_ID) val expectedParams = StartLiveLocationShareTask.Params( roomId = A_ROOM_ID, timeoutMillis = A_TIMEOUT @@ -133,10 +132,11 @@ internal class DefaultLocationSharingServiceTest { @Test fun `live location share can be stopped`() = runTest { - coEvery { stopLiveLocationShareTask.execute(any()) } just runs + coEvery { stopLiveLocationShareTask.execute(any()) } returns UpdateLiveLocationShareResult.Success(AN_EVENT_ID) - defaultLocationSharingService.stopLiveLocationShare() + val result = defaultLocationSharingService.stopLiveLocationShare() + result shouldBeEqualTo UpdateLiveLocationShareResult.Success(AN_EVENT_ID) val expectedParams = StopLiveLocationShareTask.Params( roomId = A_ROOM_ID ) diff --git a/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/session/room/location/DefaultStartLiveLocationShareTaskTest.kt b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/session/room/location/DefaultStartLiveLocationShareTaskTest.kt index c435e60db3..909ba5d048 100644 --- a/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/session/room/location/DefaultStartLiveLocationShareTaskTest.kt +++ b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/session/room/location/DefaultStartLiveLocationShareTaskTest.kt @@ -20,10 +20,12 @@ import io.mockk.unmockkAll import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.test.runTest import org.amshove.kluent.shouldBeEqualTo +import org.amshove.kluent.shouldBeInstanceOf import org.junit.After import org.junit.Test 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.location.UpdateLiveLocationShareResult import org.matrix.android.sdk.api.session.room.model.message.MessageBeaconInfoContent import org.matrix.android.sdk.internal.session.room.state.SendStateTask import org.matrix.android.sdk.test.fakes.FakeClock @@ -53,7 +55,7 @@ internal class DefaultStartLiveLocationShareTaskTest { } @Test - fun `given parameters when calling the task then it is correctly executed`() = runTest { + fun `given parameters and no error when calling the task then result is success`() = runTest { val params = StartLiveLocationShareTask.Params( roomId = A_ROOM_ID, timeoutMillis = A_TIMEOUT @@ -63,7 +65,7 @@ internal class DefaultStartLiveLocationShareTaskTest { val result = defaultStartLiveLocationShareTask.execute(params) - result shouldBeEqualTo AN_EVENT_ID + result shouldBeEqualTo UpdateLiveLocationShareResult.Success(AN_EVENT_ID) val expectedBeaconContent = MessageBeaconInfoContent( timeout = params.timeoutMillis, isLive = true, @@ -80,4 +82,33 @@ internal class DefaultStartLiveLocationShareTaskTest { remainingRetry = 3 ) } + + @Test + fun `given parameters and an empty returned event id when calling the task then result is failure`() = runTest { + val params = StartLiveLocationShareTask.Params( + roomId = A_ROOM_ID, + timeoutMillis = A_TIMEOUT + ) + fakeClock.givenEpoch(AN_EPOCH) + fakeSendStateTask.givenExecuteRetryReturns("") + + val result = defaultStartLiveLocationShareTask.execute(params) + + result shouldBeInstanceOf UpdateLiveLocationShareResult.Failure::class + } + + @Test + fun `given parameters and error during event sending when calling the task then result is failure`() = runTest { + val params = StartLiveLocationShareTask.Params( + roomId = A_ROOM_ID, + timeoutMillis = A_TIMEOUT + ) + fakeClock.givenEpoch(AN_EPOCH) + val error = Throwable() + fakeSendStateTask.givenExecuteRetryThrows(error) + + val result = defaultStartLiveLocationShareTask.execute(params) + + result shouldBeEqualTo UpdateLiveLocationShareResult.Failure(error) + } } diff --git a/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/session/room/location/DefaultStopLiveLocationShareTaskTest.kt b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/session/room/location/DefaultStopLiveLocationShareTaskTest.kt index 81a5742f90..7cb5abff62 100644 --- a/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/session/room/location/DefaultStopLiveLocationShareTaskTest.kt +++ b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/session/room/location/DefaultStopLiveLocationShareTaskTest.kt @@ -19,11 +19,15 @@ package org.matrix.android.sdk.internal.session.room.location import io.mockk.unmockkAll import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.test.runTest +import org.amshove.kluent.shouldBeEqualTo +import org.amshove.kluent.shouldBeInstanceOf import org.junit.After import org.junit.Test 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.room.location.UpdateLiveLocationShareResult +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.internal.session.room.state.SendStateTask import org.matrix.android.sdk.test.fakes.FakeSendStateTask @@ -53,7 +57,7 @@ class DefaultStopLiveLocationShareTaskTest { } @Test - fun `given parameters when calling the task then it is correctly executed`() = runTest { + fun `given parameters and no error when calling the task then result is success`() = runTest { val params = StopLiveLocationShareTask.Params(roomId = A_ROOM_ID) val currentStateEvent = Event( stateKey = A_USER_ID, @@ -66,8 +70,9 @@ class DefaultStopLiveLocationShareTaskTest { fakeStateEventDataSource.givenGetStateEventReturns(currentStateEvent) fakeSendStateTask.givenExecuteRetryReturns(AN_EVENT_ID) - defaultStopLiveLocationShareTask.execute(params) + val result = defaultStopLiveLocationShareTask.execute(params) + result shouldBeEqualTo UpdateLiveLocationShareResult.Success(AN_EVENT_ID) val expectedBeaconContent = MessageBeaconInfoContent( timeout = A_TIMEOUT, isLive = false, @@ -89,4 +94,78 @@ class DefaultStopLiveLocationShareTaskTest { stateKey = A_USER_ID ) } + + @Test + fun `given parameters and an incorrect current state event when calling the task then result is failure`() = runTest { + val incorrectCurrentStateEvents = listOf( + // no event + null, + // no stateKey + Event( + stateKey = null, + content = MessageBeaconInfoContent( + timeout = A_TIMEOUT, + isLive = true, + unstableTimestampMillis = AN_EPOCH + ).toContent() + ), + // incorrect content + Event( + stateKey = A_USER_ID, + content = MessageAudioContent( + msgType = "", + body = "" + ).toContent() + ) + ) + + incorrectCurrentStateEvents.forEach { currentStateEvent -> + fakeStateEventDataSource.givenGetStateEventReturns(currentStateEvent) + fakeSendStateTask.givenExecuteRetryReturns(AN_EVENT_ID) + val params = StopLiveLocationShareTask.Params(roomId = A_ROOM_ID) + + val result = defaultStopLiveLocationShareTask.execute(params) + + result shouldBeInstanceOf UpdateLiveLocationShareResult.Failure::class + } + } + + @Test + fun `given parameters and an empty returned event id when calling the task then result is failure`() = runTest { + val params = StopLiveLocationShareTask.Params(roomId = A_ROOM_ID) + val currentStateEvent = Event( + stateKey = A_USER_ID, + content = MessageBeaconInfoContent( + timeout = A_TIMEOUT, + isLive = true, + unstableTimestampMillis = AN_EPOCH + ).toContent() + ) + fakeStateEventDataSource.givenGetStateEventReturns(currentStateEvent) + fakeSendStateTask.givenExecuteRetryReturns("") + + val result = defaultStopLiveLocationShareTask.execute(params) + + result shouldBeInstanceOf UpdateLiveLocationShareResult.Failure::class + } + + @Test + fun `given parameters and error during event sending when calling the task then result is failure`() = runTest { + val params = StopLiveLocationShareTask.Params(roomId = A_ROOM_ID) + val currentStateEvent = Event( + stateKey = A_USER_ID, + content = MessageBeaconInfoContent( + timeout = A_TIMEOUT, + isLive = true, + unstableTimestampMillis = AN_EPOCH + ).toContent() + ) + fakeStateEventDataSource.givenGetStateEventReturns(currentStateEvent) + val error = Throwable() + fakeSendStateTask.givenExecuteRetryThrows(error) + + val result = defaultStopLiveLocationShareTask.execute(params) + + result shouldBeEqualTo UpdateLiveLocationShareResult.Failure(error) + } } diff --git a/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fakes/FakeSendStateTask.kt b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fakes/FakeSendStateTask.kt index 0999ba619b..08a25be93e 100644 --- a/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fakes/FakeSendStateTask.kt +++ b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fakes/FakeSendStateTask.kt @@ -27,6 +27,10 @@ internal class FakeSendStateTask : SendStateTask by mockk() { coEvery { executeRetry(any(), any()) } returns eventId } + fun givenExecuteRetryThrows(error: Throwable) { + coEvery { executeRetry(any(), any()) } throws error + } + fun verifyExecuteRetry(params: SendStateTask.Params, remainingRetry: Int) { coVerify { executeRetry(params, remainingRetry) } } diff --git a/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fakes/FakeStateEventDataSource.kt b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fakes/FakeStateEventDataSource.kt index 498901bdac..ca03316fa7 100644 --- a/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fakes/FakeStateEventDataSource.kt +++ b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fakes/FakeStateEventDataSource.kt @@ -27,7 +27,7 @@ internal class FakeStateEventDataSource { val instance: StateEventDataSource = mockk() - fun givenGetStateEventReturns(event: Event) { + fun givenGetStateEventReturns(event: Event?) { every { instance.getStateEvent( roomId = any(), 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 07b20b4914..1c2255246b 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 @@ -1293,6 +1293,10 @@ class TimelineViewModel @AssistedInject constructor( locationSharingServiceConnection.bind(this) } + override fun onLocationServiceError(error: Throwable) { + _viewEvents.post(RoomDetailViewEvents.Failure(throwable = error, showInDialog = true)) + } + override fun onCleared() { timeline.dispose() timeline.removeAllListeners() 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 77f3abcc28..ef612eeec2 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 @@ -30,6 +30,7 @@ import kotlinx.coroutines.launch import kotlinx.parcelize.Parcelize import org.matrix.android.sdk.api.session.Session import org.matrix.android.sdk.api.session.getRoom +import org.matrix.android.sdk.api.session.room.location.UpdateLiveLocationShareResult import timber.log.Timber import java.util.Timer import java.util.TimerTask @@ -54,8 +55,9 @@ class LocationSharingService : VectorService(), LocationTracker.Callback { /** * Keep track of a map between beacon event Id starting the live and RoomArgs. */ - private var roomArgsMap = mutableMapOf() - private var timers = mutableListOf() + private val roomArgsMap = mutableMapOf() + private val timers = mutableListOf() + var callback: Callback? = null override fun onCreate() { super.onCreate() @@ -89,19 +91,27 @@ class LocationSharingService : VectorService(), LocationTracker.Callback { } private suspend fun sendStartingLiveBeaconInfo(session: Session, roomArgs: RoomArgs) { - val beaconEventId = session + val updateLiveResult = session .getRoom(roomArgs.roomId) ?.locationSharingService() ?.startLiveLocationShare(timeoutMillis = roomArgs.durationMillis) - beaconEventId - ?.takeUnless { it.isEmpty() } - ?.let { - roomArgsMap[it] = roomArgs - locationTracker.requestLastKnownLocation() + updateLiveResult + ?.let { result -> + when (result) { + is UpdateLiveLocationShareResult.Success -> { + roomArgsMap[result.beaconEventId] = roomArgs + locationTracker.requestLastKnownLocation() + } + is UpdateLiveLocationShareResult.Failure -> { + callback?.onServiceError(result.error) + tryToDestroyMe() + } + } } ?: run { Timber.w("### LocationSharingService.sendStartingLiveBeaconInfo error, no received beacon info id") + tryToDestroyMe() } } @@ -123,28 +133,28 @@ 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 - sendStoppedBeaconInfo(roomId) + launchInIO { session -> + when (val result = sendStoppedBeaconInfo(session, roomId)) { + is UpdateLiveLocationShareResult.Success -> { + synchronized(roomArgsMap) { + val beaconIds = roomArgsMap + .filter { it.value.roomId == roomId } + .map { it.key } + beaconIds.forEach { roomArgsMap.remove(it) } - synchronized(roomArgsMap) { - val beaconIds = roomArgsMap - .filter { it.value.roomId == roomId } - .map { it.key } - beaconIds.forEach { roomArgsMap.remove(it) } - - if (roomArgsMap.isEmpty()) { - Timber.i("### LocationSharingService. Destroying self, time is up for all rooms") - destroyMe() + tryToDestroyMe() + } + } + is UpdateLiveLocationShareResult.Failure -> callback?.onServiceError(result.error) + else -> Unit } } } - private fun sendStoppedBeaconInfo(roomId: String) { - launchInIO { session -> - session.getRoom(roomId) - ?.locationSharingService() - ?.stopLiveLocationShare() - } + private suspend fun sendStoppedBeaconInfo(session: Session, roomId: String): UpdateLiveLocationShareResult? { + return session.getRoom(roomId) + ?.locationSharingService() + ?.stopLiveLocationShare() } override fun onLocationUpdate(locationData: LocationData) { @@ -178,6 +188,13 @@ class LocationSharingService : VectorService(), LocationTracker.Callback { stopSelf() } + private fun tryToDestroyMe() { + if (roomArgsMap.isEmpty()) { + Timber.i("### LocationSharingService. Destroying self, time is up for all rooms") + destroyMe() + } + } + private fun destroyMe() { locationTracker.removeCallback(this) timers.forEach { it.cancel() } @@ -209,6 +226,10 @@ class LocationSharingService : VectorService(), LocationTracker.Callback { fun getService(): LocationSharingService = this@LocationSharingService } + interface Callback { + fun onServiceError(error: Throwable) + } + companion object { const val EXTRA_ROOM_ARGS = "EXTRA_ROOM_ARGS" } 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 e72f77531b..af09e0b1e0 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 @@ -25,11 +25,12 @@ import javax.inject.Inject class LocationSharingServiceConnection @Inject constructor( private val context: Context -) : ServiceConnection { +) : ServiceConnection, LocationSharingService.Callback { interface Callback { fun onLocationServiceRunning() fun onLocationServiceStopped() + fun onLocationServiceError(error: Throwable) } private var callback: Callback? = null @@ -57,14 +58,21 @@ class LocationSharingServiceConnection @Inject constructor( } override fun onServiceConnected(className: ComponentName, binder: IBinder) { - locationSharingService = (binder as LocationSharingService.LocalBinder).getService() + locationSharingService = (binder as LocationSharingService.LocalBinder).getService().also { + it.callback = this + } isBound = true callback?.onLocationServiceRunning() } override fun onServiceDisconnected(className: ComponentName) { isBound = false + locationSharingService?.callback = null locationSharingService = null callback?.onLocationServiceStopped() } + + override fun onServiceError(error: Throwable) { + callback?.onLocationServiceError(error) + } } diff --git a/vector/src/main/java/im/vector/app/features/location/live/map/LocationLiveMapViewEvents.kt b/vector/src/main/java/im/vector/app/features/location/live/map/LocationLiveMapViewEvents.kt index 6645ff58d9..23771299c8 100644 --- a/vector/src/main/java/im/vector/app/features/location/live/map/LocationLiveMapViewEvents.kt +++ b/vector/src/main/java/im/vector/app/features/location/live/map/LocationLiveMapViewEvents.kt @@ -18,4 +18,6 @@ package im.vector.app.features.location.live.map import im.vector.app.core.platform.VectorViewEvents -sealed interface LocationLiveMapViewEvents : VectorViewEvents +sealed interface LocationLiveMapViewEvents : VectorViewEvents { + data class Error(val error: Throwable) : LocationLiveMapViewEvents +} diff --git a/vector/src/main/java/im/vector/app/features/location/live/map/LocationLiveMapViewFragment.kt b/vector/src/main/java/im/vector/app/features/location/live/map/LocationLiveMapViewFragment.kt index 5f2410d697..a57ba74685 100644 --- a/vector/src/main/java/im/vector/app/features/location/live/map/LocationLiveMapViewFragment.kt +++ b/vector/src/main/java/im/vector/app/features/location/live/map/LocationLiveMapViewFragment.kt @@ -76,6 +76,8 @@ class LocationLiveMapViewFragment @Inject constructor() : VectorBaseFragment + when (viewEvent) { + is LocationLiveMapViewEvents.Error -> displayErrorDialog(viewEvent.error) + } + } + } + override fun onResume() { super.onResume() setupMap() diff --git a/vector/src/main/java/im/vector/app/features/location/live/map/LocationLiveMapViewModel.kt b/vector/src/main/java/im/vector/app/features/location/live/map/LocationLiveMapViewModel.kt index eb5bccff0f..e89649709a 100644 --- a/vector/src/main/java/im/vector/app/features/location/live/map/LocationLiveMapViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/location/live/map/LocationLiveMapViewModel.kt @@ -80,4 +80,8 @@ class LocationLiveMapViewModel @AssistedInject constructor( override fun onLocationServiceStopped() { // NOOP } + + override fun onLocationServiceError(error: Throwable) { + _viewEvents.post(LocationLiveMapViewEvents.Error(error)) + } }