From b16ccf5098d917dac7291c6d3470cf6a16bb2274 Mon Sep 17 00:00:00 2001
From: Maxime NATUREL <maxime.naturel@niji.fr>
Date: Mon, 20 Jun 2022 10:16:27 +0200
Subject: [PATCH 01/11] Fix unit tests after rebase

---
 .../DefaultStopLiveLocationShareTaskTest.kt   |  3 +-
 .../android/sdk/test/fakes/FakeRealm.kt       | 43 -------------------
 .../test/fakes/FakeStateEventDataSource.kt    |  4 +-
 3 files changed, 3 insertions(+), 47 deletions(-)

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 55d13803b9..81a5742f90 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
@@ -21,7 +21,6 @@ import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.test.runTest
 import org.junit.After
 import org.junit.Test
-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
@@ -87,7 +86,7 @@ class DefaultStopLiveLocationShareTaskTest {
         fakeStateEventDataSource.verifyGetStateEvent(
                 roomId = params.roomId,
                 eventType = EventType.STATE_ROOM_BEACON_INFO.first(),
-                stateKey = QueryStringValue.Equals(A_USER_ID)
+                stateKey = A_USER_ID
         )
     }
 }
diff --git a/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fakes/FakeRealm.kt b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fakes/FakeRealm.kt
index d60c9a627e..0ebff87278 100644
--- a/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fakes/FakeRealm.kt
+++ b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fakes/FakeRealm.kt
@@ -97,46 +97,3 @@ inline fun <reified T : RealmModel> RealmQuery<T>.givenIsNotNull(
     every { isNotNull(fieldName) } returns this
     return this
 }
-
-inline fun <reified T : RealmModel> RealmQuery<T>.givenFindFirst(
-        result: T?
-): RealmQuery<T> {
-    every { findFirst() } returns result
-    return this
-}
-
-inline fun <reified T : RealmModel> RealmQuery<T>.givenFindAll(
-        result: List<T>
-): RealmQuery<T> {
-    val realmResults = mockk<RealmResults<T>>()
-    result.forEachIndexed { index, t ->
-        every { realmResults[index] } returns t
-    }
-    every { realmResults.size } returns result.size
-    every { findAll() } returns realmResults
-    return this
-}
-
-inline fun <reified T : RealmModel> RealmQuery<T>.givenEqualTo(
-        fieldName: String,
-        value: String
-): RealmQuery<T> {
-    every { equalTo(fieldName, value) } returns this
-    return this
-}
-
-inline fun <reified T : RealmModel> RealmQuery<T>.givenEqualTo(
-        fieldName: String,
-        value: Boolean
-): RealmQuery<T> {
-    every { equalTo(fieldName, value) } returns this
-    return this
-}
-
-inline fun <reified T : RealmModel> RealmQuery<T>.givenNotEqualTo(
-        fieldName: String,
-        value: String
-): RealmQuery<T> {
-    every { notEqualTo(fieldName, value) } returns this
-    return this
-}
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 e4f19abaa7..498901bdac 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
@@ -37,12 +37,12 @@ internal class FakeStateEventDataSource {
         } returns event
     }
 
