diff --git a/changelog.d/6364.feature b/changelog.d/6364.feature new file mode 100644 index 0000000000..207d6d141b --- /dev/null +++ b/changelog.d/6364.feature @@ -0,0 +1 @@ +[Location sharing] - Stop any active live before starting a new one diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/RoomModule.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/RoomModule.kt index 271e82a1e0..c4d37d124b 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/RoomModule.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/RoomModule.kt @@ -51,10 +51,14 @@ import org.matrix.android.sdk.internal.session.room.directory.DefaultSetRoomDire import org.matrix.android.sdk.internal.session.room.directory.GetPublicRoomTask import org.matrix.android.sdk.internal.session.room.directory.GetRoomDirectoryVisibilityTask import org.matrix.android.sdk.internal.session.room.directory.SetRoomDirectoryVisibilityTask +import org.matrix.android.sdk.internal.session.room.location.CheckIfExistingActiveLiveTask +import org.matrix.android.sdk.internal.session.room.location.DefaultCheckIfExistingActiveLiveTask +import org.matrix.android.sdk.internal.session.room.location.DefaultGetActiveBeaconInfoForUserTask import org.matrix.android.sdk.internal.session.room.location.DefaultSendLiveLocationTask import org.matrix.android.sdk.internal.session.room.location.DefaultSendStaticLocationTask import org.matrix.android.sdk.internal.session.room.location.DefaultStartLiveLocationShareTask import org.matrix.android.sdk.internal.session.room.location.DefaultStopLiveLocationShareTask +import org.matrix.android.sdk.internal.session.room.location.GetActiveBeaconInfoForUserTask import org.matrix.android.sdk.internal.session.room.location.SendLiveLocationTask import org.matrix.android.sdk.internal.session.room.location.SendStaticLocationTask import org.matrix.android.sdk.internal.session.room.location.StartLiveLocationShareTask @@ -319,4 +323,10 @@ internal abstract class RoomModule { @Binds abstract fun bindSendLiveLocationTask(task: DefaultSendLiveLocationTask): SendLiveLocationTask + + @Binds + abstract fun bindGetActiveBeaconInfoForUserTask(task: DefaultGetActiveBeaconInfoForUserTask): GetActiveBeaconInfoForUserTask + + @Binds + abstract fun bindCheckIfExistingActiveLiveTask(task: DefaultCheckIfExistingActiveLiveTask): CheckIfExistingActiveLiveTask } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/location/CheckIfExistingActiveLiveTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/location/CheckIfExistingActiveLiveTask.kt new file mode 100644 index 0000000000..228a046f53 --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/location/CheckIfExistingActiveLiveTask.kt @@ -0,0 +1,45 @@ +/* + * 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.internal.session.room.location + +import org.matrix.android.sdk.api.extensions.orFalse +import org.matrix.android.sdk.api.session.events.model.toModel +import org.matrix.android.sdk.api.session.room.model.message.MessageBeaconInfoContent +import org.matrix.android.sdk.internal.task.Task +import javax.inject.Inject + +internal interface CheckIfExistingActiveLiveTask : Task { + data class Params( + val roomId: String, + ) +} + +internal class DefaultCheckIfExistingActiveLiveTask @Inject constructor( + private val getActiveBeaconInfoForUserTask: GetActiveBeaconInfoForUserTask, +) : CheckIfExistingActiveLiveTask { + + override suspend fun execute(params: CheckIfExistingActiveLiveTask.Params): Boolean { + val getActiveBeaconTaskParams = GetActiveBeaconInfoForUserTask.Params( + roomId = params.roomId + ) + return getActiveBeaconInfoForUserTask.execute(getActiveBeaconTaskParams) + ?.getClearContent() + ?.toModel() + ?.isLive + .orFalse() + } +} 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 8e67142c52..20320cad23 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 @@ -41,6 +41,7 @@ internal class DefaultLocationSharingService @AssistedInject constructor( private val sendLiveLocationTask: SendLiveLocationTask, private val startLiveLocationShareTask: StartLiveLocationShareTask, private val stopLiveLocationShareTask: StopLiveLocationShareTask, + private val checkIfExistingActiveLiveTask: CheckIfExistingActiveLiveTask, private val liveLocationShareAggregatedSummaryMapper: LiveLocationShareAggregatedSummaryMapper, ) : LocationSharingService { @@ -72,6 +73,13 @@ internal class DefaultLocationSharingService @AssistedInject constructor( } override suspend fun startLiveLocationShare(timeoutMillis: Long): UpdateLiveLocationShareResult { + // Ensure to stop any active live before starting a new one + if (checkIfExistingActiveLive()) { + val result = stopLiveLocationShare() + if (result is UpdateLiveLocationShareResult.Failure) { + return result + } + } val params = StartLiveLocationShareTask.Params( roomId = roomId, timeoutMillis = timeoutMillis @@ -79,6 +87,13 @@ internal class DefaultLocationSharingService @AssistedInject constructor( return startLiveLocationShareTask.execute(params) } + private suspend fun checkIfExistingActiveLive(): Boolean { + val params = CheckIfExistingActiveLiveTask.Params( + roomId = roomId + ) + return checkIfExistingActiveLiveTask.execute(params) + } + 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/GetActiveBeaconInfoForUserTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/location/GetActiveBeaconInfoForUserTask.kt new file mode 100644 index 0000000000..a8d955af1d --- /dev/null +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/location/GetActiveBeaconInfoForUserTask.kt @@ -0,0 +1,54 @@ +/* + * 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.internal.session.room.location + +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 +import org.matrix.android.sdk.api.session.events.model.toModel +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.StateEventDataSource +import org.matrix.android.sdk.internal.task.Task +import javax.inject.Inject + +internal interface GetActiveBeaconInfoForUserTask : Task { + data class Params( + val roomId: String, + ) +} + +internal class DefaultGetActiveBeaconInfoForUserTask @Inject constructor( + @UserId private val userId: String, + private val stateEventDataSource: StateEventDataSource, +) : GetActiveBeaconInfoForUserTask { + + override suspend fun execute(params: GetActiveBeaconInfoForUserTask.Params): Event? { + return EventType.STATE_ROOM_BEACON_INFO + .mapNotNull { + stateEventDataSource.getStateEvent( + roomId = params.roomId, + eventType = it, + stateKey = QueryStringValue.Equals(userId) + ) + } + .firstOrNull { beaconInfoEvent -> + beaconInfoEvent.getClearContent()?.toModel()?.isLive.orFalse() + } + } +} 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 d0e7ff3f82..da5fd76940 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 @@ -16,17 +16,13 @@ package org.matrix.android.sdk.internal.session.room.location -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 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 -import org.matrix.android.sdk.internal.session.room.state.StateEventDataSource import org.matrix.android.sdk.internal.task.Task import javax.inject.Inject @@ -37,13 +33,12 @@ internal interface StopLiveLocationShareTask : Task() ?: return getResultForIncorrectBeaconInfoEvent() val updatedContent = content.copy(isLive = false).toContent() @@ -68,17 +63,10 @@ internal class DefaultStopLiveLocationShareTask @Inject constructor( 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 { - stateEventDataSource.getStateEvent( - roomId = roomId, - eventType = it, - stateKey = QueryStringValue.Equals(userId) - ) - } - .firstOrNull { beaconInfoEvent -> - beaconInfoEvent.getClearContent()?.toModel()?.isLive.orFalse() - } + private suspend fun getActiveLiveLocationBeaconInfoForUser(roomId: String): Event? { + val params = GetActiveBeaconInfoForUserTask.Params( + roomId = roomId + ) + return getActiveBeaconInfoForUserTask.execute(params) } } diff --git a/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/session/room/location/DefaultCheckIfExistingActiveLiveTaskTest.kt b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/session/room/location/DefaultCheckIfExistingActiveLiveTaskTest.kt new file mode 100644 index 0000000000..3198392eab --- /dev/null +++ b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/session/room/location/DefaultCheckIfExistingActiveLiveTaskTest.kt @@ -0,0 +1,105 @@ +/* + * 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.internal.session.room.location + +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.events.model.Event +import org.matrix.android.sdk.api.session.events.model.toContent +import org.matrix.android.sdk.api.session.room.model.message.MessageBeaconInfoContent +import org.matrix.android.sdk.test.fakes.FakeGetActiveBeaconInfoForUserTask + +private const val A_USER_ID = "user-id" +private const val A_ROOM_ID = "room-id" +private const val A_TIMEOUT = 15_000L +private const val AN_EPOCH = 1655210176L + +@ExperimentalCoroutinesApi +class DefaultCheckIfExistingActiveLiveTaskTest { + + private val fakeGetActiveBeaconInfoForUserTask = FakeGetActiveBeaconInfoForUserTask() + + private val defaultCheckIfExistingActiveLiveTask = DefaultCheckIfExistingActiveLiveTask( + getActiveBeaconInfoForUserTask = fakeGetActiveBeaconInfoForUserTask + ) + + @After + fun tearDown() { + unmockkAll() + } + + @Test + fun `given parameters and existing active live event when calling the task then result is true`() = runTest { + val params = CheckIfExistingActiveLiveTask.Params( + roomId = A_ROOM_ID + ) + val currentStateEvent = Event( + stateKey = A_USER_ID, + content = MessageBeaconInfoContent( + timeout = A_TIMEOUT, + isLive = true, + unstableTimestampMillis = AN_EPOCH + ).toContent() + ) + fakeGetActiveBeaconInfoForUserTask.givenExecuteReturns(currentStateEvent) + + val result = defaultCheckIfExistingActiveLiveTask.execute(params) + + result shouldBeEqualTo true + val expectedGetActiveBeaconParams = GetActiveBeaconInfoForUserTask.Params( + roomId = params.roomId + ) + fakeGetActiveBeaconInfoForUserTask.verifyExecute(expectedGetActiveBeaconParams) + } + + @Test + fun `given parameters and no existing active live event when calling the task then result is false`() = runTest { + val params = CheckIfExistingActiveLiveTask.Params( + roomId = A_ROOM_ID + ) + val inactiveEvents = listOf( + // no event + null, + // null content + Event( + stateKey = A_USER_ID, + content = null + ), + // inactive live + Event( + stateKey = A_USER_ID, + content = MessageBeaconInfoContent( + timeout = A_TIMEOUT, + isLive = false, + unstableTimestampMillis = AN_EPOCH + ).toContent() + ) + ) + + inactiveEvents.forEach { currentStateEvent -> + fakeGetActiveBeaconInfoForUserTask.givenExecuteReturns(currentStateEvent) + + val result = defaultCheckIfExistingActiveLiveTask.execute(params) + + result shouldBeEqualTo false + } + } +} diff --git a/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/session/room/location/DefaultGetActiveBeaconInfoForUserTaskTest.kt b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/session/room/location/DefaultGetActiveBeaconInfoForUserTaskTest.kt new file mode 100644 index 0000000000..588bfaa979 --- /dev/null +++ b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/session/room/location/DefaultGetActiveBeaconInfoForUserTaskTest.kt @@ -0,0 +1,75 @@ +/* + * 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.internal.session.room.location + +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.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.model.message.MessageBeaconInfoContent +import org.matrix.android.sdk.test.fakes.FakeStateEventDataSource + +private const val A_USER_ID = "user-id" +private const val A_ROOM_ID = "room-id" +private const val A_TIMEOUT = 15_000L +private const val AN_EPOCH = 1655210176L + +@ExperimentalCoroutinesApi +class DefaultGetActiveBeaconInfoForUserTaskTest { + + private val fakeStateEventDataSource = FakeStateEventDataSource() + + private val defaultGetActiveBeaconInfoForUserTask = DefaultGetActiveBeaconInfoForUserTask( + userId = A_USER_ID, + stateEventDataSource = fakeStateEventDataSource.instance + ) + + @After + fun tearDown() { + unmockkAll() + } + + @Test + fun `given parameters and no error when calling the task then result is computed`() = runTest { + val currentStateEvent = Event( + stateKey = A_USER_ID, + content = MessageBeaconInfoContent( + timeout = A_TIMEOUT, + isLive = true, + unstableTimestampMillis = AN_EPOCH + ).toContent() + ) + fakeStateEventDataSource.givenGetStateEventReturns(currentStateEvent) + val params = GetActiveBeaconInfoForUserTask.Params( + roomId = A_ROOM_ID + ) + + val result = defaultGetActiveBeaconInfoForUserTask.execute(params) + + result shouldBeEqualTo currentStateEvent + fakeStateEventDataSource.verifyGetStateEvent( + roomId = params.roomId, + eventType = EventType.STATE_ROOM_BEACON_INFO.first(), + stateKey = A_USER_ID + ) + } +} 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 4b556402d5..de91206531 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 @@ -60,6 +60,7 @@ internal class DefaultLocationSharingServiceTest { private val sendLiveLocationTask = mockk() private val startLiveLocationShareTask = mockk() private val stopLiveLocationShareTask = mockk() + private val checkIfExistingActiveLiveTask = mockk() private val fakeLiveLocationShareAggregatedSummaryMapper = mockk() private val defaultLocationSharingService = DefaultLocationSharingService( @@ -69,6 +70,7 @@ internal class DefaultLocationSharingServiceTest { sendLiveLocationTask = sendLiveLocationTask, startLiveLocationShareTask = startLiveLocationShareTask, stopLiveLocationShareTask = stopLiveLocationShareTask, + checkIfExistingActiveLiveTask = checkIfExistingActiveLiveTask, liveLocationShareAggregatedSummaryMapper = fakeLiveLocationShareAggregatedSummaryMapper ) @@ -130,17 +132,65 @@ internal class DefaultLocationSharingServiceTest { } @Test - fun `live location share can be started with a given timeout`() = runTest { + fun `given existing active live can be stopped when starting a live then the current live is stopped and the new live is started`() = runTest { + coEvery { checkIfExistingActiveLiveTask.execute(any()) } returns true + coEvery { stopLiveLocationShareTask.execute(any()) } returns UpdateLiveLocationShareResult.Success("stopped-event-id") coEvery { startLiveLocationShareTask.execute(any()) } returns UpdateLiveLocationShareResult.Success(AN_EVENT_ID) val result = defaultLocationSharingService.startLiveLocationShare(A_TIMEOUT) result shouldBeEqualTo UpdateLiveLocationShareResult.Success(AN_EVENT_ID) - val expectedParams = StartLiveLocationShareTask.Params( + val expectedCheckExistingParams = CheckIfExistingActiveLiveTask.Params( + roomId = A_ROOM_ID + ) + coVerify { checkIfExistingActiveLiveTask.execute(expectedCheckExistingParams) } + val expectedStopParams = StopLiveLocationShareTask.Params( + roomId = A_ROOM_ID + ) + coVerify { stopLiveLocationShareTask.execute(expectedStopParams) } + val expectedStartParams = StartLiveLocationShareTask.Params( roomId = A_ROOM_ID, timeoutMillis = A_TIMEOUT ) - coVerify { startLiveLocationShareTask.execute(expectedParams) } + coVerify { startLiveLocationShareTask.execute(expectedStartParams) } + } + + @Test + fun `given existing active live cannot be stopped when starting a live then the result is failure`() = runTest { + coEvery { checkIfExistingActiveLiveTask.execute(any()) } returns true + val error = Throwable() + coEvery { stopLiveLocationShareTask.execute(any()) } returns UpdateLiveLocationShareResult.Failure(error) + + val result = defaultLocationSharingService.startLiveLocationShare(A_TIMEOUT) + + result shouldBeEqualTo UpdateLiveLocationShareResult.Failure(error) + val expectedCheckExistingParams = CheckIfExistingActiveLiveTask.Params( + roomId = A_ROOM_ID + ) + coVerify { checkIfExistingActiveLiveTask.execute(expectedCheckExistingParams) } + val expectedStopParams = StopLiveLocationShareTask.Params( + roomId = A_ROOM_ID + ) + coVerify { stopLiveLocationShareTask.execute(expectedStopParams) } + } + + @Test + fun `given no existing active live when starting a live then the new live is started`() = runTest { + coEvery { checkIfExistingActiveLiveTask.execute(any()) } returns false + coEvery { startLiveLocationShareTask.execute(any()) } returns UpdateLiveLocationShareResult.Success(AN_EVENT_ID) + + val result = defaultLocationSharingService.startLiveLocationShare(A_TIMEOUT) + + result shouldBeEqualTo UpdateLiveLocationShareResult.Success(AN_EVENT_ID) + val expectedCheckExistingParams = CheckIfExistingActiveLiveTask.Params( + roomId = A_ROOM_ID + ) + coVerify { checkIfExistingActiveLiveTask.execute(expectedCheckExistingParams) } + val expectedStartParams = StartLiveLocationShareTask.Params( + roomId = A_ROOM_ID, + timeoutMillis = A_TIMEOUT + ) + coVerify { startLiveLocationShareTask.execute(expectedStartParams) } } @Test 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 7cb5abff62..1abf179ccf 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 @@ -27,11 +27,10 @@ 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.FakeGetActiveBeaconInfoForUserTask import org.matrix.android.sdk.test.fakes.FakeSendStateTask -import org.matrix.android.sdk.test.fakes.FakeStateEventDataSource private const val A_USER_ID = "user-id" private const val A_ROOM_ID = "room-id" @@ -43,12 +42,11 @@ private const val AN_EPOCH = 1655210176L class DefaultStopLiveLocationShareTaskTest { private val fakeSendStateTask = FakeSendStateTask() - private val fakeStateEventDataSource = FakeStateEventDataSource() + private val fakeGetActiveBeaconInfoForUserTask = FakeGetActiveBeaconInfoForUserTask() private val defaultStopLiveLocationShareTask = DefaultStopLiveLocationShareTask( - userId = A_USER_ID, sendStateTask = fakeSendStateTask, - stateEventDataSource = fakeStateEventDataSource.instance + getActiveBeaconInfoForUserTask = fakeGetActiveBeaconInfoForUserTask ) @After @@ -67,7 +65,7 @@ class DefaultStopLiveLocationShareTaskTest { unstableTimestampMillis = AN_EPOCH ).toContent() ) - fakeStateEventDataSource.givenGetStateEventReturns(currentStateEvent) + fakeGetActiveBeaconInfoForUserTask.givenExecuteReturns(currentStateEvent) fakeSendStateTask.givenExecuteRetryReturns(AN_EVENT_ID) val result = defaultStopLiveLocationShareTask.execute(params) @@ -78,20 +76,21 @@ class DefaultStopLiveLocationShareTaskTest { isLive = false, unstableTimestampMillis = AN_EPOCH ).toContent() - val expectedParams = SendStateTask.Params( + val expectedSendParams = SendStateTask.Params( roomId = params.roomId, stateKey = A_USER_ID, eventType = EventType.STATE_ROOM_BEACON_INFO.first(), body = expectedBeaconContent ) fakeSendStateTask.verifyExecuteRetry( - params = expectedParams, + params = expectedSendParams, remainingRetry = 3 ) - fakeStateEventDataSource.verifyGetStateEvent( - roomId = params.roomId, - eventType = EventType.STATE_ROOM_BEACON_INFO.first(), - stateKey = A_USER_ID + val expectedGetBeaconParams = GetActiveBeaconInfoForUserTask.Params( + roomId = params.roomId + ) + fakeGetActiveBeaconInfoForUserTask.verifyExecute( + expectedGetBeaconParams ) } @@ -109,18 +108,15 @@ class DefaultStopLiveLocationShareTaskTest { unstableTimestampMillis = AN_EPOCH ).toContent() ), - // incorrect content + // null content Event( stateKey = A_USER_ID, - content = MessageAudioContent( - msgType = "", - body = "" - ).toContent() + content = null ) ) incorrectCurrentStateEvents.forEach { currentStateEvent -> - fakeStateEventDataSource.givenGetStateEventReturns(currentStateEvent) + fakeGetActiveBeaconInfoForUserTask.givenExecuteReturns(currentStateEvent) fakeSendStateTask.givenExecuteRetryReturns(AN_EVENT_ID) val params = StopLiveLocationShareTask.Params(roomId = A_ROOM_ID) @@ -141,7 +137,7 @@ class DefaultStopLiveLocationShareTaskTest { unstableTimestampMillis = AN_EPOCH ).toContent() ) - fakeStateEventDataSource.givenGetStateEventReturns(currentStateEvent) + fakeGetActiveBeaconInfoForUserTask.givenExecuteReturns(currentStateEvent) fakeSendStateTask.givenExecuteRetryReturns("") val result = defaultStopLiveLocationShareTask.execute(params) @@ -160,7 +156,7 @@ class DefaultStopLiveLocationShareTaskTest { unstableTimestampMillis = AN_EPOCH ).toContent() ) - fakeStateEventDataSource.givenGetStateEventReturns(currentStateEvent) + fakeGetActiveBeaconInfoForUserTask.givenExecuteReturns(currentStateEvent) val error = Throwable() fakeSendStateTask.givenExecuteRetryThrows(error) diff --git a/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fakes/FakeGetActiveBeaconInfoForUserTask.kt b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fakes/FakeGetActiveBeaconInfoForUserTask.kt new file mode 100644 index 0000000000..dc4a48908a --- /dev/null +++ b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fakes/FakeGetActiveBeaconInfoForUserTask.kt @@ -0,0 +1,34 @@ +/* + * 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.test.fakes + +import io.mockk.coEvery +import io.mockk.coVerify +import io.mockk.mockk +import org.matrix.android.sdk.api.session.events.model.Event +import org.matrix.android.sdk.internal.session.room.location.GetActiveBeaconInfoForUserTask + +internal class FakeGetActiveBeaconInfoForUserTask : GetActiveBeaconInfoForUserTask by mockk() { + + fun givenExecuteReturns(event: Event?) { + coEvery { execute(any()) } returns event + } + + fun verifyExecute(params: GetActiveBeaconInfoForUserTask.Params) { + coVerify { execute(params) } + } +} 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 0ba9e2134c..8073aaaa35 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 @@ -63,10 +63,11 @@ class LocationSharingService : VectorService(), LocationTracker.Callback { private val roomArgsMap = mutableMapOf() var callback: Callback? = null private val jobs = mutableListOf() + private var startInProgress = false override fun onCreate() { super.onCreate() - Timber.i("### LocationSharingService.onCreate") + Timber.i("onCreate") initLocationTracking() } @@ -85,9 +86,11 @@ class LocationSharingService : VectorService(), LocationTracker.Callback { } override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int { + startInProgress = true + val roomArgs = intent?.getParcelableExtra(EXTRA_ROOM_ARGS) as? RoomArgs - Timber.i("### LocationSharingService.onStartCommand. sessionId - roomId ${roomArgs?.sessionId} - ${roomArgs?.roomId}") + Timber.i("onStartCommand. sessionId - roomId ${roomArgs?.sessionId} - ${roomArgs?.roomId}") if (roomArgs != null) { // Show a sticky notification @@ -100,6 +103,8 @@ class LocationSharingService : VectorService(), LocationTracker.Callback { } } + startInProgress = false + return START_STICKY } @@ -124,19 +129,19 @@ class LocationSharingService : VectorService(), LocationTracker.Callback { } } ?: run { - Timber.w("### LocationSharingService.sendStartingLiveBeaconInfo error, no received beacon info id") + Timber.w("sendStartingLiveBeaconInfo error, no received beacon info id") tryToDestroyMe() } } - private fun stopSharingLocation(roomId: String) { - Timber.i("### LocationSharingService.stopSharingLocation for $roomId") - removeRoomArgs(roomId) + private fun stopSharingLocation(beaconEventId: String) { + Timber.i("stopSharingLocation for beacon $beaconEventId") + removeRoomArgs(beaconEventId) tryToDestroyMe() } private fun onLocationUpdate(locationData: LocationData) { - Timber.i("### LocationSharingService.onLocationUpdate. Uncertainty: ${locationData.uncertainty}") + Timber.i("onLocationUpdate. Uncertainty: ${locationData.uncertainty}") // Emit location update to all rooms in which live location sharing is active roomArgsMap.toMap().forEach { item -> @@ -167,36 +172,36 @@ class LocationSharingService : VectorService(), LocationTracker.Callback { } private fun tryToDestroyMe() { - if (roomArgsMap.isEmpty()) { - Timber.i("### LocationSharingService. Destroying self, time is up for all rooms") + if (startInProgress.not() && roomArgsMap.isEmpty()) { + Timber.i("Destroying self, time is up for all rooms") stopSelf() } } override fun onDestroy() { super.onDestroy() - Timber.i("### LocationSharingService.onDestroy") + Timber.i("onDestroy") jobs.forEach { it.cancel() } jobs.clear() locationTracker.removeCallback(this) } private fun addRoomArgs(beaconEventId: String, roomArgs: RoomArgs) { + Timber.i("adding roomArgs for beaconEventId: $beaconEventId") roomArgsMap[beaconEventId] = roomArgs } - private fun removeRoomArgs(roomId: String) { - roomArgsMap.toMap() - .filter { it.value.roomId == roomId } - .forEach { roomArgsMap.remove(it.key) } + private fun removeRoomArgs(beaconEventId: String) { + Timber.i("removing roomArgs for beaconEventId: $beaconEventId") + roomArgsMap.remove(beaconEventId) } - private fun listenForLiveSummaryChanges(roomId: String, eventId: String) { + private fun listenForLiveSummaryChanges(roomId: String, beaconEventId: String) { launchWithActiveSession { session -> - val job = getLiveLocationShareSummaryUseCase.execute(roomId, eventId) + val job = getLiveLocationShareSummaryUseCase.execute(roomId, beaconEventId) .distinctUntilChangedBy { it.isActive } .filter { it.isActive == false } - .onEach { stopSharingLocation(roomId) } + .onEach { stopSharingLocation(beaconEventId) } .launchIn(session.coroutineScope) jobs.add(job) }