Merge pull request #6316 from vector-im/fix/mna/crash-offline-lls
[Location sharing] Fix crash when starting/stopping a live when offline (PSF-1124)
This commit is contained in:
commit
32c6281dd2
1
changelog.d/6315.bugfix
Normal file
1
changelog.d/6315.bugfix
Normal file
@ -0,0 +1 @@
|
|||||||
|
[Location sharing] Fix crash when starting/stopping a live when offline
|
@ -46,14 +46,15 @@ interface LocationSharingService {
|
|||||||
/**
|
/**
|
||||||
* Starts sharing live location in the room.
|
* Starts sharing live location in the room.
|
||||||
* @param timeoutMillis timeout of the live in milliseconds
|
* @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.
|
* 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.
|
* Returns a LiveData on the list of current running live location shares.
|
||||||
|
@ -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
|
||||||
|
}
|
@ -22,6 +22,7 @@ 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.session.room.location.LocationSharingService
|
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.session.room.model.livelocation.LiveLocationShareAggregatedSummary
|
||||||
import org.matrix.android.sdk.api.util.Cancelable
|
import org.matrix.android.sdk.api.util.Cancelable
|
||||||
import org.matrix.android.sdk.internal.database.mapper.LiveLocationShareAggregatedSummaryMapper
|
import org.matrix.android.sdk.internal.database.mapper.LiveLocationShareAggregatedSummaryMapper
|
||||||
@ -66,7 +67,7 @@ internal class DefaultLocationSharingService @AssistedInject constructor(
|
|||||||
return sendLiveLocationTask.execute(params)
|
return sendLiveLocationTask.execute(params)
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun startLiveLocationShare(timeoutMillis: Long): String {
|
override suspend fun startLiveLocationShare(timeoutMillis: Long): UpdateLiveLocationShareResult {
|
||||||
val params = StartLiveLocationShareTask.Params(
|
val params = StartLiveLocationShareTask.Params(
|
||||||
roomId = roomId,
|
roomId = roomId,
|
||||||
timeoutMillis = timeoutMillis
|
timeoutMillis = timeoutMillis
|
||||||
@ -74,7 +75,7 @@ internal class DefaultLocationSharingService @AssistedInject constructor(
|
|||||||
return startLiveLocationShareTask.execute(params)
|
return startLiveLocationShareTask.execute(params)
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun stopLiveLocationShare() {
|
override suspend fun stopLiveLocationShare(): UpdateLiveLocationShareResult {
|
||||||
val params = StopLiveLocationShareTask.Params(
|
val params = StopLiveLocationShareTask.Params(
|
||||||
roomId = roomId,
|
roomId = roomId,
|
||||||
)
|
)
|
||||||
|
@ -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.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.room.location.UpdateLiveLocationShareResult
|
||||||
import org.matrix.android.sdk.api.session.room.model.message.MessageBeaconInfoContent
|
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.di.UserId
|
||||||
import org.matrix.android.sdk.internal.session.room.state.SendStateTask
|
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 org.matrix.android.sdk.internal.util.time.Clock
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
internal interface StartLiveLocationShareTask : Task<StartLiveLocationShareTask.Params, String> {
|
internal interface StartLiveLocationShareTask : Task<StartLiveLocationShareTask.Params, UpdateLiveLocationShareResult> {
|
||||||
data class Params(
|
data class Params(
|
||||||
val roomId: String,
|
val roomId: String,
|
||||||
val timeoutMillis: Long,
|
val timeoutMillis: Long,
|
||||||
@ -38,7 +39,7 @@ internal class DefaultStartLiveLocationShareTask @Inject constructor(
|
|||||||
private val sendStateTask: SendStateTask,
|
private val sendStateTask: SendStateTask,
|
||||||
) : StartLiveLocationShareTask {
|
) : StartLiveLocationShareTask {
|
||||||
|
|
||||||
override suspend fun execute(params: StartLiveLocationShareTask.Params): String {
|
override suspend fun execute(params: StartLiveLocationShareTask.Params): UpdateLiveLocationShareResult {
|
||||||
val beaconContent = MessageBeaconInfoContent(
|
val beaconContent = MessageBeaconInfoContent(
|
||||||
timeout = params.timeoutMillis,
|
timeout = params.timeoutMillis,
|
||||||
isLive = true,
|
isLive = true,
|
||||||
@ -51,6 +52,15 @@ internal class DefaultStartLiveLocationShareTask @Inject constructor(
|
|||||||
eventType = eventType,
|
eventType = eventType,
|
||||||
body = beaconContent
|
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)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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.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.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.api.session.room.model.message.MessageBeaconInfoContent
|
||||||
import org.matrix.android.sdk.internal.di.UserId
|
import org.matrix.android.sdk.internal.di.UserId
|
||||||
import org.matrix.android.sdk.internal.session.room.state.SendStateTask
|
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 org.matrix.android.sdk.internal.task.Task
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
internal interface StopLiveLocationShareTask : Task<StopLiveLocationShareTask.Params, Unit> {
|
internal interface StopLiveLocationShareTask : Task<StopLiveLocationShareTask.Params, UpdateLiveLocationShareResult> {
|
||||||
data class Params(
|
data class Params(
|
||||||
val roomId: String,
|
val roomId: String,
|
||||||
)
|
)
|
||||||
@ -41,10 +42,10 @@ internal class DefaultStopLiveLocationShareTask @Inject constructor(
|
|||||||
private val stateEventDataSource: StateEventDataSource,
|
private val stateEventDataSource: StateEventDataSource,
|
||||||
) : StopLiveLocationShareTask {
|
) : StopLiveLocationShareTask {
|
||||||
|
|
||||||
override suspend fun execute(params: StopLiveLocationShareTask.Params) {
|
override suspend fun execute(params: StopLiveLocationShareTask.Params): UpdateLiveLocationShareResult {
|
||||||
val beaconInfoStateEvent = getLiveLocationBeaconInfoForUser(userId, params.roomId) ?: return
|
val beaconInfoStateEvent = getLiveLocationBeaconInfoForUser(userId, params.roomId) ?: return getResultForIncorrectBeaconInfoEvent()
|
||||||
val stateKey = beaconInfoStateEvent.stateKey ?: return
|
val stateKey = beaconInfoStateEvent.stateKey ?: return getResultForIncorrectBeaconInfoEvent()
|
||||||
val content = beaconInfoStateEvent.getClearContent()?.toModel<MessageBeaconInfoContent>() ?: return
|
val content = beaconInfoStateEvent.getClearContent()?.toModel<MessageBeaconInfoContent>() ?: return getResultForIncorrectBeaconInfoEvent()
|
||||||
val updatedContent = content.copy(isLive = false).toContent()
|
val updatedContent = content.copy(isLive = false).toContent()
|
||||||
val sendStateTaskParams = SendStateTask.Params(
|
val sendStateTaskParams = SendStateTask.Params(
|
||||||
roomId = params.roomId,
|
roomId = params.roomId,
|
||||||
@ -52,9 +53,21 @@ internal class DefaultStopLiveLocationShareTask @Inject constructor(
|
|||||||
eventType = EventType.STATE_ROOM_BEACON_INFO.first(),
|
eventType = EventType.STATE_ROOM_BEACON_INFO.first(),
|
||||||
body = updatedContent
|
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? {
|
private fun getLiveLocationBeaconInfoForUser(userId: String, roomId: String): Event? {
|
||||||
return EventType.STATE_ROOM_BEACON_INFO
|
return EventType.STATE_ROOM_BEACON_INFO
|
||||||
.mapNotNull {
|
.mapNotNull {
|
||||||
|
@ -18,15 +18,14 @@ package org.matrix.android.sdk.internal.session.room.location
|
|||||||
|
|
||||||
import io.mockk.coEvery
|
import io.mockk.coEvery
|
||||||
import io.mockk.coVerify
|
import io.mockk.coVerify
|
||||||
import io.mockk.just
|
|
||||||
import io.mockk.mockk
|
import io.mockk.mockk
|
||||||
import io.mockk.runs
|
|
||||||
import io.mockk.unmockkAll
|
import io.mockk.unmockkAll
|
||||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||||
import kotlinx.coroutines.test.runTest
|
import kotlinx.coroutines.test.runTest
|
||||||
import org.amshove.kluent.shouldBeEqualTo
|
import org.amshove.kluent.shouldBeEqualTo
|
||||||
import org.junit.After
|
import org.junit.After
|
||||||
import org.junit.Test
|
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.session.room.model.livelocation.LiveLocationShareAggregatedSummary
|
||||||
import org.matrix.android.sdk.api.util.Cancelable
|
import org.matrix.android.sdk.api.util.Cancelable
|
||||||
import org.matrix.android.sdk.internal.database.mapper.LiveLocationShareAggregatedSummaryMapper
|
import org.matrix.android.sdk.internal.database.mapper.LiveLocationShareAggregatedSummaryMapper
|
||||||
@ -119,11 +118,11 @@ internal class DefaultLocationSharingServiceTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `live location share can be started with a given timeout`() = runTest {
|
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(
|
val expectedParams = StartLiveLocationShareTask.Params(
|
||||||
roomId = A_ROOM_ID,
|
roomId = A_ROOM_ID,
|
||||||
timeoutMillis = A_TIMEOUT
|
timeoutMillis = A_TIMEOUT
|
||||||
@ -133,10 +132,11 @@ internal class DefaultLocationSharingServiceTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `live location share can be stopped`() = runTest {
|
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(
|
val expectedParams = StopLiveLocationShareTask.Params(
|
||||||
roomId = A_ROOM_ID
|
roomId = A_ROOM_ID
|
||||||
)
|
)
|
||||||
|
@ -20,10 +20,12 @@ import io.mockk.unmockkAll
|
|||||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||||
import kotlinx.coroutines.test.runTest
|
import kotlinx.coroutines.test.runTest
|
||||||
import org.amshove.kluent.shouldBeEqualTo
|
import org.amshove.kluent.shouldBeEqualTo
|
||||||
|
import org.amshove.kluent.shouldBeInstanceOf
|
||||||
import org.junit.After
|
import org.junit.After
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
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.room.location.UpdateLiveLocationShareResult
|
||||||
import org.matrix.android.sdk.api.session.room.model.message.MessageBeaconInfoContent
|
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.internal.session.room.state.SendStateTask
|
||||||
import org.matrix.android.sdk.test.fakes.FakeClock
|
import org.matrix.android.sdk.test.fakes.FakeClock
|
||||||
@ -53,7 +55,7 @@ internal class DefaultStartLiveLocationShareTaskTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@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(
|
val params = StartLiveLocationShareTask.Params(
|
||||||
roomId = A_ROOM_ID,
|
roomId = A_ROOM_ID,
|
||||||
timeoutMillis = A_TIMEOUT
|
timeoutMillis = A_TIMEOUT
|
||||||
@ -63,7 +65,7 @@ internal class DefaultStartLiveLocationShareTaskTest {
|
|||||||
|
|
||||||
val result = defaultStartLiveLocationShareTask.execute(params)
|
val result = defaultStartLiveLocationShareTask.execute(params)
|
||||||
|
|
||||||
result shouldBeEqualTo AN_EVENT_ID
|
result shouldBeEqualTo UpdateLiveLocationShareResult.Success(AN_EVENT_ID)
|
||||||
val expectedBeaconContent = MessageBeaconInfoContent(
|
val expectedBeaconContent = MessageBeaconInfoContent(
|
||||||
timeout = params.timeoutMillis,
|
timeout = params.timeoutMillis,
|
||||||
isLive = true,
|
isLive = true,
|
||||||
@ -80,4 +82,33 @@ internal class DefaultStartLiveLocationShareTaskTest {
|
|||||||
remainingRetry = 3
|
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)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -19,11 +19,15 @@ package org.matrix.android.sdk.internal.session.room.location
|
|||||||
import io.mockk.unmockkAll
|
import io.mockk.unmockkAll
|
||||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||||
import kotlinx.coroutines.test.runTest
|
import kotlinx.coroutines.test.runTest
|
||||||
|
import org.amshove.kluent.shouldBeEqualTo
|
||||||
|
import org.amshove.kluent.shouldBeInstanceOf
|
||||||
import org.junit.After
|
import org.junit.After
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
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.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.api.session.room.model.message.MessageBeaconInfoContent
|
||||||
import org.matrix.android.sdk.internal.session.room.state.SendStateTask
|
import org.matrix.android.sdk.internal.session.room.state.SendStateTask
|
||||||
import org.matrix.android.sdk.test.fakes.FakeSendStateTask
|
import org.matrix.android.sdk.test.fakes.FakeSendStateTask
|
||||||
@ -53,7 +57,7 @@ class DefaultStopLiveLocationShareTaskTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@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 params = StopLiveLocationShareTask.Params(roomId = A_ROOM_ID)
|
||||||
val currentStateEvent = Event(
|
val currentStateEvent = Event(
|
||||||
stateKey = A_USER_ID,
|
stateKey = A_USER_ID,
|
||||||
@ -66,8 +70,9 @@ class DefaultStopLiveLocationShareTaskTest {
|
|||||||
fakeStateEventDataSource.givenGetStateEventReturns(currentStateEvent)
|
fakeStateEventDataSource.givenGetStateEventReturns(currentStateEvent)
|
||||||
fakeSendStateTask.givenExecuteRetryReturns(AN_EVENT_ID)
|
fakeSendStateTask.givenExecuteRetryReturns(AN_EVENT_ID)
|
||||||
|
|
||||||
defaultStopLiveLocationShareTask.execute(params)
|
val result = defaultStopLiveLocationShareTask.execute(params)
|
||||||
|
|
||||||
|
result shouldBeEqualTo UpdateLiveLocationShareResult.Success(AN_EVENT_ID)
|
||||||
val expectedBeaconContent = MessageBeaconInfoContent(
|
val expectedBeaconContent = MessageBeaconInfoContent(
|
||||||
timeout = A_TIMEOUT,
|
timeout = A_TIMEOUT,
|
||||||
isLive = false,
|
isLive = false,
|
||||||
@ -89,4 +94,78 @@ class DefaultStopLiveLocationShareTaskTest {
|
|||||||
stateKey = A_USER_ID
|
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)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -27,6 +27,10 @@ internal class FakeSendStateTask : SendStateTask by mockk() {
|
|||||||
coEvery { executeRetry(any(), any()) } returns eventId
|
coEvery { executeRetry(any(), any()) } returns eventId
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun givenExecuteRetryThrows(error: Throwable) {
|
||||||
|
coEvery { executeRetry(any(), any()) } throws error
|
||||||
|
}
|
||||||
|
|
||||||
fun verifyExecuteRetry(params: SendStateTask.Params, remainingRetry: Int) {
|
fun verifyExecuteRetry(params: SendStateTask.Params, remainingRetry: Int) {
|
||||||
coVerify { executeRetry(params, remainingRetry) }
|
coVerify { executeRetry(params, remainingRetry) }
|
||||||
}
|
}
|
||||||
|
@ -27,7 +27,7 @@ internal class FakeStateEventDataSource {
|
|||||||
|
|
||||||
val instance: StateEventDataSource = mockk()
|
val instance: StateEventDataSource = mockk()
|
||||||
|
|
||||||
fun givenGetStateEventReturns(event: Event) {
|
fun givenGetStateEventReturns(event: Event?) {
|
||||||
every {
|
every {
|
||||||
instance.getStateEvent(
|
instance.getStateEvent(
|
||||||
roomId = any(),
|
roomId = any(),
|
||||||
|
@ -1293,6 +1293,10 @@ class TimelineViewModel @AssistedInject constructor(
|
|||||||
locationSharingServiceConnection.bind(this)
|
locationSharingServiceConnection.bind(this)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun onLocationServiceError(error: Throwable) {
|
||||||
|
_viewEvents.post(RoomDetailViewEvents.Failure(throwable = error, showInDialog = true))
|
||||||
|
}
|
||||||
|
|
||||||
override fun onCleared() {
|
override fun onCleared() {
|
||||||
timeline.dispose()
|
timeline.dispose()
|
||||||
timeline.removeAllListeners()
|
timeline.removeAllListeners()
|
||||||
|
@ -30,6 +30,7 @@ import kotlinx.coroutines.launch
|
|||||||
import kotlinx.parcelize.Parcelize
|
import kotlinx.parcelize.Parcelize
|
||||||
import org.matrix.android.sdk.api.session.Session
|
import org.matrix.android.sdk.api.session.Session
|
||||||
import org.matrix.android.sdk.api.session.getRoom
|
import org.matrix.android.sdk.api.session.getRoom
|
||||||
|
import org.matrix.android.sdk.api.session.room.location.UpdateLiveLocationShareResult
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
import java.util.Timer
|
import java.util.Timer
|
||||||
import java.util.TimerTask
|
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.
|
* Keep track of a map between beacon event Id starting the live and RoomArgs.
|
||||||
*/
|
*/
|
||||||
private var roomArgsMap = mutableMapOf<String, RoomArgs>()
|
private val roomArgsMap = mutableMapOf<String, RoomArgs>()
|
||||||
private var timers = mutableListOf<Timer>()
|
private val timers = mutableListOf<Timer>()
|
||||||
|
var callback: Callback? = null
|
||||||
|
|
||||||
override fun onCreate() {
|
override fun onCreate() {
|
||||||
super.onCreate()
|
super.onCreate()
|
||||||
@ -89,19 +91,27 @@ class LocationSharingService : VectorService(), LocationTracker.Callback {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun sendStartingLiveBeaconInfo(session: Session, roomArgs: RoomArgs) {
|
private suspend fun sendStartingLiveBeaconInfo(session: Session, roomArgs: RoomArgs) {
|
||||||
val beaconEventId = session
|
val updateLiveResult = session
|
||||||
.getRoom(roomArgs.roomId)
|
.getRoom(roomArgs.roomId)
|
||||||
?.locationSharingService()
|
?.locationSharingService()
|
||||||
?.startLiveLocationShare(timeoutMillis = roomArgs.durationMillis)
|
?.startLiveLocationShare(timeoutMillis = roomArgs.durationMillis)
|
||||||
|
|
||||||
beaconEventId
|
updateLiveResult
|
||||||
?.takeUnless { it.isEmpty() }
|
?.let { result ->
|
||||||
?.let {
|
when (result) {
|
||||||
roomArgsMap[it] = roomArgs
|
is UpdateLiveLocationShareResult.Success -> {
|
||||||
locationTracker.requestLastKnownLocation()
|
roomArgsMap[result.beaconEventId] = roomArgs
|
||||||
|
locationTracker.requestLastKnownLocation()
|
||||||
|
}
|
||||||
|
is UpdateLiveLocationShareResult.Failure -> {
|
||||||
|
callback?.onServiceError(result.error)
|
||||||
|
tryToDestroyMe()
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
?: run {
|
?: run {
|
||||||
Timber.w("### LocationSharingService.sendStartingLiveBeaconInfo error, no received beacon info id")
|
Timber.w("### LocationSharingService.sendStartingLiveBeaconInfo error, no received beacon info id")
|
||||||
|
tryToDestroyMe()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -123,28 +133,28 @@ class LocationSharingService : VectorService(), LocationTracker.Callback {
|
|||||||
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
|
launchInIO { session ->
|
||||||
sendStoppedBeaconInfo(roomId)
|
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) {
|
tryToDestroyMe()
|
||||||
val beaconIds = roomArgsMap
|
}
|
||||||
.filter { it.value.roomId == roomId }
|
}
|
||||||
.map { it.key }
|
is UpdateLiveLocationShareResult.Failure -> callback?.onServiceError(result.error)
|
||||||
beaconIds.forEach { roomArgsMap.remove(it) }
|
else -> Unit
|
||||||
|
|
||||||
if (roomArgsMap.isEmpty()) {
|
|
||||||
Timber.i("### LocationSharingService. Destroying self, time is up for all rooms")
|
|
||||||
destroyMe()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun sendStoppedBeaconInfo(roomId: String) {
|
private suspend fun sendStoppedBeaconInfo(session: Session, roomId: String): UpdateLiveLocationShareResult? {
|
||||||
launchInIO { session ->
|
return session.getRoom(roomId)
|
||||||
session.getRoom(roomId)
|
?.locationSharingService()
|
||||||
?.locationSharingService()
|
?.stopLiveLocationShare()
|
||||||
?.stopLiveLocationShare()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onLocationUpdate(locationData: LocationData) {
|
override fun onLocationUpdate(locationData: LocationData) {
|
||||||
@ -178,6 +188,13 @@ class LocationSharingService : VectorService(), LocationTracker.Callback {
|
|||||||
stopSelf()
|
stopSelf()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun tryToDestroyMe() {
|
||||||
|
if (roomArgsMap.isEmpty()) {
|
||||||
|
Timber.i("### LocationSharingService. Destroying self, time is up for all rooms")
|
||||||
|
destroyMe()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private fun destroyMe() {
|
private fun destroyMe() {
|
||||||
locationTracker.removeCallback(this)
|
locationTracker.removeCallback(this)
|
||||||
timers.forEach { it.cancel() }
|
timers.forEach { it.cancel() }
|
||||||
@ -209,6 +226,10 @@ class LocationSharingService : VectorService(), LocationTracker.Callback {
|
|||||||
fun getService(): LocationSharingService = this@LocationSharingService
|
fun getService(): LocationSharingService = this@LocationSharingService
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface Callback {
|
||||||
|
fun onServiceError(error: Throwable)
|
||||||
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
const val EXTRA_ROOM_ARGS = "EXTRA_ROOM_ARGS"
|
const val EXTRA_ROOM_ARGS = "EXTRA_ROOM_ARGS"
|
||||||
}
|
}
|
||||||
|
@ -25,11 +25,12 @@ import javax.inject.Inject
|
|||||||
|
|
||||||
class LocationSharingServiceConnection @Inject constructor(
|
class LocationSharingServiceConnection @Inject constructor(
|
||||||
private val context: Context
|
private val context: Context
|
||||||
) : ServiceConnection {
|
) : ServiceConnection, LocationSharingService.Callback {
|
||||||
|
|
||||||
interface Callback {
|
interface Callback {
|
||||||
fun onLocationServiceRunning()
|
fun onLocationServiceRunning()
|
||||||
fun onLocationServiceStopped()
|
fun onLocationServiceStopped()
|
||||||
|
fun onLocationServiceError(error: Throwable)
|
||||||
}
|
}
|
||||||
|
|
||||||
private var callback: Callback? = null
|
private var callback: Callback? = null
|
||||||
@ -57,14 +58,21 @@ class LocationSharingServiceConnection @Inject constructor(
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun onServiceConnected(className: ComponentName, binder: IBinder) {
|
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
|
isBound = true
|
||||||
callback?.onLocationServiceRunning()
|
callback?.onLocationServiceRunning()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onServiceDisconnected(className: ComponentName) {
|
override fun onServiceDisconnected(className: ComponentName) {
|
||||||
isBound = false
|
isBound = false
|
||||||
|
locationSharingService?.callback = null
|
||||||
locationSharingService = null
|
locationSharingService = null
|
||||||
callback?.onLocationServiceStopped()
|
callback?.onLocationServiceStopped()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun onServiceError(error: Throwable) {
|
||||||
|
callback?.onLocationServiceError(error)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -18,4 +18,6 @@ package im.vector.app.features.location.live.map
|
|||||||
|
|
||||||
import im.vector.app.core.platform.VectorViewEvents
|
import im.vector.app.core.platform.VectorViewEvents
|
||||||
|
|
||||||
sealed interface LocationLiveMapViewEvents : VectorViewEvents
|
sealed interface LocationLiveMapViewEvents : VectorViewEvents {
|
||||||
|
data class Error(val error: Throwable) : LocationLiveMapViewEvents
|
||||||
|
}
|
||||||
|
@ -76,6 +76,8 @@ class LocationLiveMapViewFragment @Inject constructor() : VectorBaseFragment<Fra
|
|||||||
|
|
||||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||||
super.onViewCreated(view, savedInstanceState)
|
super.onViewCreated(view, savedInstanceState)
|
||||||
|
observeViewEvents()
|
||||||
|
|
||||||
views.liveLocationBottomSheetRecyclerView.configureWith(bottomSheetController, hasFixedSize = false, disableItemAnimation = true)
|
views.liveLocationBottomSheetRecyclerView.configureWith(bottomSheetController, hasFixedSize = false, disableItemAnimation = true)
|
||||||
|
|
||||||
bottomSheetController.callback = object : LiveLocationBottomSheetController.Callback {
|
bottomSheetController.callback = object : LiveLocationBottomSheetController.Callback {
|
||||||
@ -89,6 +91,14 @@ class LocationLiveMapViewFragment @Inject constructor() : VectorBaseFragment<Fra
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun observeViewEvents() {
|
||||||
|
viewModel.observeViewEvents { viewEvent ->
|
||||||
|
when (viewEvent) {
|
||||||
|
is LocationLiveMapViewEvents.Error -> displayErrorDialog(viewEvent.error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
override fun onResume() {
|
override fun onResume() {
|
||||||
super.onResume()
|
super.onResume()
|
||||||
setupMap()
|
setupMap()
|
||||||
|
@ -80,4 +80,8 @@ class LocationLiveMapViewModel @AssistedInject constructor(
|
|||||||
override fun onLocationServiceStopped() {
|
override fun onLocationServiceStopped() {
|
||||||
// NOOP
|
// NOOP
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun onLocationServiceError(error: Throwable) {
|
||||||
|
_viewEvents.post(LocationLiveMapViewEvents.Error(error))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user