-    fun verifyGetStateEvent(roomId: String, eventType: String, stateKey: QueryStringValue) {
+    fun verifyGetStateEvent(roomId: String, eventType: String, stateKey: String) {
         verify {
             instance.getStateEvent(
                     roomId = roomId,
                     eventType = eventType,
-                    stateKey = stateKey
+                    stateKey = QueryStringValue.Equals(stateKey)
             )
         }
     }

From e55c378683f5026055051ce6eb80fa0e32fb2c11 Mon Sep 17 00:00:00 2001
From: Maxime NATUREL <maxime.naturel@niji.fr>
Date: Wed, 15 Jun 2022 10:01:35 +0200
Subject: [PATCH 02/11] Catching crash when offline during start of a live
 location share

---
 .../room/location/LocationSharingService.kt   |  4 +--
 .../location/UpdateLiveLocationShareResult.kt | 32 +++++++++++++++++++
 .../location/DefaultLocationSharingService.kt |  3 +-
 .../location/StartLiveLocationShareTask.kt    | 17 ++++++++--
 .../location/StopLiveLocationShareTask.kt     |  1 +
 .../location/LocationSharingService.kt        | 28 +++++++++++-----
 6 files changed, 71 insertions(+), 14 deletions(-)
 create mode 100644 matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/location/UpdateLiveLocationShareResult.kt

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..ce0b746d4f 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,9 +46,9 @@ 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.
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..6c6bc1029a
--- /dev/null
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/location/UpdateLiveLocationShareResult.kt
@@ -0,0 +1,32 @@
+/*
+ * Copyright (c) 2022 New Vector Ltd
+ *
+ * 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 {
+    /**
+     * @param beaconEventId event id of the updated state event
+     */
+    data class Success(val beaconEventId: String) : UpdateLiveLocationShareResult
+
+    /**
+     * @param error thrown during the update
+     */
+    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..c15cdda6f3 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
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..bf6a0049d8 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,20 +26,21 @@ 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<StartLiveLocationShareTask.Params, String> {
+internal interface StartLiveLocationShareTask : Task<StartLiveLocationShareTask.Params, UpdateLiveLocationShareResult> {
     data class Params(
             val roomId: String,
             val timeoutMillis: Long,
     )
 }
 
+// TODO update unit test
 internal class DefaultStartLiveLocationShareTask @Inject constructor(
         @UserId private val userId: String,
         private val clock: Clock,
         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 +53,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..8f2fa27288 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
@@ -41,6 +41,7 @@ internal class DefaultStopLiveLocationShareTask @Inject constructor(
         private val stateEventDataSource: StateEventDataSource,
 ) : StopLiveLocationShareTask {
 
+    @Throws
     override suspend fun execute(params: StopLiveLocationShareTask.Params) {
         val beaconInfoStateEvent = getLiveLocationBeaconInfoForUser(userId, params.roomId) ?: return
         val stateKey = beaconInfoStateEvent.stateKey ?: return
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..7df84a4cee 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
@@ -95,13 +96,20 @@ class LocationSharingService : VectorService(), LocationTracker.Callback {
                 ?.startLiveLocationShare(timeoutMillis = roomArgs.durationMillis)
 
         beaconEventId
-                ?.takeUnless { it.isEmpty() }
-                ?.let {
-                    roomArgsMap[it] = roomArgs
-                    locationTracker.requestLastKnownLocation()
+                ?.let { result ->
+                    when (result) {
+                        is UpdateLiveLocationShareResult.Success -> {
+                            roomArgsMap[result.beaconEventId] = roomArgs
+                            locationTracker.requestLastKnownLocation()
+                        }
+                        is UpdateLiveLocationShareResult.Failure -> {
+                            tryToDestroyMe()
+                        }
+                    }
                 }
                 ?: run {
                     Timber.w("### LocationSharingService.sendStartingLiveBeaconInfo error, no received beacon info id")
+                    tryToDestroyMe()
                 }
     }
 
@@ -132,10 +140,7 @@ class LocationSharingService : VectorService(), LocationTracker.Callback {
                     .map { it.key }
             beaconIds.forEach { roomArgsMap.remove(it) }
 
-            if (roomArgsMap.isEmpty()) {
-                Timber.i("### LocationSharingService. Destroying self, time is up for all rooms")
-                destroyMe()
-            }
+            tryToDestroyMe()
         }
     }
 
@@ -178,6 +183,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() }

From 9eba3034db3a3bfa22833520437639bcf43f8f91 Mon Sep 17 00:00:00 2001
From: Maxime NATUREL <maxime.naturel@niji.fr>
Date: Wed, 15 Jun 2022 12:09:39 +0200
Subject: [PATCH 03/11] Catching crash when offline during stop of a live
 location share

---
 .../room/location/LocationSharingService.kt   |  2 +-
 .../location/DefaultLocationSharingService.kt |  2 +-
 .../location/StopLiveLocationShareTask.kt     | 27 +++++++++++----
 .../location/LocationSharingService.kt        | 33 ++++++++++---------
 .../LocationSharingServiceConnection.kt       |  1 +
 5 files changed, 41 insertions(+), 24 deletions(-)

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 ce0b746d4f..7e5906b517 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
@@ -53,7 +53,7 @@ interface LocationSharingService {
     /**
      * Stops sharing live location in the room.
      */
-    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/internal/session/room/location/DefaultLocationSharingService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/location/DefaultLocationSharingService.kt
index c15cdda6f3..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
@@ -75,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/StopLiveLocationShareTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/location/StopLiveLocationShareTask.kt
index 8f2fa27288..dc12054d7b 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,23 +30,23 @@ 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<StopLiveLocationShareTask.Params, Unit> {
+internal interface StopLiveLocationShareTask : Task<StopLiveLocationShareTask.Params, UpdateLiveLocationShareResult> {
     data class Params(
             val roomId: String,
     )
 }
 
+// TODO update unit tests
 internal class DefaultStopLiveLocationShareTask @Inject constructor(
         @UserId private val userId: String,
         private val sendStateTask: SendStateTask,
         private val stateEventDataSource: StateEventDataSource,
 ) : StopLiveLocationShareTask {
 
-    @Throws
-    override suspend fun execute(params: StopLiveLocationShareTask.Params) {
-        val beaconInfoStateEvent = getLiveLocationBeaconInfoForUser(userId, params.roomId) ?: return
-        val stateKey = beaconInfoStateEvent.stateKey ?: return
-        val content = beaconInfoStateEvent.getClearContent()?.toModel<MessageBeaconInfoContent>() ?: 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<MessageBeaconInfoContent>() ?: return getResultForIncorrectBeaconInfoEvent()
         val updatedContent = content.copy(isLive = false).toContent()
         val sendStateTaskParams = SendStateTask.Params(
                 roomId = params.roomId,
@@ -53,9 +54,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/vector/src/main/java/im/vector/app/features/location/LocationSharingService.kt b/vector/src/main/java/im/vector/app/features/location/LocationSharingService.kt
index 7df84a4cee..27eea498e4 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
@@ -131,25 +131,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 ->
+            // Send a new beacon info state by setting live field as false
+            when (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) }
-
-            tryToDestroyMe()
+                        tryToDestroyMe()
+                    }
+                }
+                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) {
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..97f447ec4b 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
@@ -28,6 +28,7 @@ class LocationSharingServiceConnection @Inject constructor(
 ) : ServiceConnection {
 
     interface Callback {
+        // TODO add onLocationServiceError()
         fun onLocationServiceRunning()
         fun onLocationServiceStopped()
     }

From 31bb9eaac8adf4454d33849f946c7212d9e24012 Mon Sep 17 00:00:00 2001
From: Maxime NATUREL <maxime.naturel@niji.fr>
Date: Wed, 15 Jun 2022 14:34:13 +0200
Subject: [PATCH 04/11] Forward error to UI in timeline screen

---
 .../features/home/room/detail/TimelineViewModel.kt |  4 ++++
 .../features/location/LocationSharingService.kt    | 14 ++++++++++----
 .../location/LocationSharingServiceConnection.kt   | 13 ++++++++++---
 .../location/live/map/LocationLiveMapViewModel.kt  |  4 ++++
 4 files changed, 28 insertions(+), 7 deletions(-)

diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/TimelineViewModel.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/TimelineViewModel.kt
index 07b20b4914..99a01211c3 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(error))
+    }
+
     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 27eea498e4..4a15a9d643 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
@@ -55,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<String, RoomArgs>()
-    private var timers = mutableListOf<Timer>()
+    private val roomArgsMap = mutableMapOf<String, RoomArgs>()
+    private val timers = mutableListOf<Timer>()
+    var callback: Callback? = null
 
     override fun onCreate() {
         super.onCreate()
@@ -103,6 +104,7 @@ class LocationSharingService : VectorService(), LocationTracker.Callback {
                             locationTracker.requestLastKnownLocation()
                         }
                         is UpdateLiveLocationShareResult.Failure -> {
+                            callback?.onServiceError(result.error)
                             tryToDestroyMe()
                         }
                     }
@@ -132,8 +134,7 @@ class LocationSharingService : VectorService(), LocationTracker.Callback {
         Timber.i("### LocationSharingService.stopSharingLocation for $roomId")
 
         launchInIO { session ->
-            // Send a new beacon info state by setting live field as false
-            when (sendStoppedBeaconInfo(session, roomId)) {
+            when (val result = sendStoppedBeaconInfo(session, roomId)) {
                 is UpdateLiveLocationShareResult.Success -> {
                     synchronized(roomArgsMap) {
                         val beaconIds = roomArgsMap
@@ -144,6 +145,7 @@ class LocationSharingService : VectorService(), LocationTracker.Callback {
                         tryToDestroyMe()
                     }
                 }
+                is UpdateLiveLocationShareResult.Failure -> callback?.onServiceError(result.error)
                 else -> Unit
             }
         }
@@ -224,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 97f447ec4b..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,12 +25,12 @@ import javax.inject.Inject
 
 class LocationSharingServiceConnection @Inject constructor(
         private val context: Context
-) : ServiceConnection {
+) : ServiceConnection, LocationSharingService.Callback {
 
     interface Callback {
-        // TODO add onLocationServiceError()
         fun onLocationServiceRunning()
         fun onLocationServiceStopped()
+        fun onLocationServiceError(error: Throwable)
     }
 
     private var callback: Callback? = null
@@ -58,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/LocationLiveMapViewModel.kt b/vector/src/main/java/im/vector/app/features/location/live/map/LocationLiveMapViewModel.kt
index eb5bccff0f..9ef6449ea0 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) {
+        // TODO
+    }
 }

From fc980570424762329efd1b2bc1ed1d03925eeb78 Mon Sep 17 00:00:00 2001
From: Maxime NATUREL <maxime.naturel@niji.fr>
Date: Wed, 15 Jun 2022 14:45:32 +0200
Subject: [PATCH 05/11] Forward error to UI in map screen

---
 .../app/features/home/room/detail/TimelineViewModel.kt |  2 +-
 .../location/live/map/LocationLiveMapViewEvents.kt     |  4 +++-
 .../location/live/map/LocationLiveMapViewFragment.kt   | 10 ++++++++++
 .../location/live/map/LocationLiveMapViewModel.kt      |  2 +-
 4 files changed, 15 insertions(+), 3 deletions(-)

diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/TimelineViewModel.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/TimelineViewModel.kt
index 99a01211c3..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
@@ -1294,7 +1294,7 @@ class TimelineViewModel @AssistedInject constructor(
     }
 
     override fun onLocationServiceError(error: Throwable) {
-        _viewEvents.post(RoomDetailViewEvents.Failure(error))
+        _viewEvents.post(RoomDetailViewEvents.Failure(throwable = error, showInDialog = true))
     }
 
     override fun onCleared() {
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..09522ce4c8 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<Fra
 
     override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
         super.onViewCreated(view, savedInstanceState)
+        observeViewEvents()
+
         views.liveLocationBottomSheetRecyclerView.configureWith(bottomSheetController, hasFixedSize = false, disableItemAnimation = true)
 
         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() {
         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 9ef6449ea0..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
@@ -82,6 +82,6 @@ class LocationLiveMapViewModel @AssistedInject constructor(
     }
 
     override fun onLocationServiceError(error: Throwable) {
-        // TODO
+        _viewEvents.post(LocationLiveMapViewEvents.Error(error))
     }
 }

From 6c0b7f7b4337142ace9145f1fd4f25c2bc479f38 Mon Sep 17 00:00:00 2001
From: Maxime NATUREL <maxime.naturel@niji.fr>
Date: Wed, 15 Jun 2022 14:51:28 +0200
Subject: [PATCH 06/11] Renaming a variable to be more precise

---
 .../im/vector/app/features/location/LocationSharingService.kt | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

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 4a15a9d643..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
@@ -91,12 +91,12 @@ 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
+        updateLiveResult
                 ?.let { result ->
                     when (result) {
                         is UpdateLiveLocationShareResult.Success -> {

From 3e05431e6f70bcdfc704814ee5b1bc8a9eb264ba Mon Sep 17 00:00:00 2001
From: Maxime NATUREL <maxime.naturel@niji.fr>
Date: Wed, 15 Jun 2022 14:56:00 +0200
Subject: [PATCH 07/11] Fixing unit tests

---
 .../location/DefaultLocationSharingServiceTest.kt    | 12 +++++++-----
 .../DefaultStartLiveLocationShareTaskTest.kt         |  5 +++--
 .../location/DefaultStopLiveLocationShareTaskTest.kt |  5 ++++-
 3 files changed, 14 insertions(+), 8 deletions(-)

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..d9b3526bf5 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
@@ -27,6 +27,7 @@ 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 +120,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 +134,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..6a487a4e81 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
@@ -24,6 +24,7 @@ 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 +54,7 @@ internal class DefaultStartLiveLocationShareTaskTest {
     }
 
     @Test
-    fun `given parameters when calling the task then it is correctly executed`() = runTest {
+    fun `given parameters an no error when calling the task then it is correctly executed`() = runTest {
         val params = StartLiveLocationShareTask.Params(
                 roomId = A_ROOM_ID,
                 timeoutMillis = A_TIMEOUT
@@ -63,7 +64,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,
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..78433d8b6e 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,13 @@ 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.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.FakeSendStateTask
@@ -66,8 +68,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,

From e1fc6fa727b1964098c6a6493a3260bacce35041 Mon Sep 17 00:00:00 2001
From: Maxime NATUREL <maxime.naturel@niji.fr>
Date: Wed, 15 Jun 2022 15:16:25 +0200
Subject: [PATCH 08/11] Adding tests to cover errors thrown during start/stop
 process

---
 .../location/StartLiveLocationShareTask.kt    |  1 -
 .../location/StopLiveLocationShareTask.kt     |  1 -
 .../DefaultStartLiveLocationShareTaskTest.kt  | 32 +++++++-
 .../DefaultStopLiveLocationShareTaskTest.kt   | 78 ++++++++++++++++++-
 .../sdk/test/fakes/FakeSendStateTask.kt       |  4 +
 .../test/fakes/FakeStateEventDataSource.kt    |  2 +-
 6 files changed, 113 insertions(+), 5 deletions(-)

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 bf6a0049d8..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
@@ -33,7 +33,6 @@ internal interface StartLiveLocationShareTask : Task<StartLiveLocationShareTask.
     )
 }
 
-// TODO update unit test
 internal class DefaultStartLiveLocationShareTask @Inject constructor(
         @UserId private val userId: String,
         private val clock: Clock,
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 dc12054d7b..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
@@ -36,7 +36,6 @@ internal interface StopLiveLocationShareTask : Task<StopLiveLocationShareTask.Pa
     )
 }
 
-// TODO update unit tests
 internal class DefaultStopLiveLocationShareTask @Inject constructor(
         @UserId private val userId: String,
         private val sendStateTask: SendStateTask,
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 6a487a4e81..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,6 +20,7 @@ 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
@@ -54,7 +55,7 @@ internal class DefaultStartLiveLocationShareTaskTest {
     }
 
     @Test
-    fun `given parameters an no error 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
@@ -81,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 78433d8b6e..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
@@ -20,12 +20,14 @@ 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
@@ -55,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,
@@ -92,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(),

From eb503b8ab62641b3600d5d88bcb53bd02d1fce43 Mon Sep 17 00:00:00 2001
From: Maxime NATUREL <maxime.naturel@niji.fr>
Date: Wed, 15 Jun 2022 15:36:08 +0200
Subject: [PATCH 09/11] Adding a changelog entry

---
 changelog.d/6315.bugfix | 1 +
 1 file changed, 1 insertion(+)
 create mode 100644 changelog.d/6315.bugfix

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

From 082b39e651f23b30cc3bb2515ce51154a02a58f1 Mon Sep 17 00:00:00 2001
From: Maxime NATUREL <maxime.naturel@niji.fr>
Date: Wed, 15 Jun 2022 15:48:03 +0200
Subject: [PATCH 10/11] Adding return type in the doc for stop API

---
 .../sdk/api/session/room/location/LocationSharingService.kt      | 1 +
 1 file changed, 1 insertion(+)

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 7e5906b517..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
@@ -52,6 +52,7 @@ interface LocationSharingService {
 
     /**
      * Stops sharing live location in the room.
+     * @return the result of the update of the live
      */
     suspend fun stopLiveLocationShare(): UpdateLiveLocationShareResult
 

From 9047d9d62c562ea28c412bca3f321cf53ed04056 Mon Sep 17 00:00:00 2001
From: Maxime NATUREL <maxime.naturel@niji.fr>
Date: Wed, 15 Jun 2022 16:04:09 +0200
Subject: [PATCH 11/11] Fixing coding style issues

---
 .../room/location/UpdateLiveLocationShareResult.kt       | 9 +--------
 .../room/location/DefaultLocationSharingServiceTest.kt   | 2 --
 .../location/live/map/LocationLiveMapViewFragment.kt     | 2 +-
 3 files changed, 2 insertions(+), 11 deletions(-)

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
index 6c6bc1029a..6f8c03be46 100644
--- 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
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2022 New Vector Ltd
+ * 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.
@@ -20,13 +20,6 @@ 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 {
-    /**
-     * @param beaconEventId event id of the updated state event
-     */
     data class Success(val beaconEventId: String) : UpdateLiveLocationShareResult
-
-    /**
-     * @param error thrown during the update
-     */
     data class Failure(val error: Throwable) : UpdateLiveLocationShareResult
 }
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 d9b3526bf5..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,9 +18,7 @@ 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
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 09522ce4c8..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
@@ -93,7 +93,7 @@ class LocationLiveMapViewFragment @Inject constructor() : VectorBaseFragment<Fra
 
     private fun observeViewEvents() {
         viewModel.observeViewEvents { viewEvent ->
-            when(viewEvent) {
+            when (viewEvent) {
                 is LocationLiveMapViewEvents.Error -> displayErrorDialog(viewEvent.error)
             }
         }