diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index f6a1906394..1c0491fda4 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -8,8 +8,9 @@ on:
 # Enrich gradle.properties for CI/CD
 env:
   CI_GRADLE_ARG_PROPERTIES: >
-    -Porg.gradle.jvmargs=-Xmx2g
+    -Porg.gradle.jvmargs=-Xmx4g
     -Porg.gradle.parallel=false
+    --no-daemon
 
 jobs:
   debug:
diff --git a/.github/workflows/post-pr.yml b/.github/workflows/post-pr.yml
index 6040fd5f78..a7f1d6f204 100644
--- a/.github/workflows/post-pr.yml
+++ b/.github/workflows/post-pr.yml
@@ -13,6 +13,7 @@ env:
   CI_GRADLE_ARG_PROPERTIES: >
     -Porg.gradle.jvmargs=-Xmx4g
     -Porg.gradle.parallel=false
+    --no-daemon
 
 jobs:
 
diff --git a/.github/workflows/quality.yml b/.github/workflows/quality.yml
index 89abac5b72..d7f5ce8b57 100644
--- a/.github/workflows/quality.yml
+++ b/.github/workflows/quality.yml
@@ -9,6 +9,8 @@ on:
 env:
   CI_GRADLE_ARG_PROPERTIES: >
     -Porg.gradle.jvmargs=-Xmx4g
+    -Porg.gradle.parallel=false
+    --no-daemon
 
 jobs:
   check:
diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml
index e803319423..1a9cc5c239 100644
--- a/.github/workflows/tests.yml
+++ b/.github/workflows/tests.yml
@@ -8,8 +8,9 @@ on:
 # Enrich gradle.properties for CI/CD
 env:
   CI_GRADLE_ARG_PROPERTIES: >
-    -Porg.gradle.jvmargs=-Xmx2g
+    -Porg.gradle.jvmargs=-Xmx4g
     -Porg.gradle.parallel=false
+    --no-daemon
 
 jobs:
   tests:
diff --git a/build.gradle b/build.gradle
index c6654499ca..0244080ad0 100644
--- a/build.gradle
+++ b/build.gradle
@@ -29,7 +29,7 @@ buildscript {
         classpath 'com.google.android.gms:oss-licenses-plugin:0.10.5'
         classpath "com.likethesalad.android:stem-plugin:2.1.1"
         classpath 'org.owasp:dependency-check-gradle:7.1.1'
-        classpath "org.jetbrains.dokka:dokka-gradle-plugin:1.6.21"
+        classpath "org.jetbrains.dokka:dokka-gradle-plugin:1.7.0"
         classpath "org.jetbrains.kotlinx:kotlinx-knit:0.4.0"
         // NOTE: Do not place your application dependencies here; they belong
         // in the individual module build.gradle files
@@ -43,7 +43,7 @@ plugins {
     id "io.gitlab.arturbosch.detekt" version "1.20.0"
 
     // Dependency Analysis
-    id 'com.autonomousapps.dependency-analysis' version "1.8.0"
+    id 'com.autonomousapps.dependency-analysis' version "1.9.0"
 }
 
 // https://github.com/jeremylong/DependencyCheck
@@ -267,6 +267,8 @@ dependencyAnalysis {
             onUnusedDependencies {
                 // False positives
                 exclude(
+                        "androidx.fragment:fragment-testing",
+                        "com.facebook.soloader:soloader",
                         "com.vanniktech:emoji-google",
                         "com.vanniktech:emoji-material",
                         "org.maplibre.gl:android-plugin-annotation-v9",
diff --git a/changelog.d/6101.bugfix b/changelog.d/6101.bugfix
new file mode 100644
index 0000000000..2d8da5327d
--- /dev/null
+++ b/changelog.d/6101.bugfix
@@ -0,0 +1 @@
+Refactor - better naming, return native user id and not sip user id and create a dm with the native user instead of with the sip user.
diff --git a/changelog.d/6328.bugfix b/changelog.d/6328.bugfix
new file mode 100644
index 0000000000..7a41996e57
--- /dev/null
+++ b/changelog.d/6328.bugfix
@@ -0,0 +1 @@
+Fix | Some user verification requests couldn't be accepted/declined
diff --git a/changelog.d/6349.bugfix b/changelog.d/6349.bugfix
new file mode 100644
index 0000000000..70718248a7
--- /dev/null
+++ b/changelog.d/6349.bugfix
@@ -0,0 +1 @@
+[Location sharing] Fix stop of a live not possible from another device
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/changelog.d/6366.misc b/changelog.d/6366.misc
new file mode 100644
index 0000000000..5752b3d700
--- /dev/null
+++ b/changelog.d/6366.misc
@@ -0,0 +1 @@
+Poll view state unit tests
diff --git a/changelog.d/6375.bugfix b/changelog.d/6375.bugfix
new file mode 100644
index 0000000000..769ed81e69
--- /dev/null
+++ b/changelog.d/6375.bugfix
@@ -0,0 +1 @@
+[Location Share] - Adding missing prefix "u=" for uncertainty in geo URI
diff --git a/changelog.d/6396.doc b/changelog.d/6396.doc
new file mode 100644
index 0000000000..9b876d74af
--- /dev/null
+++ b/changelog.d/6396.doc
@@ -0,0 +1 @@
+Update the PR process doc to come back to one reviewer with optional additional reviewers.
\ No newline at end of file
diff --git a/dependencies.gradle b/dependencies.gradle
index 297e17be0a..db9278b975 100644
--- a/dependencies.gradle
+++ b/dependencies.gradle
@@ -21,6 +21,7 @@ def markwon = "4.6.2"
 def moshi = "1.13.0"
 def lifecycle = "2.4.1"
 def flowBinding = "1.2.0"
+def flipper = "0.151.1"
 def epoxy = "4.6.2"
 def mavericks = "2.7.0"
 def glide = "4.13.2"
@@ -91,6 +92,10 @@ ext.libs = [
                 'hiltAndroidTesting'      : "com.google.dagger:hilt-android-testing:$dagger",
                 'hiltCompiler'            : "com.google.dagger:hilt-compiler:$dagger"
         ],
+        flipper     : [
+                'flipper'                 : "com.facebook.flipper:flipper:$flipper",
+                'flipperNetworkPlugin'    : "com.facebook.flipper:flipper-network-plugin:$flipper",
+        ],
         squareup    : [
                 'moshi'                  : "com.squareup.moshi:moshi:$moshi",
                 'moshiKt'                : "com.squareup.moshi:moshi-kotlin:$moshi",
diff --git a/docs/pull_request.md b/docs/pull_request.md
index d2d2bb7a3b..eebf2814a9 100644
--- a/docs/pull_request.md
+++ b/docs/pull_request.md
@@ -83,15 +83,16 @@ Exceptions can occur:
 
 ##### PR Review Assignment
 
-We use automatic assignment for PR reviews. A PR is automatically routed by GitHub to 2 team members using the round robin algorithm. The process is the following:
+We use automatic assignment for PR reviews. **A PR is automatically routed by GitHub to one team member** using the round robin algorithm. Additional reviewers can be used for complex changes or when the first reviewer is not confident enough on the changes.
+The process is the following:
 
-- The PR creator can assign specific people if they have another Android developer in their team or they think a specific reviewer should take a look at the PR.
-- If there are missing reviewers, the PR creator assigns the [element-android-reviewers](https://github.com/orgs/vector-im/teams/element-android-reviewers) team as a reviewer.
-- GitHub automatically assigns other reviewers. If one of the chosen reviewers is not available (holiday, etc.), remove them and set again the team, GitHub will select another reviewer.
+- The PR creator selects the [element-android-reviewers](https://github.com/orgs/vector-im/teams/element-android-reviewers) team as a reviewer.
+- GitHub automatically assign the reviewer. If the reviewer is not available (holiday, etc.), remove them and set again the team, GitHub will select another reviewer.
+- Alternatively, the PR creator can directly assign specific people if they have another Android developer in their team or they think a specific reviewer should take a look at their PR.
 - Reviewers get a notification to make the review: they review the code following the good practice (see the rest of this document).
 - After making their own review, if they feel not confident enough, they can ask another person for a full review, or they can tag someone within a PR comment to check specific lines.
 
-For PRs coming from the community, the issue wrangler can assign either the team [element-android-reviewers](https://github.com/orgs/vector-im/teams/element-android-reviewers) or any members directly.
+For PRs coming from the community, the issue wrangler can assign either the team [element-android-reviewers](https://github.com/orgs/vector-im/teams/element-android-reviewers) or any member directly.
 
 ##### PR review time
 
@@ -102,6 +103,7 @@ Some tips to achieve it:
 - Set up your GH notifications correctly
 - Check your pulls page: [https://github.com/pulls](https://github.com/pulls)
 - Check your pending assigned PRs before starting or resuming your day to day tasks
+- If you are busy with high priority tasks, inform the author. They will find another developer
 
 It is hard to define a deadline for a review. It depends on the PR size and the complexity. Let's start with a goal of 24h (working day!) for a PR smaller than 500 lines. If bigger, the submitter and the reviewer should discuss.
 
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 0f88f891cc..ada3dc85d7 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
@@ -16,9 +16,11 @@
 
 package org.matrix.android.sdk.api.session.room.location
 
+import androidx.annotation.MainThread
 import androidx.lifecycle.LiveData
 import org.matrix.android.sdk.api.session.room.model.livelocation.LiveLocationShareAggregatedSummary
 import org.matrix.android.sdk.api.util.Cancelable
+import org.matrix.android.sdk.api.util.Optional
 
 /**
  * Manage all location sharing related features.
@@ -59,5 +61,13 @@ interface LocationSharingService {
     /**
      * Returns a LiveData on the list of current running live location shares.
      */
+    @MainThread
     fun getRunningLiveLocationShareSummaries(): LiveData<List<LiveLocationShareAggregatedSummary>>
+
+    /**
+     * Returns a LiveData on the live location share summary with the given eventId.
+     * @param beaconInfoEventId event id of the initial beacon info state event
+     */
+    @MainThread
+    fun getLiveLocationShareSummary(beaconInfoEventId: String): LiveData<Optional<LiveLocationShareAggregatedSummary>>
 }
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/LocationInfo.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/LocationInfo.kt
index a1fd3bd2ec..e0a7846167 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/LocationInfo.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/LocationInfo.kt
@@ -22,7 +22,7 @@ import com.squareup.moshi.JsonClass
 @JsonClass(generateAdapter = true)
 data class LocationInfo(
         /**
-         * Required. RFC5870 formatted geo uri 'geo:latitude,longitude;uncertainty' like 'geo:40.05,29.24;30' representing this location.
+         * Required. RFC5870 formatted geo uri 'geo:latitude,longitude;u=uncertainty' like 'geo:40.05,29.24;u=30' representing this location.
          */
         @Json(name = "uri") val geoUri: String? = null,
 
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/MessageLocationContent.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/MessageLocationContent.kt
index 0a66a6e400..30420fd3c7 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/MessageLocationContent.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/MessageLocationContent.kt
@@ -35,7 +35,7 @@ data class MessageLocationContent(
         @Json(name = "body") override val body: String,
 
         /**
-         * Required. RFC5870 formatted geo uri 'geo:latitude,longitude;uncertainty' like 'geo:40.05,29.24;30' representing this location.
+         * Required. RFC5870 formatted geo uri 'geo:latitude,longitude;u=uncertainty' like 'geo:40.05,29.24;u=30' representing this location.
          */
         @Json(name = "geo_uri") val geoUri: String,
 
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/PollCreationInfo.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/PollCreationInfo.kt
index 81b034a809..ee31d5959e 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/PollCreationInfo.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/PollCreationInfo.kt
@@ -25,4 +25,7 @@ data class PollCreationInfo(
         @Json(name = "kind") val kind: PollType? = PollType.DISCLOSED_UNSTABLE,
         @Json(name = "max_selections") val maxSelections: Int = 1,
         @Json(name = "answers") val answers: List<PollAnswer>? = null
-)
+) {
+
+    fun isUndisclosed() = kind in listOf(PollType.UNDISCLOSED_UNSTABLE, PollType.UNDISCLOSED)
+}
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/verification/VerificationMessageProcessor.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/verification/VerificationMessageProcessor.kt
index 9f123f0c08..821663bcff 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/verification/VerificationMessageProcessor.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/verification/VerificationMessageProcessor.kt
@@ -62,7 +62,7 @@ internal class VerificationMessageProcessor @Inject constructor(
         // If the request is in the future by more than 5 minutes or more than 10 minutes in the past,
         // the message should be ignored by the receiver.
 
-        if (event.ageLocalTs != null && !VerificationService.isValidRequest(event.ageLocalTs, clock.epochMillis())) return Unit.also {
+        if (!VerificationService.isValidRequest(event.ageLocalTs, clock.epochMillis())) return Unit.also {
             Timber.d("## SAS Verification live observer: msgId: ${event.eventId} is outdated age:$event.ageLocalTs ms")
         }
 
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/helper/ThreadSummaryHelper.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/helper/ThreadSummaryHelper.kt
index 79a99cdfac..0a6d4bf833 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/helper/ThreadSummaryHelper.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/helper/ThreadSummaryHelper.kt
@@ -271,7 +271,7 @@ private fun HashMap<String, RoomMemberContent?>.addSenderState(realm: Realm, roo
  * Create an EventEntity for the root thread event or get an existing one.
  */
 private fun createEventEntity(realm: Realm, roomId: String, event: Event, currentTimeMillis: Long): EventEntity {
-    val ageLocalTs = event.unsignedData?.age?.let { currentTimeMillis - it }
+    val ageLocalTs = currentTimeMillis - (event.unsignedData?.age ?: 0)
     return event.toEntity(roomId, SendState.SYNCED, ageLocalTs).copyToRealmOrIgnore(realm, EventInsertType.PAGINATION)
 }
 
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/mapper/EventMapper.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/mapper/EventMapper.kt
index 5b60c53642..0f0a847c78 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/mapper/EventMapper.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/mapper/EventMapper.kt
@@ -130,7 +130,7 @@ internal fun EventEntity.asDomain(castJsonNumbers: Boolean = false): Event {
 internal fun Event.toEntity(
         roomId: String,
         sendState: SendState,
-        ageLocalTs: Long?,
+        ageLocalTs: Long,
         contentToInject: String? = null
 ): EventEntity {
     return EventMapper.map(this, roomId).apply {
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/migration/MigrateSessionTo030.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/migration/MigrateSessionTo030.kt
index b9c611f5dd..5d24b1433c 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/migration/MigrateSessionTo030.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/migration/MigrateSessionTo030.kt
@@ -52,6 +52,10 @@ internal class MigrateSessionTo030(realm: DynamicRealm) : RealmMigrator(realm, 3
             timelineEvents.deleteAllFromRealm()
         }
         chunks.deleteAllFromRealm()
-        Timber.d("MigrateSessionTo030: $nbOfDeletedChunks deleted chunk(s), $nbOfDeletedTimelineEvents deleted TimelineEvent(s) and $nbOfDeletedEvents deleted Event(s).")
+        Timber.d(
+                "MigrateSessionTo030: $nbOfDeletedChunks deleted chunk(s)," +
+                        " $nbOfDeletedTimelineEvents deleted TimelineEvent(s)" +
+                        " and $nbOfDeletedEvents deleted Event(s)."
+        )
     }
 }
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/query/LiveLocationShareAggregatedSummaryEntityQuery.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/query/LiveLocationShareAggregatedSummaryEntityQuery.kt
index 6bcd737474..d69f251f6f 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/query/LiveLocationShareAggregatedSummaryEntityQuery.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/database/query/LiveLocationShareAggregatedSummaryEntityQuery.kt
@@ -76,7 +76,7 @@ internal fun LiveLocationShareAggregatedSummaryEntity.Companion.findActiveLiveIn
         realm: Realm,
         roomId: String,
         userId: String,
-        ignoredEventId: String
+        ignoredEventId: String,
 ): List<LiveLocationShareAggregatedSummaryEntity> {
     return LiveLocationShareAggregatedSummaryEntity
             .whereRoomId(realm, roomId = roomId)
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<CheckIfExistingActiveLiveTask.Params, Boolean> {
+    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<MessageBeaconInfoContent>()
+                ?.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 015c1cca0b..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
@@ -17,6 +17,7 @@
 package org.matrix.android.sdk.internal.session.room.location
 
 import androidx.lifecycle.LiveData
+import androidx.lifecycle.Transformations
 import com.zhuinden.monarchy.Monarchy
 import dagger.assisted.Assisted
 import dagger.assisted.AssistedFactory
@@ -25,9 +26,12 @@ 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.api.util.Optional
+import org.matrix.android.sdk.api.util.toOptional
 import org.matrix.android.sdk.internal.database.mapper.LiveLocationShareAggregatedSummaryMapper
 import org.matrix.android.sdk.internal.database.model.livelocation.LiveLocationShareAggregatedSummaryEntity
 import org.matrix.android.sdk.internal.database.query.findRunningLiveInRoom
+import org.matrix.android.sdk.internal.database.query.where
 import org.matrix.android.sdk.internal.di.SessionDatabase
 
 internal class DefaultLocationSharingService @AssistedInject constructor(
@@ -37,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 {
 
@@ -68,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
@@ -75,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,
@@ -88,4 +107,15 @@ internal class DefaultLocationSharingService @AssistedInject constructor(
                 liveLocationShareAggregatedSummaryMapper
         )
     }
+
+    override fun getLiveLocationShareSummary(beaconInfoEventId: String): LiveData<Optional<LiveLocationShareAggregatedSummary>> {
+        return Transformations.map(
+                monarchy.findAllMappedWithChanges(
+                        { LiveLocationShareAggregatedSummaryEntity.where(it, roomId = roomId, eventId = beaconInfoEventId) },
+                        liveLocationShareAggregatedSummaryMapper
+                )
+        ) {
+            it.firstOrNull().toOptional()
+        }
+    }
 }
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<GetActiveBeaconInfoForUserTask.Params, Event?> {
+    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<MessageBeaconInfoContent>()?.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<StopLiveLocationShareTask.Pa
 }
 
 internal class DefaultStopLiveLocationShareTask @Inject constructor(
-        @UserId private val userId: String,
         private val sendStateTask: SendStateTask,
-        private val stateEventDataSource: StateEventDataSource,
+        private val getActiveBeaconInfoForUserTask: GetActiveBeaconInfoForUserTask,
 ) : StopLiveLocationShareTask {
 
     override suspend fun execute(params: StopLiveLocationShareTask.Params): UpdateLiveLocationShareResult {
-        val beaconInfoStateEvent = getLiveLocationBeaconInfoForUser(userId, params.roomId) ?: return getResultForIncorrectBeaconInfoEvent()
+        val beaconInfoStateEvent = getActiveLiveLocationBeaconInfoForUser(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()
@@ -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<MessageBeaconInfoContent>()?.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/main/java/org/matrix/android/sdk/internal/session/room/membership/LoadRoomMembersTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/membership/LoadRoomMembersTask.kt
index b7670499ae..7052eb23e2 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/membership/LoadRoomMembersTask.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/membership/LoadRoomMembersTask.kt
@@ -116,7 +116,7 @@ internal class DefaultLoadRoomMembersTask @Inject constructor(
                     if (roomMemberEvent.eventId == null || roomMemberEvent.stateKey == null || roomMemberEvent.type == null) {
                         continue
                     }
-                    val ageLocalTs = roomMemberEvent.unsignedData?.age?.let { now - it }
+                    val ageLocalTs = now - (roomMemberEvent.unsignedData?.age ?: 0)
                     val eventEntity = roomMemberEvent.toEntity(roomId, SendState.SYNCED, ageLocalTs).copyToRealmOrIgnore(realm, EventInsertType.PAGINATION)
                     CurrentStateEventEntity.getOrCreate(
                             realm,
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/relation/threads/FetchThreadTimelineTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/relation/threads/FetchThreadTimelineTask.kt
index bad734173e..bac810f424 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/relation/threads/FetchThreadTimelineTask.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/relation/threads/FetchThreadTimelineTask.kt
@@ -209,7 +209,8 @@ internal class DefaultFetchThreadTimelineTask @Inject constructor(
      * Create an EventEntity to be added in the TimelineEventEntity.
      */
     private fun createEventEntity(roomId: String, event: Event, realm: Realm): EventEntity {
-        val ageLocalTs = event.unsignedData?.age?.let { clock.epochMillis() - it }
+        val now = clock.epochMillis()
+        val ageLocalTs = now - (event.unsignedData?.age ?: 0)
         return event.toEntity(roomId, SendState.SYNCED, ageLocalTs).copyToRealmOrIgnore(realm, EventInsertType.PAGINATION)
     }
 
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/LocalEchoEventFactory.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/LocalEchoEventFactory.kt
index bcaa257d78..f52500de1b 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/LocalEchoEventFactory.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/send/LocalEchoEventFactory.kt
@@ -708,7 +708,7 @@ internal class LocalEchoEventFactory @Inject constructor(
     }
 
     /**
-     * Returns RFC5870 formatted geo uri 'geo:latitude,longitude;uncertainty' like 'geo:40.05,29.24;30'
+     * Returns RFC5870 formatted geo uri 'geo:latitude,longitude;u=uncertainty' like 'geo:40.05,29.24;u=30'
      * Uncertainty of the location is in meters and not required.
      */
     private fun buildGeoUri(latitude: Double, longitude: Double, uncertainty: Double?): String {
@@ -718,7 +718,7 @@ internal class LocalEchoEventFactory @Inject constructor(
             append(",")
             append(longitude)
             uncertainty?.let {
-                append(";")
+                append(";u=")
                 append(it)
             }
         }
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/GetEventTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/GetEventTask.kt
index aef9e24c8b..7c662444e4 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/GetEventTask.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/GetEventTask.kt
@@ -61,7 +61,7 @@ internal class DefaultGetEventTask @Inject constructor(
                     }
         }
 
-        event.ageLocalTs = event.unsignedData?.age?.let { clock.epochMillis() - it }
+        event.ageLocalTs = clock.epochMillis() - (event.unsignedData?.age ?: 0)
 
         return event
     }
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/TokenChunkEventPersistor.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/TokenChunkEventPersistor.kt
index fd1703dbc8..ea22f8cd78 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/TokenChunkEventPersistor.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/timeline/TokenChunkEventPersistor.kt
@@ -142,7 +142,7 @@ internal class TokenChunkEventPersistor @Inject constructor(
         val now = clock.epochMillis()
 
         stateEvents?.forEach { stateEvent ->
-            val ageLocalTs = stateEvent.unsignedData?.age?.let { now - it }
+            val ageLocalTs = now - (stateEvent.unsignedData?.age ?: 0)
             val stateEventEntity = stateEvent.toEntity(roomId, SendState.SYNCED, ageLocalTs).copyToRealmOrIgnore(realm, EventInsertType.PAGINATION)
             currentChunk.addStateEvent(roomId, stateEventEntity, direction)
             if (stateEvent.type == EventType.STATE_ROOM_MEMBER && stateEvent.stateKey != null) {
@@ -155,7 +155,7 @@ internal class TokenChunkEventPersistor @Inject constructor(
                 if (event.eventId == null || event.senderId == null) {
                     return@forEach
                 }
-                val ageLocalTs = event.unsignedData?.age?.let { now - it }
+                val ageLocalTs = now - (event.unsignedData?.age ?: 0)
                 val eventEntity = event.toEntity(roomId, SendState.SYNCED, ageLocalTs).copyToRealmOrIgnore(realm, EventInsertType.PAGINATION)
                 if (event.type == EventType.STATE_ROOM_MEMBER && event.stateKey != null) {
                     val contentToUse = if (direction == PaginationDirection.BACKWARDS) {
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/handler/room/RoomSyncHandler.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/handler/room/RoomSyncHandler.kt
index f99fe96410..30e1ec6679 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/handler/room/RoomSyncHandler.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/handler/room/RoomSyncHandler.kt
@@ -244,7 +244,7 @@ internal class RoomSyncHandler @Inject constructor(
                 if (event.eventId == null || event.stateKey == null || event.type == null) {
                     continue
                 }
-                val ageLocalTs = event.unsignedData?.age?.let { syncLocalTimestampMillis - it }
+                val ageLocalTs = syncLocalTimestampMillis - (event.unsignedData?.age ?: 0)
                 val eventEntity = event.toEntity(roomId, SendState.SYNCED, ageLocalTs).copyToRealmOrIgnore(realm, insertType)
                 Timber.v("## received state event ${event.type} and key ${event.stateKey}")
                 CurrentStateEventEntity.getOrCreate(realm, roomId, event.stateKey, event.type).apply {
@@ -306,7 +306,7 @@ internal class RoomSyncHandler @Inject constructor(
                 if (event.stateKey == null || event.type == null) {
                     return@forEach
                 }
-                val ageLocalTs = event.unsignedData?.age?.let { syncLocalTimestampMillis - it }
+                val ageLocalTs = syncLocalTimestampMillis - (event.unsignedData?.age ?: 0)
                 val eventEntity = event.toEntity(roomId, SendState.SYNCED, ageLocalTs).copyToRealmOrIgnore(realm, insertType)
                 CurrentStateEventEntity.getOrCreate(realm, roomId, event.stateKey, event.type).apply {
                     eventId = eventEntity.eventId
@@ -336,7 +336,7 @@ internal class RoomSyncHandler @Inject constructor(
             if (event.eventId == null || event.stateKey == null || event.type == null) {
                 continue
             }
-            val ageLocalTs = event.unsignedData?.age?.let { syncLocalTimestampMillis - it }
+            val ageLocalTs = syncLocalTimestampMillis - (event.unsignedData?.age ?: 0)
             val eventEntity = event.toEntity(roomId, SendState.SYNCED, ageLocalTs).copyToRealmOrIgnore(realm, insertType)
             CurrentStateEventEntity.getOrCreate(realm, roomId, event.stateKey, event.type).apply {
                 eventId = event.eventId
@@ -348,7 +348,7 @@ internal class RoomSyncHandler @Inject constructor(
             if (event.eventId == null || event.senderId == null || event.type == null) {
                 continue
             }
-            val ageLocalTs = event.unsignedData?.age?.let { syncLocalTimestampMillis - it }
+            val ageLocalTs = syncLocalTimestampMillis - (event.unsignedData?.age ?: 0)
             val eventEntity = event.toEntity(roomId, SendState.SYNCED, ageLocalTs).copyToRealmOrIgnore(realm, insertType)
             if (event.stateKey != null) {
                 CurrentStateEventEntity.getOrCreate(realm, roomId, event.stateKey, event.type).apply {
@@ -401,7 +401,10 @@ internal class RoomSyncHandler @Inject constructor(
         for (rawEvent in eventList) {
             // It's annoying roomId is not there, but lot of code rely on it.
             // And had to do it now as copy would delete all decryption results..
-            val event = rawEvent.copy(roomId = roomId)
+            val ageLocalTs = syncLocalTimestampMillis - (rawEvent.unsignedData?.age ?: 0)
+            val event = rawEvent.copy(roomId = roomId).also {
+                it.ageLocalTs = ageLocalTs
+            }
             if (event.eventId == null || event.senderId == null || event.type == null) {
                 continue
             }
@@ -423,7 +426,6 @@ internal class RoomSyncHandler @Inject constructor(
                 contentToInject = threadsAwarenessHandler.makeEventThreadAware(realm, roomId, event)
             }
 
-            val ageLocalTs = event.unsignedData?.age?.let { syncLocalTimestampMillis - it }
             val eventEntity = event.toEntity(roomId, SendState.SYNCED, ageLocalTs, contentToInject).copyToRealmOrIgnore(realm, insertType)
             if (event.stateKey != null) {
                 CurrentStateEventEntity.getOrCreate(realm, roomId, event.stateKey, event.type).apply {
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/handler/room/ThreadsAwarenessHandler.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/handler/room/ThreadsAwarenessHandler.kt
index 8c7557a5b8..70553359ff 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/handler/room/ThreadsAwarenessHandler.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/sync/handler/room/ThreadsAwarenessHandler.kt
@@ -53,6 +53,7 @@ import org.matrix.android.sdk.internal.session.permalinks.PermalinkFactory
 import org.matrix.android.sdk.internal.session.room.send.LocalEchoEventFactory
 import org.matrix.android.sdk.internal.session.room.timeline.GetEventTask
 import org.matrix.android.sdk.internal.util.awaitTransaction
+import org.matrix.android.sdk.internal.util.time.Clock
 import javax.inject.Inject
 
 /**
@@ -64,7 +65,8 @@ internal class ThreadsAwarenessHandler @Inject constructor(
         private val permalinkFactory: PermalinkFactory,
         @SessionDatabase private val monarchy: Monarchy,
         private val lightweightSettingsStorage: LightweightSettingsStorage,
-        private val getEventTask: GetEventTask
+        private val getEventTask: GetEventTask,
+        private val clock: Clock,
 ) {
 
     // This caching is responsible to improve the performance when we receive a root event
@@ -120,7 +122,7 @@ internal class ThreadsAwarenessHandler @Inject constructor(
     private suspend fun fetchThreadsEvents(threadsToFetch: Map<String, String>) {
         val eventEntityList = threadsToFetch.mapNotNull { (eventId, roomId) ->
             fetchEvent(eventId, roomId)?.let {
-                it.toEntity(roomId, SendState.SYNCED, it.ageLocalTs)
+                it.toEntity(roomId, SendState.SYNCED, it.ageLocalTs ?: clock.epochMillis())
             }
         }
 
diff --git a/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/session/room/aggregation/livelocation/LiveLocationAggregationProcessorTest.kt b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/session/room/aggregation/livelocation/LiveLocationAggregationProcessorTest.kt
index e6d63f5e5e..933087af2b 100644
--- a/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/session/room/aggregation/livelocation/LiveLocationAggregationProcessorTest.kt
+++ b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/session/room/aggregation/livelocation/LiveLocationAggregationProcessorTest.kt
@@ -46,7 +46,7 @@ private const val A_TIMEOUT_MILLIS = 15 * 60 * 1000L
 private const val A_LATITUDE = 40.05
 private const val A_LONGITUDE = 29.24
 private const val A_UNCERTAINTY = 30.0
-private const val A_GEO_URI = "geo:$A_LATITUDE,$A_LONGITUDE;$A_UNCERTAINTY"
+private const val A_GEO_URI = "geo:$A_LATITUDE,$A_LONGITUDE;u=$A_UNCERTAINTY"
 
 internal class LiveLocationAggregationProcessorTest {
 
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 30a9671733..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
@@ -16,18 +16,27 @@
 
 package org.matrix.android.sdk.internal.session.room.location
 
+import androidx.arch.core.util.Function
+import androidx.lifecycle.MutableLiveData
+import androidx.lifecycle.Transformations
 import io.mockk.coEvery
 import io.mockk.coVerify
+import io.mockk.every
 import io.mockk.mockk
+import io.mockk.mockkStatic
+import io.mockk.slot
 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.Before
 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.api.util.Optional
+import org.matrix.android.sdk.api.util.toOptional
 import org.matrix.android.sdk.internal.database.mapper.LiveLocationShareAggregatedSummaryMapper
 import org.matrix.android.sdk.internal.database.model.livelocation.LiveLocationShareAggregatedSummaryEntity
 import org.matrix.android.sdk.internal.database.model.livelocation.LiveLocationShareAggregatedSummaryEntityFields
@@ -46,24 +55,30 @@ private const val A_TIMEOUT = 15_000L
 @ExperimentalCoroutinesApi
 internal class DefaultLocationSharingServiceTest {
 
-    private val fakeRoomId = A_ROOM_ID
     private val fakeMonarchy = FakeMonarchy()
     private val sendStaticLocationTask = mockk<SendStaticLocationTask>()
     private val sendLiveLocationTask = mockk<SendLiveLocationTask>()
     private val startLiveLocationShareTask = mockk<StartLiveLocationShareTask>()
     private val stopLiveLocationShareTask = mockk<StopLiveLocationShareTask>()
+    private val checkIfExistingActiveLiveTask = mockk<CheckIfExistingActiveLiveTask>()
     private val fakeLiveLocationShareAggregatedSummaryMapper = mockk<LiveLocationShareAggregatedSummaryMapper>()
 
     private val defaultLocationSharingService = DefaultLocationSharingService(
-            roomId = fakeRoomId,
+            roomId = A_ROOM_ID,
             monarchy = fakeMonarchy.instance,
             sendStaticLocationTask = sendStaticLocationTask,
             sendLiveLocationTask = sendLiveLocationTask,
             startLiveLocationShareTask = startLiveLocationShareTask,
             stopLiveLocationShareTask = stopLiveLocationShareTask,
+            checkIfExistingActiveLiveTask = checkIfExistingActiveLiveTask,
             liveLocationShareAggregatedSummaryMapper = fakeLiveLocationShareAggregatedSummaryMapper
     )
 
+    @Before
+    fun setUp() {
+        mockkStatic("androidx.lifecycle.Transformations")
+    }
+
     @After
     fun tearDown() {
         unmockkAll()
@@ -117,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
@@ -154,7 +217,7 @@ internal class DefaultLocationSharingServiceTest {
         )
 
         fakeMonarchy.givenWhere<LiveLocationShareAggregatedSummaryEntity>()
-                .givenEqualTo(LiveLocationShareAggregatedSummaryEntityFields.ROOM_ID, fakeRoomId)
+                .givenEqualTo(LiveLocationShareAggregatedSummaryEntityFields.ROOM_ID, A_ROOM_ID)
                 .givenEqualTo(LiveLocationShareAggregatedSummaryEntityFields.IS_ACTIVE, true)
                 .givenIsNotEmpty(LiveLocationShareAggregatedSummaryEntityFields.USER_ID)
                 .givenIsNotNull(LiveLocationShareAggregatedSummaryEntityFields.LAST_LOCATION_CONTENT)
@@ -168,4 +231,38 @@ internal class DefaultLocationSharingServiceTest {
 
         result shouldBeEqualTo listOf(summary)
     }
+
+    @Test
+    fun `given an event id when getting livedata on corresponding live summary then it is correctly computed`() {
+        val entity = LiveLocationShareAggregatedSummaryEntity()
+        val summary = LiveLocationShareAggregatedSummary(
+                userId = "",
+                isActive = true,
+                endOfLiveTimestampMillis = 123,
+                lastLocationDataContent = null
+        )
+
+        fakeMonarchy.givenWhere<LiveLocationShareAggregatedSummaryEntity>()
+                .givenEqualTo(LiveLocationShareAggregatedSummaryEntityFields.ROOM_ID, A_ROOM_ID)
+                .givenEqualTo(LiveLocationShareAggregatedSummaryEntityFields.EVENT_ID, AN_EVENT_ID)
+        val liveData = fakeMonarchy.givenFindAllMappedWithChangesReturns(
+                realmEntities = listOf(entity),
+                mappedResult = listOf(summary),
+                fakeLiveLocationShareAggregatedSummaryMapper
+        )
+        val mapper = slot<Function<List<LiveLocationShareAggregatedSummary>, Optional<LiveLocationShareAggregatedSummary>>>()
+        every {
+            Transformations.map(
+                    liveData,
+                    capture(mapper)
+            )
+        } answers {
+            val value = secondArg<Function<List<LiveLocationShareAggregatedSummary>, Optional<LiveLocationShareAggregatedSummary>>>().apply(listOf(summary))
+            MutableLiveData(value)
+        }
+
+        val result = defaultLocationSharingService.getLiveLocationShareSummary(AN_EVENT_ID).value
+
+        result shouldBeEqualTo summary.toOptional()
+    }
 }
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/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fakes/FakeMonarchy.kt b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fakes/FakeMonarchy.kt
index 9b4ca332d5..d77084fe3b 100644
--- a/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fakes/FakeMonarchy.kt
+++ b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fakes/FakeMonarchy.kt
@@ -16,6 +16,7 @@
 
 package org.matrix.android.sdk.test.fakes
 
+import androidx.lifecycle.LiveData
 import androidx.lifecycle.MutableLiveData
 import com.zhuinden.monarchy.Monarchy
 import io.mockk.MockKVerificationScope
@@ -60,10 +61,11 @@ internal class FakeMonarchy {
             realmEntities: List<T>,
             mappedResult: List<R>,
             mapper: Monarchy.Mapper<R, T>
-    ) {
+    ): LiveData<List<R>> {
         every { mapper.map(any()) } returns mockk()
         val monarchyQuery = slot<Monarchy.Query<T>>()
         val monarchyMapper = slot<Monarchy.Mapper<R, T>>()
+        val result = MutableLiveData(mappedResult)
         every {
             instance.findAllMappedWithChanges(capture(monarchyQuery), capture(monarchyMapper))
         } answers {
@@ -71,7 +73,8 @@ internal class FakeMonarchy {
             realmEntities.forEach {
                 monarchyMapper.captured.map(it)
             }
-            MutableLiveData(mappedResult)
+            result
         }
+        return result
     }
 }
diff --git a/vector/build.gradle b/vector/build.gradle
index f3435178e9..f9ca36f1b5 100644
--- a/vector/build.gradle
+++ b/vector/build.gradle
@@ -372,7 +372,6 @@ dependencies {
     implementation "com.gabrielittner.threetenbp:lazythreetenbp:0.10.0"
 
     implementation libs.squareup.moshi
-    implementation libs.squareup.moshiKt
     kapt libs.squareup.moshiKotlin
 
     // Lifecycle
@@ -534,10 +533,10 @@ dependencies {
     }
 
     // Flipper, debug builds only
-    debugImplementation('com.facebook.flipper:flipper:0.150.0') {
+    debugImplementation(libs.flipper.flipper) {
         exclude group: 'com.facebook.fbjni', module: 'fbjni'
     }
-    debugImplementation('com.facebook.flipper:flipper-network-plugin:0.150.0') {
+    debugImplementation(libs.flipper.flipperNetworkPlugin) {
         exclude group: 'com.facebook.fbjni', module: 'fbjni'
     }
     debugImplementation 'com.facebook.soloader:soloader:0.10.3'
diff --git a/vector/src/main/java/im/vector/app/core/di/ActiveSessionHolder.kt b/vector/src/main/java/im/vector/app/core/di/ActiveSessionHolder.kt
index ef7f0896b8..21b4e287c6 100644
--- a/vector/src/main/java/im/vector/app/core/di/ActiveSessionHolder.kt
+++ b/vector/src/main/java/im/vector/app/core/di/ActiveSessionHolder.kt
@@ -44,11 +44,11 @@ class ActiveSessionHolder @Inject constructor(
         private val guardServiceStarter: GuardServiceStarter
 ) {
 
-    private var activeSession: AtomicReference<Session?> = AtomicReference()
+    private var activeSessionReference: AtomicReference<Session?> = AtomicReference()
 
     fun setActiveSession(session: Session) {
         Timber.w("setActiveSession of ${session.myUserId}")
-        activeSession.set(session)
+        activeSessionReference.set(session)
         activeSessionDataSource.post(Option.just(session))
 
         keyRequestHandler.start(session)
@@ -68,7 +68,7 @@ class ActiveSessionHolder @Inject constructor(
             it.removeListener(sessionListener)
         }
 
-        activeSession.set(null)
+        activeSessionReference.set(null)
         activeSessionDataSource.post(Option.empty())
 
         keyRequestHandler.stop()
@@ -80,15 +80,15 @@ class ActiveSessionHolder @Inject constructor(
     }
 
     fun hasActiveSession(): Boolean {
-        return activeSession.get() != null
+        return activeSessionReference.get() != null
     }
 
     fun getSafeActiveSession(): Session? {
-        return activeSession.get()
+        return activeSessionReference.get()
     }
 
     fun getActiveSession(): Session {
-        return activeSession.get()
+        return activeSessionReference.get()
                 ?: throw IllegalStateException("You should authenticate before using this")
     }
 
diff --git a/vector/src/main/java/im/vector/app/core/ui/list/ButtonPositiveDestructiveButtonBarItem.kt b/vector/src/main/java/im/vector/app/core/ui/list/ButtonPositiveDestructiveButtonBarItem.kt
new file mode 100644
index 0000000000..95c1a4457d
--- /dev/null
+++ b/vector/src/main/java/im/vector/app/core/ui/list/ButtonPositiveDestructiveButtonBarItem.kt
@@ -0,0 +1,59 @@
+/*
+ * Copyright (c) 2020 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 im.vector.app.core.ui.list
+
+import com.airbnb.epoxy.EpoxyAttribute
+import com.airbnb.epoxy.EpoxyModelClass
+import com.google.android.material.button.MaterialButton
+import im.vector.app.R
+import im.vector.app.core.epoxy.ClickListener
+import im.vector.app.core.epoxy.VectorEpoxyHolder
+import im.vector.app.core.epoxy.VectorEpoxyModel
+import im.vector.app.core.epoxy.onClick
+import im.vector.lib.core.utils.epoxy.charsequence.EpoxyCharSequence
+
+/**
+ * A generic button list item.
+ */
+@EpoxyModelClass(layout = R.layout.item_positive_destrutive_buttons)
+abstract class ButtonPositiveDestructiveButtonBarItem : VectorEpoxyModel<ButtonPositiveDestructiveButtonBarItem.Holder>() {
+
+    @EpoxyAttribute
+    var positiveText: EpoxyCharSequence? = null
+
+    @EpoxyAttribute
+    var destructiveText: EpoxyCharSequence? = null
+
+    @EpoxyAttribute(EpoxyAttribute.Option.DoNotHash)
+    var positiveButtonClickAction: ClickListener? = null
+
+    @EpoxyAttribute(EpoxyAttribute.Option.DoNotHash)
+    var destructiveButtonClickAction: ClickListener? = null
+
+    override fun bind(holder: Holder) {
+        super.bind(holder)
+        positiveText?.charSequence?.let { holder.positiveButton.text = it }
+        destructiveText?.charSequence?.let { holder.destructiveButton.text = it }
+
+        holder.positiveButton.onClick(positiveButtonClickAction)
+        holder.destructiveButton.onClick(destructiveButtonClickAction)
+    }
+
+    class Holder : VectorEpoxyHolder() {
+        val destructiveButton by bind<MaterialButton>(R.id.destructive_button)
+        val positiveButton by bind<MaterialButton>(R.id.positive_button)
+    }
+}
diff --git a/vector/src/main/java/im/vector/app/features/call/dialpad/DialPadLookup.kt b/vector/src/main/java/im/vector/app/features/call/dialpad/DialPadLookup.kt
index e835a74fd6..8f904c8ab8 100644
--- a/vector/src/main/java/im/vector/app/features/call/dialpad/DialPadLookup.kt
+++ b/vector/src/main/java/im/vector/app/features/call/dialpad/DialPadLookup.kt
@@ -22,6 +22,7 @@ import im.vector.app.features.call.vectorCallService
 import im.vector.app.features.call.webrtc.WebRtcCallManager
 import im.vector.app.features.createdirect.DirectRoomHelper
 import org.matrix.android.sdk.api.session.Session
+import timber.log.Timber
 import javax.inject.Inject
 
 class DialPadLookup @Inject constructor(
@@ -42,18 +43,23 @@ class DialPadLookup @Inject constructor(
         val sipUserId = thirdPartyUser.userId
         val nativeLookupResults = session.sipNativeLookup(thirdPartyUser.userId)
         // If I have a native user I check for an existing native room with him...
-        val roomId = if (nativeLookupResults.isNotEmpty()) {
+        if (nativeLookupResults.isNotEmpty()) {
             val nativeUserId = nativeLookupResults.first().userId
             if (nativeUserId == session.myUserId) {
                 throw Failure.NumberIsYours
             }
-            session.roomService().getExistingDirectRoomWithUser(nativeUserId)
-            // if there is not, just create a DM with the sip user
-                    ?: directRoomHelper.ensureDMExists(sipUserId)
-        } else {
-            // do the same if there is no corresponding native user.
-            directRoomHelper.ensureDMExists(sipUserId)
+            var nativeRoomId = session.roomService().getExistingDirectRoomWithUser(nativeUserId)
+            if (nativeRoomId == null) {
+                // if there is no existing native room with the existing native user,
+                // just create a DM with the native user
+                nativeRoomId = directRoomHelper.ensureDMExists(nativeUserId)
+            }
+            Timber.d("lookupPhoneNumber with nativeUserId: $nativeUserId and nativeRoomId: $nativeRoomId")
+            return Result(userId = nativeUserId, roomId = nativeRoomId)
         }
-        return Result(userId = sipUserId, roomId = roomId)
+        // If there is no native user then we return sipUserId and sipRoomId - this is usually a PSTN call.
+        val sipRoomId = directRoomHelper.ensureDMExists(sipUserId)
+        Timber.d("lookupPhoneNumber with sipRoomId: $sipRoomId and sipUserId: $sipUserId")
+        return Result(userId = sipUserId, roomId = sipRoomId)
     }
 }
diff --git a/vector/src/main/java/im/vector/app/features/crypto/verification/VerificationAction.kt b/vector/src/main/java/im/vector/app/features/crypto/verification/VerificationAction.kt
index c4ae2d278b..1b18117cf3 100644
--- a/vector/src/main/java/im/vector/app/features/crypto/verification/VerificationAction.kt
+++ b/vector/src/main/java/im/vector/app/features/crypto/verification/VerificationAction.kt
@@ -30,6 +30,8 @@ sealed class VerificationAction : VectorViewModelAction {
     data class GotItConclusion(val verified: Boolean) : VerificationAction()
     object SkipVerification : VerificationAction()
     object VerifyFromPassphrase : VerificationAction()
+    object ReadyPendingVerification : VerificationAction()
+    object CancelPendingVerification : VerificationAction()
     data class GotResultFromSsss(val cypherData: String, val alias: String) : VerificationAction()
     object CancelledFromSsss : VerificationAction()
     object SecuredStorageHasBeenReset : VerificationAction()
diff --git a/vector/src/main/java/im/vector/app/features/crypto/verification/VerificationBottomSheetViewModel.kt b/vector/src/main/java/im/vector/app/features/crypto/verification/VerificationBottomSheetViewModel.kt
index 46f7adb911..b8146b8041 100644
--- a/vector/src/main/java/im/vector/app/features/crypto/verification/VerificationBottomSheetViewModel.kt
+++ b/vector/src/main/java/im/vector/app/features/crypto/verification/VerificationBottomSheetViewModel.kt
@@ -360,6 +360,27 @@ class VerificationBottomSheetViewModel @AssistedInject constructor(
                         as? SasVerificationTransaction)
                         ?.shortCodeDoesNotMatch()
             }
+            is VerificationAction.ReadyPendingVerification -> {
+                state.pendingRequest.invoke()?.let { request ->
+                    // will only be there for dm verif
+                    if (state.roomId != null) {
+                        session.cryptoService().verificationService()
+                                .readyPendingVerificationInDMs(
+                                        supportedVerificationMethodsProvider.provide(),
+                                        state.otherUserId,
+                                        state.roomId,
+                                        request.transactionId ?: ""
+                                )
+                    }
+                }
+            }
+            is VerificationAction.CancelPendingVerification -> {
+                state.pendingRequest.invoke()?.let {
+                    session.cryptoService().verificationService()
+                            .cancelVerificationRequest(it)
+                }
+                _viewEvents.post(VerificationBottomSheetViewEvents.Dismiss)
+            }
             is VerificationAction.GotItConclusion -> {
                 if (state.isVerificationRequired && !action.verified) {
                     // we should go back to first screen
diff --git a/vector/src/main/java/im/vector/app/features/crypto/verification/choose/VerificationChooseMethodController.kt b/vector/src/main/java/im/vector/app/features/crypto/verification/choose/VerificationChooseMethodController.kt
index acc8cf61b9..f2a0a7d7e9 100644
--- a/vector/src/main/java/im/vector/app/features/crypto/verification/choose/VerificationChooseMethodController.kt
+++ b/vector/src/main/java/im/vector/app/features/crypto/verification/choose/VerificationChooseMethodController.kt
@@ -21,6 +21,7 @@ import im.vector.app.R
 import im.vector.app.core.epoxy.bottomSheetDividerItem
 import im.vector.app.core.resources.ColorProvider
 import im.vector.app.core.resources.StringProvider
+import im.vector.app.core.ui.list.buttonPositiveDestructiveButtonBarItem
 import im.vector.app.features.crypto.verification.epoxy.bottomSheetVerificationActionItem
 import im.vector.app.features.crypto.verification.epoxy.bottomSheetVerificationNoticeItem
 import im.vector.app.features.crypto.verification.epoxy.bottomSheetVerificationQrCodeItem
@@ -108,6 +109,15 @@ class VerificationChooseMethodController @Inject constructor(
                 iconColor(host.colorProvider.getColorFromAttribute(R.attr.vctr_content_primary))
                 listener { host.listener?.doVerifyBySas() }
             }
+        } else if (!state.isReadySent) {
+            // a bit of a special case, if you tapped on the timeline cell but not on a button
+            buttonPositiveDestructiveButtonBarItem {
+                id("accept_decline")
+                positiveText(host.stringProvider.getString(R.string.action_accept).toEpoxyCharSequence())
+                destructiveText(host.stringProvider.getString(R.string.action_decline).toEpoxyCharSequence())
+                positiveButtonClickAction { host.listener?.acceptRequest() }
+                destructiveButtonClickAction { host.listener?.declineRequest() }
+            }
         }
 
         if (state.isMe && state.canCrossSign) {
@@ -131,5 +141,7 @@ class VerificationChooseMethodController @Inject constructor(
         fun openCamera()
         fun doVerifyBySas()
         fun onClickOnWasNotMe()
+        fun acceptRequest()
+        fun declineRequest()
     }
 }
diff --git a/vector/src/main/java/im/vector/app/features/crypto/verification/choose/VerificationChooseMethodFragment.kt b/vector/src/main/java/im/vector/app/features/crypto/verification/choose/VerificationChooseMethodFragment.kt
index cf6bcc58c0..3d3766f430 100644
--- a/vector/src/main/java/im/vector/app/features/crypto/verification/choose/VerificationChooseMethodFragment.kt
+++ b/vector/src/main/java/im/vector/app/features/crypto/verification/choose/VerificationChooseMethodFragment.kt
@@ -100,6 +100,14 @@ class VerificationChooseMethodFragment @Inject constructor(
         sharedViewModel.itWasNotMe()
     }
 
+    override fun acceptRequest() {
+        sharedViewModel.handle(VerificationAction.ReadyPendingVerification)
+    }
+
+    override fun declineRequest() {
+        sharedViewModel.handle(VerificationAction.CancelPendingVerification)
+    }
+
     private fun doOpenQRCodeScanner() {
         QrCodeScannerActivity.startForResult(requireActivity(), scanActivityResultLauncher)
     }
diff --git a/vector/src/main/java/im/vector/app/features/crypto/verification/choose/VerificationChooseMethodViewModel.kt b/vector/src/main/java/im/vector/app/features/crypto/verification/choose/VerificationChooseMethodViewModel.kt
index a1f902f8f4..0f78dd52cb 100644
--- a/vector/src/main/java/im/vector/app/features/crypto/verification/choose/VerificationChooseMethodViewModel.kt
+++ b/vector/src/main/java/im/vector/app/features/crypto/verification/choose/VerificationChooseMethodViewModel.kt
@@ -44,7 +44,8 @@ data class VerificationChooseMethodViewState(
         val qrCodeText: String? = null,
         val sasModeAvailable: Boolean = false,
         val isMe: Boolean = false,
-        val canCrossSign: Boolean = false
+        val canCrossSign: Boolean = false,
+        val isReadySent: Boolean = false
 ) : MavericksState
 
 class VerificationChooseMethodViewModel @AssistedInject constructor(
@@ -81,7 +82,8 @@ class VerificationChooseMethodViewModel @AssistedInject constructor(
             copy(
                     otherCanShowQrCode = pvr?.otherCanShowQrCode().orFalse(),
                     otherCanScanQrCode = pvr?.otherCanScanQrCode().orFalse(),
-                    sasModeAvailable = pvr?.isSasSupported().orFalse()
+                    sasModeAvailable = pvr?.isSasSupported().orFalse(),
+                    isReadySent = pvr?.isReady.orFalse(),
             )
         }
     }
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 1c2255246b..48f8aef421 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
@@ -53,6 +53,7 @@ import im.vector.app.features.home.room.detail.timeline.factory.TimelineFactory
 import im.vector.app.features.home.room.detail.timeline.url.PreviewUrlRetriever
 import im.vector.app.features.home.room.typing.TypingHelper
 import im.vector.app.features.location.LocationSharingServiceConnection
+import im.vector.app.features.location.live.StopLiveLocationShareUseCase
 import im.vector.app.features.notifications.NotificationDrawerManager
 import im.vector.app.features.powerlevel.PowerLevelsFlowFactory
 import im.vector.app.features.raw.wellknown.getOutboundSessionKeySharingStrategyOrDefault
@@ -92,6 +93,7 @@ import org.matrix.android.sdk.api.session.file.FileService
 import org.matrix.android.sdk.api.session.getRoom
 import org.matrix.android.sdk.api.session.room.getStateEvent
 import org.matrix.android.sdk.api.session.room.getTimelineEvent
+import org.matrix.android.sdk.api.session.room.location.UpdateLiveLocationShareResult
 import org.matrix.android.sdk.api.session.room.members.ChangeMembershipState
 import org.matrix.android.sdk.api.session.room.members.roomMemberQueryParams
 import org.matrix.android.sdk.api.session.room.model.Membership
@@ -133,8 +135,9 @@ class TimelineViewModel @AssistedInject constructor(
         private val decryptionFailureTracker: DecryptionFailureTracker,
         private val notificationDrawerManager: NotificationDrawerManager,
         private val locationSharingServiceConnection: LocationSharingServiceConnection,
+        private val stopLiveLocationShareUseCase: StopLiveLocationShareUseCase,
         timelineFactory: TimelineFactory,
-        appStateHandler: AppStateHandler
+        appStateHandler: AppStateHandler,
 ) : VectorViewModel<RoomDetailViewState, RoomDetailAction, RoomDetailViewEvents>(initialState),
         Timeline.Listener, ChatEffectManager.Delegate, CallProtocolsChecker.Listener, LocationSharingServiceConnection.Callback {
 
@@ -1139,7 +1142,12 @@ class TimelineViewModel @AssistedInject constructor(
     }
 
     private fun handleStopLiveLocationSharing() {
-        locationSharingServiceConnection.stopLiveLocationSharing(room.roomId)
+        viewModelScope.launch {
+            val result = stopLiveLocationShareUseCase.execute(room.roomId)
+            if (result is UpdateLiveLocationShareResult.Failure) {
+                _viewEvents.post(RoomDetailViewEvents.Failure(throwable = result.error, showInDialog = true))
+            }
+        }
     }
 
     private fun observeRoomSummary() {
@@ -1310,7 +1318,7 @@ class TimelineViewModel @AssistedInject constructor(
         // we should also mark it as read here, for the scenario that the user
         // is already in the thread timeline
         markThreadTimelineAsReadLocal()
-        locationSharingServiceConnection.unbind()
+        locationSharingServiceConnection.unbind(this)
         super.onCleared()
     }
 }
diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/MessageItemFactory.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/MessageItemFactory.kt
index 54bfbdd8a0..853fef8bc8 100644
--- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/MessageItemFactory.kt
+++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/MessageItemFactory.kt
@@ -59,12 +59,6 @@ import im.vector.app.features.home.room.detail.timeline.item.MessageVoiceItem
 import im.vector.app.features.home.room.detail.timeline.item.MessageVoiceItem_
 import im.vector.app.features.home.room.detail.timeline.item.PollItem
 import im.vector.app.features.home.room.detail.timeline.item.PollItem_
-import im.vector.app.features.home.room.detail.timeline.item.PollOptionViewState.PollEnded
-import im.vector.app.features.home.room.detail.timeline.item.PollOptionViewState.PollReady
-import im.vector.app.features.home.room.detail.timeline.item.PollOptionViewState.PollSending
-import im.vector.app.features.home.room.detail.timeline.item.PollOptionViewState.PollUndisclosed
-import im.vector.app.features.home.room.detail.timeline.item.PollOptionViewState.PollVoted
-import im.vector.app.features.home.room.detail.timeline.item.PollResponseData
 import im.vector.app.features.home.room.detail.timeline.item.RedactedMessageItem
 import im.vector.app.features.home.room.detail.timeline.item.RedactedMessageItem_
 import im.vector.app.features.home.room.detail.timeline.item.VerificationRequestItem
@@ -81,18 +75,11 @@ import im.vector.app.features.location.UrlMapProvider
 import im.vector.app.features.location.toLocationData
 import im.vector.app.features.media.ImageContentRenderer
 import im.vector.app.features.media.VideoContentRenderer
-import im.vector.app.features.poll.PollState
-import im.vector.app.features.poll.PollState.Ended
-import im.vector.app.features.poll.PollState.Ready
-import im.vector.app.features.poll.PollState.Sending
-import im.vector.app.features.poll.PollState.Undisclosed
-import im.vector.app.features.poll.PollState.Voted
 import im.vector.app.features.settings.VectorPreferences
 import im.vector.app.features.voice.AudioWaveformView
 import im.vector.lib.core.utils.epoxy.charsequence.toEpoxyCharSequence
 import me.gujun.android.span.span
 import org.matrix.android.sdk.api.MatrixUrls.isMxcUrl
-import org.matrix.android.sdk.api.extensions.orFalse
 import org.matrix.android.sdk.api.session.Session
 import org.matrix.android.sdk.api.session.crypto.attachments.toElementToDecrypt
 import org.matrix.android.sdk.api.session.events.model.RelationType
@@ -113,8 +100,6 @@ import org.matrix.android.sdk.api.session.room.model.message.MessageTextContent
 import org.matrix.android.sdk.api.session.room.model.message.MessageType
 import org.matrix.android.sdk.api.session.room.model.message.MessageVerificationRequestContent
 import org.matrix.android.sdk.api.session.room.model.message.MessageVideoContent
-import org.matrix.android.sdk.api.session.room.model.message.PollAnswer
-import org.matrix.android.sdk.api.session.room.model.message.PollType
 import org.matrix.android.sdk.api.session.room.model.message.getFileUrl
 import org.matrix.android.sdk.api.session.room.model.message.getThumbnailUrl
 import org.matrix.android.sdk.api.session.room.timeline.getLastMessageContent
@@ -149,6 +134,7 @@ class MessageItemFactory @Inject constructor(
         private val vectorPreferences: VectorPreferences,
         private val urlMapProvider: UrlMapProvider,
         private val liveLocationShareMessageItemFactory: LiveLocationShareMessageItemFactory,
+        private val pollItemViewStateFactory: PollItemViewStateFactory,
 ) {
 
     // TODO inject this properly?
@@ -251,62 +237,21 @@ class MessageItemFactory @Inject constructor(
             callback: TimelineEventController.Callback?,
             attributes: AbsMessageItem.Attributes,
     ): PollItem {
-        val pollResponseSummary = informationData.pollResponseAggregatedSummary
-        val pollState = createPollState(informationData, pollResponseSummary, pollContent)
-        val pollCreationInfo = pollContent.getBestPollCreationInfo()
-        val questionText = pollCreationInfo?.question?.getBestQuestion().orEmpty()
-        val question = createPollQuestion(informationData, questionText, callback)
-        val optionViewStates = pollCreationInfo?.answers?.mapToOptions(pollState, informationData)
-        val totalVotesText = createTotalVotesText(pollState, pollResponseSummary)
+        val pollViewState = pollItemViewStateFactory.create(pollContent, informationData)
 
         return PollItem_()
                 .attributes(attributes)
                 .eventId(informationData.eventId)
-                .pollQuestion(question)
-                .canVote(pollState.isVotable())
-                .totalVotesText(totalVotesText)
-                .optionViewStates(optionViewStates)
+                .pollQuestion(createPollQuestion(informationData, pollViewState.question, callback))
+                .canVote(pollViewState.canVote)
+                .totalVotesText(pollViewState.totalVotes)
+                .optionViewStates(pollViewState.optionViewStates)
                 .edited(informationData.hasBeenEdited)
                 .highlighted(highlight)
                 .leftGuideline(avatarSizeProvider.leftGuideline)
                 .callback(callback)
     }
 
-    private fun createPollState(
-            informationData: MessageInformationData,
-            pollResponseSummary: PollResponseData?,
-            pollContent: MessagePollContent,
-    ): PollState = when {
-        !informationData.sendState.isSent() -> Sending
-        pollResponseSummary?.isClosed.orFalse() -> Ended
-        pollContent.getBestPollCreationInfo()?.kind == PollType.UNDISCLOSED -> Undisclosed
-        pollResponseSummary?.myVote?.isNotEmpty().orFalse() -> Voted(pollResponseSummary?.totalVotes ?: 0)
-        else -> Ready
-    }
-
-    private fun List<PollAnswer>.mapToOptions(
-            pollState: PollState,
-            informationData: MessageInformationData,
-    ) = map { answer ->
-        val pollResponseSummary = informationData.pollResponseAggregatedSummary
-        val winnerVoteCount = pollResponseSummary?.winnerVoteCount
-        val optionId = answer.id ?: ""
-        val optionAnswer = answer.getBestAnswer() ?: ""
-        val voteSummary = pollResponseSummary?.votes?.get(answer.id)
-        val voteCount = voteSummary?.total ?: 0
-        val votePercentage = voteSummary?.percentage ?: 0.0
-        val isMyVote = pollResponseSummary?.myVote == answer.id
-        val isWinner = winnerVoteCount != 0 && voteCount == winnerVoteCount
-
-        when (pollState) {
-            Sending -> PollSending(optionId, optionAnswer)
-            Ready -> PollReady(optionId, optionAnswer)
-            is Voted -> PollVoted(optionId, optionAnswer, voteCount, votePercentage, isMyVote)
-            Undisclosed -> PollUndisclosed(optionId, optionAnswer, isMyVote)
-            Ended -> PollEnded(optionId, optionAnswer, voteCount, votePercentage, isWinner)
-        }
-    }
-
     private fun createPollQuestion(
             informationData: MessageInformationData,
             question: String,
@@ -317,20 +262,6 @@ class MessageItemFactory @Inject constructor(
         question
     }.toEpoxyCharSequence()
 
-    private fun createTotalVotesText(
-            pollState: PollState,
-            pollResponseSummary: PollResponseData?,
-    ): String {
-        val votes = pollResponseSummary?.totalVotes ?: 0
-        return when {
-            pollState is Ended -> stringProvider.getQuantityString(R.plurals.poll_total_vote_count_after_ended, votes, votes)
-            pollState is Undisclosed -> ""
-            pollState is Voted -> stringProvider.getQuantityString(R.plurals.poll_total_vote_count_before_ended_and_voted, votes, votes)
-            votes == 0 -> stringProvider.getString(R.string.poll_no_votes_cast)
-            else -> stringProvider.getQuantityString(R.plurals.poll_total_vote_count_before_ended_and_not_voted, votes, votes)
-        }
-    }
-
     private fun buildAudioMessageItem(
             params: TimelineItemFactoryParams,
             messageContent: MessageAudioContent,
diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/PollItemViewStateFactory.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/PollItemViewStateFactory.kt
new file mode 100644
index 0000000000..8da0f2d279
--- /dev/null
+++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/PollItemViewStateFactory.kt
@@ -0,0 +1,165 @@
+/*
+ * 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 im.vector.app.features.home.room.detail.timeline.factory
+
+import im.vector.app.R
+import im.vector.app.core.resources.StringProvider
+import im.vector.app.features.home.room.detail.timeline.item.MessageInformationData
+import im.vector.app.features.home.room.detail.timeline.item.PollOptionViewState
+import im.vector.app.features.home.room.detail.timeline.item.PollResponseData
+import im.vector.app.features.poll.PollViewState
+import org.matrix.android.sdk.api.extensions.orFalse
+import org.matrix.android.sdk.api.session.room.model.message.MessagePollContent
+import org.matrix.android.sdk.api.session.room.model.message.PollCreationInfo
+import javax.inject.Inject
+
+class PollItemViewStateFactory @Inject constructor(
+        private val stringProvider: StringProvider,
+) {
+
+    fun create(
+            pollContent: MessagePollContent,
+            informationData: MessageInformationData,
+    ): PollViewState {
+        val pollCreationInfo = pollContent.getBestPollCreationInfo()
+
+        val question = pollCreationInfo?.question?.getBestQuestion().orEmpty()
+
+        val pollResponseSummary = informationData.pollResponseAggregatedSummary
+        val winnerVoteCount = pollResponseSummary?.winnerVoteCount
+        val totalVotes = pollResponseSummary?.totalVotes ?: 0
+
+        return when {
+            !informationData.sendState.isSent() -> {
+                createSendingPollViewState(question, pollCreationInfo)
+            }
+            informationData.pollResponseAggregatedSummary?.isClosed.orFalse() -> {
+                createEndedPollViewState(question, pollCreationInfo, pollResponseSummary, totalVotes, winnerVoteCount)
+            }
+            pollContent.getBestPollCreationInfo()?.isUndisclosed().orFalse() -> {
+                createUndisclosedPollViewState(question, pollCreationInfo, pollResponseSummary)
+            }
+            informationData.pollResponseAggregatedSummary?.myVote?.isNotEmpty().orFalse() -> {
+                createVotedPollViewState(question, pollCreationInfo, pollResponseSummary, totalVotes)
+            }
+            else -> {
+                createReadyPollViewState(question, pollCreationInfo, totalVotes)
+            }
+        }
+    }
+
+    private fun createSendingPollViewState(question: String, pollCreationInfo: PollCreationInfo?): PollViewState {
+        return PollViewState(
+                question = question,
+                totalVotes = stringProvider.getString(R.string.poll_no_votes_cast),
+                canVote = false,
+                optionViewStates = pollCreationInfo?.answers?.map { answer ->
+                    PollOptionViewState.PollSending(
+                            optionId = answer.id ?: "",
+                            optionAnswer = answer.getBestAnswer() ?: ""
+                    )
+                },
+        )
+    }
+
+    private fun createEndedPollViewState(
+            question: String,
+            pollCreationInfo: PollCreationInfo?,
+            pollResponseSummary: PollResponseData?,
+            totalVotes: Int,
+            winnerVoteCount: Int?,
+    ): PollViewState {
+        return PollViewState(
+                question = question,
+                totalVotes = stringProvider.getQuantityString(R.plurals.poll_total_vote_count_after_ended, totalVotes, totalVotes),
+                canVote = false,
+                optionViewStates = pollCreationInfo?.answers?.map { answer ->
+                    val voteSummary = pollResponseSummary?.getVoteSummaryOfAnOption(answer.id ?: "")
+                    PollOptionViewState.PollEnded(
+                            optionId = answer.id ?: "",
+                            optionAnswer = answer.getBestAnswer() ?: "",
+                            voteCount = voteSummary?.total ?: 0,
+                            votePercentage = voteSummary?.percentage ?: 0.0,
+                            isWinner = winnerVoteCount != 0 && voteSummary?.total == winnerVoteCount
+                    )
+                },
+        )
+    }
+
+    private fun createUndisclosedPollViewState(
+            question: String,
+            pollCreationInfo: PollCreationInfo?,
+            pollResponseSummary: PollResponseData?
+    ): PollViewState {
+        return PollViewState(
+                question = question,
+                totalVotes = "",
+                canVote = true,
+                optionViewStates = pollCreationInfo?.answers?.map { answer ->
+                    val isMyVote = pollResponseSummary?.myVote == answer.id
+                    PollOptionViewState.PollUndisclosed(
+                            optionId = answer.id ?: "",
+                            optionAnswer = answer.getBestAnswer() ?: "",
+                            isSelected = isMyVote
+                    )
+                },
+        )
+    }
+
+    private fun createVotedPollViewState(
+            question: String,
+            pollCreationInfo: PollCreationInfo?,
+            pollResponseSummary: PollResponseData?,
+            totalVotes: Int
+    ): PollViewState {
+        return PollViewState(
+                question = question,
+                totalVotes = stringProvider.getQuantityString(R.plurals.poll_total_vote_count_before_ended_and_voted, totalVotes, totalVotes),
+                canVote = true,
+                optionViewStates = pollCreationInfo?.answers?.map { answer ->
+                    val isMyVote = pollResponseSummary?.myVote == answer.id
+                    val voteSummary = pollResponseSummary?.getVoteSummaryOfAnOption(answer.id ?: "")
+                    PollOptionViewState.PollVoted(
+                            optionId = answer.id ?: "",
+                            optionAnswer = answer.getBestAnswer() ?: "",
+                            voteCount = voteSummary?.total ?: 0,
+                            votePercentage = voteSummary?.percentage ?: 0.0,
+                            isSelected = isMyVote
+                    )
+                },
+        )
+    }
+
+    private fun createReadyPollViewState(question: String, pollCreationInfo: PollCreationInfo?, totalVotes: Int): PollViewState {
+        val totalVotesText = if (totalVotes == 0) {
+            stringProvider.getString(R.string.poll_no_votes_cast)
+        } else {
+            stringProvider.getQuantityString(R.plurals.poll_total_vote_count_before_ended_and_not_voted, totalVotes, totalVotes)
+        }
+        return PollViewState(
+                question = question,
+                totalVotes = totalVotesText,
+                canVote = true,
+                optionViewStates = pollCreationInfo?.answers?.map { answer ->
+                    PollOptionViewState.PollReady(
+                            optionId = answer.id ?: "",
+                            optionAnswer = answer.getBestAnswer() ?: ""
+                    )
+                },
+        )
+    }
+}
diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageInformationData.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageInformationData.kt
index 554dd0ada8..9b24720c88 100644
--- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageInformationData.kt
+++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/item/MessageInformationData.kt
@@ -91,7 +91,10 @@ data class PollResponseData(
         val totalVotes: Int = 0,
         val winnerVoteCount: Int = 0,
         val isClosed: Boolean = false
-) : Parcelable
+) : Parcelable {
+
+    fun getVoteSummaryOfAnOption(optionId: String) = votes?.get(optionId)
+}
 
 @Parcelize
 data class PollVoteSummaryData(
diff --git a/vector/src/main/java/im/vector/app/features/location/LocationData.kt b/vector/src/main/java/im/vector/app/features/location/LocationData.kt
index b3466ff871..3c25a5b398 100644
--- a/vector/src/main/java/im/vector/app/features/location/LocationData.kt
+++ b/vector/src/main/java/im/vector/app/features/location/LocationData.kt
@@ -30,7 +30,7 @@ data class LocationData(
 
 /**
  * Creates location data from a MessageLocationContent.
- * "geo:40.05,29.24;30" -> LocationData(40.05, 29.24, 30)
+ * "geo:40.05,29.24;u=30" -> LocationData(40.05, 29.24, 30)
  * @return location data or null if geo uri is not valid
  */
 fun MessageLocationContent.toLocationData(): LocationData? {
@@ -39,7 +39,7 @@ fun MessageLocationContent.toLocationData(): LocationData? {
 
 /**
  * Creates location data from a geoUri String.
- * "geo:40.05,29.24;30" -> LocationData(40.05, 29.24, 30)
+ * "geo:40.05,29.24;u=30" -> LocationData(40.05, 29.24, 30)
  * @return location data or null if geo uri is null or not valid
  */
 fun String?.toLocationData(): LocationData? {
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 ef612eeec2..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
@@ -23,17 +23,21 @@ import android.os.Parcelable
 import dagger.hilt.android.AndroidEntryPoint
 import im.vector.app.core.di.ActiveSessionHolder
 import im.vector.app.core.services.VectorService
+import im.vector.app.features.location.live.GetLiveLocationShareSummaryUseCase
 import im.vector.app.features.notifications.NotificationUtils
 import im.vector.app.features.session.coroutineScope
 import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Job
+import kotlinx.coroutines.flow.distinctUntilChangedBy
+import kotlinx.coroutines.flow.filter
+import kotlinx.coroutines.flow.launchIn
+import kotlinx.coroutines.flow.onEach
 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
 import javax.inject.Inject
 
 @AndroidEntryPoint
@@ -49,6 +53,7 @@ class LocationSharingService : VectorService(), LocationTracker.Callback {
     @Inject lateinit var notificationUtils: NotificationUtils
     @Inject lateinit var locationTracker: LocationTracker
     @Inject lateinit var activeSessionHolder: ActiveSessionHolder
+    @Inject lateinit var getLiveLocationShareSummaryUseCase: GetLiveLocationShareSummaryUseCase
 
     private val binder = LocalBinder()
 
@@ -56,37 +61,50 @@ class LocationSharingService : VectorService(), LocationTracker.Callback {
      * Keep track of a map between beacon event Id starting the live and RoomArgs.
      */
     private val roomArgsMap = mutableMapOf<String, RoomArgs>()
-    private val timers = mutableListOf<Timer>()
     var callback: Callback? = null
+    private val jobs = mutableListOf<Job>()
+    private var startInProgress = false
 
     override fun onCreate() {
         super.onCreate()
-        Timber.i("### LocationSharingService.onCreate")
+        Timber.i("onCreate")
 
+        initLocationTracking()
+    }
+
+    private fun initLocationTracking() {
         // Start tracking location
         locationTracker.addCallback(this)
         locationTracker.start()
+
+        launchWithActiveSession { session ->
+            val job = locationTracker.locations
+                    .onEach(this@LocationSharingService::onLocationUpdate)
+                    .launchIn(session.coroutineScope)
+            jobs.add(job)
+        }
     }
 
     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
             val notification = notificationUtils.buildLiveLocationSharingNotification()
             startForeground(roomArgs.roomId.hashCode(), notification)
 
-            // Schedule a timer to stop sharing
-            scheduleTimer(roomArgs.roomId, roomArgs.durationMillis)
-
             // Send beacon info state event
-            launchInIO { session ->
+            launchWithActiveSession { session ->
                 sendStartingLiveBeaconInfo(session, roomArgs)
             }
         }
 
+        startInProgress = false
+
         return START_STICKY
     }
 
@@ -100,7 +118,8 @@ class LocationSharingService : VectorService(), LocationTracker.Callback {
                 ?.let { result ->
                     when (result) {
                         is UpdateLiveLocationShareResult.Success -> {
-                            roomArgsMap[result.beaconEventId] = roomArgs
+                            addRoomArgs(result.beaconEventId, roomArgs)
+                            listenForLiveSummaryChanges(roomArgs.roomId, result.beaconEventId)
                             locationTracker.requestLastKnownLocation()
                         }
                         is UpdateLiveLocationShareResult.Failure -> {
@@ -110,55 +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 scheduleTimer(roomId: String, durationMillis: Long) {
-        Timer()
-                .apply {
-                    schedule(object : TimerTask() {
-                        override fun run() {
-                            stopSharingLocation(roomId)
-                            timers.remove(this@apply)
-                        }
-                    }, durationMillis)
-                }
-                .also {
-                    timers.add(it)
-                }
+    private fun stopSharingLocation(beaconEventId: String) {
+        Timber.i("stopSharingLocation for beacon $beaconEventId")
+        removeRoomArgs(beaconEventId)
+        tryToDestroyMe()
     }
 
-    fun stopSharingLocation(roomId: String) {
-        Timber.i("### LocationSharingService.stopSharingLocation for $roomId")
-
-        launchInIO { session ->
-            when (val result = sendStoppedBeaconInfo(session, roomId)) {
-                is UpdateLiveLocationShareResult.Success -> {
-                    synchronized(roomArgsMap) {
-                        val beaconIds = roomArgsMap
-                                .filter { it.value.roomId == roomId }
-                                .map { it.key }
-                        beaconIds.forEach { roomArgsMap.remove(it) }
-
-                        tryToDestroyMe()
-                    }
-                }
-                is UpdateLiveLocationShareResult.Failure -> callback?.onServiceError(result.error)
-                else -> Unit
-            }
-        }
-    }
-
-    private suspend fun sendStoppedBeaconInfo(session: Session, roomId: String): UpdateLiveLocationShareResult? {
-        return session.getRoom(roomId)
-                ?.locationSharingService()
-                ?.stopLiveLocationShare()
-    }
-
-    override fun onLocationUpdate(locationData: LocationData) {
-        Timber.i("### LocationSharingService.onLocationUpdate. Uncertainty: ${locationData.uncertainty}")
+    private fun onLocationUpdate(locationData: LocationData) {
+        Timber.i("onLocationUpdate. Uncertainty: ${locationData.uncertainty}")
 
         // Emit location update to all rooms in which live location sharing is active
         roomArgsMap.toMap().forEach { item ->
@@ -171,7 +154,7 @@ class LocationSharingService : VectorService(), LocationTracker.Callback {
             beaconInfoEventId: String,
             locationData: LocationData
     ) {
-        launchInIO { session ->
+        launchWithActiveSession { session ->
             session.getRoom(roomId)
                     ?.locationSharingService()
                     ?.sendLiveLocation(
@@ -189,31 +172,46 @@ class LocationSharingService : VectorService(), LocationTracker.Callback {
     }
 
     private fun tryToDestroyMe() {
-        if (roomArgsMap.isEmpty()) {
-            Timber.i("### LocationSharingService. Destroying self, time is up for all rooms")
-            destroyMe()
+        if (startInProgress.not() && roomArgsMap.isEmpty()) {
+            Timber.i("Destroying self, time is up for all rooms")
+            stopSelf()
         }
     }
 
-    private fun destroyMe() {
-        locationTracker.removeCallback(this)
-        timers.forEach { it.cancel() }
-        timers.clear()
-        stopSelf()
-    }
-
     override fun onDestroy() {
         super.onDestroy()
-        Timber.i("### LocationSharingService.onDestroy")
-        destroyMe()
+        Timber.i("onDestroy")
+        jobs.forEach { it.cancel() }
+        jobs.clear()
+        locationTracker.removeCallback(this)
     }
 
-    private fun launchInIO(block: suspend CoroutineScope.(Session) -> Unit) =
+    private fun addRoomArgs(beaconEventId: String, roomArgs: RoomArgs) {
+        Timber.i("adding roomArgs for beaconEventId: $beaconEventId")
+        roomArgsMap[beaconEventId] = roomArgs
+    }
+
+    private fun removeRoomArgs(beaconEventId: String) {
+        Timber.i("removing roomArgs for beaconEventId: $beaconEventId")
+        roomArgsMap.remove(beaconEventId)
+    }
+
+    private fun listenForLiveSummaryChanges(roomId: String, beaconEventId: String) {
+        launchWithActiveSession { session ->
+            val job = getLiveLocationShareSummaryUseCase.execute(roomId, beaconEventId)
+                    .distinctUntilChangedBy { it.isActive }
+                    .filter { it.isActive == false }
+                    .onEach { stopSharingLocation(beaconEventId) }
+                    .launchIn(session.coroutineScope)
+            jobs.add(job)
+        }
+    }
+
+    private fun launchWithActiveSession(block: suspend CoroutineScope.(Session) -> Unit) =
             activeSessionHolder
                     .getSafeActiveSession()
                     ?.let { session ->
                         session.coroutineScope.launch(
-                                context = session.coroutineDispatchers.io,
                                 block = { block(session) }
                         )
                     }
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 af09e0b1e0..db79564462 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
@@ -22,7 +22,9 @@ import android.content.Intent
 import android.content.ServiceConnection
 import android.os.IBinder
 import javax.inject.Inject
+import javax.inject.Singleton
 
+@Singleton
 class LocationSharingServiceConnection @Inject constructor(
         private val context: Context
 ) : ServiceConnection, LocationSharingService.Callback {
@@ -33,12 +35,12 @@ class LocationSharingServiceConnection @Inject constructor(
         fun onLocationServiceError(error: Throwable)
     }
 
-    private var callback: Callback? = null
+    private val callbacks = mutableSetOf<Callback>()
     private var isBound = false
     private var locationSharingService: LocationSharingService? = null
 
     fun bind(callback: Callback) {
-        this.callback = callback
+        addCallback(callback)
 
         if (isBound) {
             callback.onLocationServiceRunning()
@@ -49,12 +51,8 @@ class LocationSharingServiceConnection @Inject constructor(
         }
     }
 
-    fun unbind() {
-        callback = null
-    }
-
-    fun stopLiveLocationSharing(roomId: String) {
-        locationSharingService?.stopSharingLocation(roomId)
+    fun unbind(callback: Callback) {
+        removeCallback(callback)
     }
 
     override fun onServiceConnected(className: ComponentName, binder: IBinder) {
@@ -62,17 +60,33 @@ class LocationSharingServiceConnection @Inject constructor(
             it.callback = this
         }
         isBound = true
-        callback?.onLocationServiceRunning()
+        onCallbackActionNoArg(Callback::onLocationServiceRunning)
     }
 
     override fun onServiceDisconnected(className: ComponentName) {
         isBound = false
         locationSharingService?.callback = null
         locationSharingService = null
-        callback?.onLocationServiceStopped()
+        onCallbackActionNoArg(Callback::onLocationServiceStopped)
     }
 
     override fun onServiceError(error: Throwable) {
-        callback?.onLocationServiceError(error)
+        forwardErrorToCallbacks(error)
+    }
+
+    private fun addCallback(callback: Callback) {
+        callbacks.add(callback)
+    }
+
+    private fun removeCallback(callback: Callback) {
+        callbacks.remove(callback)
+    }
+
+    private fun onCallbackActionNoArg(action: Callback.() -> Unit) {
+        callbacks.toList().forEach(action)
+    }
+
+    private fun forwardErrorToCallbacks(error: Throwable) {
+        callbacks.toList().forEach { it.onLocationServiceError(error) }
     }
 }
diff --git a/vector/src/main/java/im/vector/app/features/location/LocationSharingViewModel.kt b/vector/src/main/java/im/vector/app/features/location/LocationSharingViewModel.kt
index 30476d064f..b9a2dc830c 100644
--- a/vector/src/main/java/im/vector/app/features/location/LocationSharingViewModel.kt
+++ b/vector/src/main/java/im/vector/app/features/location/LocationSharingViewModel.kt
@@ -39,6 +39,7 @@ import org.matrix.android.sdk.api.session.Session
 import org.matrix.android.sdk.api.session.getRoom
 import org.matrix.android.sdk.api.session.getUser
 import org.matrix.android.sdk.api.util.toMatrixItem
+import timber.log.Timber
 
 /**
  * Sampling period to compare target location and user location.
@@ -65,13 +66,20 @@ class LocationSharingViewModel @AssistedInject constructor(
     companion object : MavericksViewModelFactory<LocationSharingViewModel, LocationSharingViewState> by hiltMavericksViewModelFactory()
 
     init {
-        locationTracker.addCallback(this)
-        locationTracker.start()
+        initLocationTracking()
         setUserItem()
         updatePin()
         compareTargetAndUserLocation()
     }
 
+    private fun initLocationTracking() {
+        locationTracker.addCallback(this)
+        locationTracker.locations
+                .onEach(::onLocationUpdate)
+                .launchIn(viewModelScope)
+        locationTracker.start()
+    }
+
     private fun setUserItem() {
         setState { copy(userItem = session.getUser(session.myUserId)?.toMatrixItem()) }
     }
@@ -172,7 +180,8 @@ class LocationSharingViewModel @AssistedInject constructor(
         )
     }
 
-    override fun onLocationUpdate(locationData: LocationData) {
+    private fun onLocationUpdate(locationData: LocationData) {
+        Timber.d("onLocationUpdate()")
         setState {
             copy(lastKnownUserLocation = locationData)
         }
diff --git a/vector/src/main/java/im/vector/app/features/location/LocationTracker.kt b/vector/src/main/java/im/vector/app/features/location/LocationTracker.kt
index cdf13a7004..aa05fe764b 100644
--- a/vector/src/main/java/im/vector/app/features/location/LocationTracker.kt
+++ b/vector/src/main/java/im/vector/app/features/location/LocationTracker.kt
@@ -25,28 +25,27 @@ import androidx.annotation.VisibleForTesting
 import androidx.core.content.getSystemService
 import androidx.core.location.LocationListenerCompat
 import im.vector.app.BuildConfig
-import im.vector.app.core.utils.Debouncer
-import im.vector.app.core.utils.createBackgroundHandler
+import im.vector.app.core.di.ActiveSessionHolder
+import im.vector.app.features.session.coroutineScope
+import kotlinx.coroutines.flow.MutableSharedFlow
+import kotlinx.coroutines.flow.asSharedFlow
+import kotlinx.coroutines.flow.debounce
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.onEach
+import kotlinx.coroutines.launch
 import timber.log.Timber
 import javax.inject.Inject
 import javax.inject.Singleton
 
-private const val BKG_HANDLER_NAME = "LocationTracker.BKG_HANDLER_NAME"
-private const val LOCATION_DEBOUNCE_ID = "LocationTracker.LOCATION_DEBOUNCE_ID"
-
 @Singleton
 class LocationTracker @Inject constructor(
-        context: Context
+        context: Context,
+        private val activeSessionHolder: ActiveSessionHolder
 ) : LocationListenerCompat {
 
     private val locationManager = context.getSystemService<LocationManager>()
 
     interface Callback {
-        /**
-         * Called on every location update.
-         */
-        fun onLocationUpdate(locationData: LocationData)
-
         /**
          * Called when no location provider is available to request location updates.
          */
@@ -62,9 +61,16 @@ class LocationTracker @Inject constructor(
     @VisibleForTesting
     var hasLocationFromGPSProvider = false
 
-    private var lastLocation: LocationData? = null
+    private val _locations = MutableSharedFlow<Location>(replay = 1)
 
-    private val debouncer = Debouncer(createBackgroundHandler(BKG_HANDLER_NAME))
+    /**
+     * SharedFlow to collect location updates.
+     */
+    val locations = _locations.asSharedFlow()
+            .onEach { Timber.d("new location emitted") }
+            .debounce(MIN_TIME_TO_UPDATE_LOCATION_MILLIS)
+            .onEach { Timber.d("new location emitted after debounce") }
+            .map { it.toLocationData() }
 
     @RequiresPermission(anyOf = [Manifest.permission.ACCESS_COARSE_LOCATION, Manifest.permission.ACCESS_FINE_LOCATION])
     fun start() {
@@ -119,33 +125,35 @@ class LocationTracker @Inject constructor(
     }
 
     @RequiresPermission(anyOf = [Manifest.permission.ACCESS_COARSE_LOCATION, Manifest.permission.ACCESS_FINE_LOCATION])
+    @VisibleForTesting
     fun stop() {
         Timber.d("stop()")
         locationManager?.removeUpdates(this)
-        synchronized(this) {
-            callbacks.clear()
-        }
-        debouncer.cancelAll()
+        callbacks.clear()
         hasLocationFromGPSProvider = false
         hasLocationFromFusedProvider = false
     }
 
     /**
-     * Request the last known location. It will be given async through Callback.
-     * Please ensure adding a callback to receive the value.
+     * Request the last known location. It will be given async through corresponding flow.
+     * Please ensure collecting the flow before calling this method.
      */
     fun requestLastKnownLocation() {
-        lastLocation?.let { locationData -> onLocationUpdate(locationData) }
+        Timber.d("requestLastKnownLocation")
+        activeSessionHolder.getSafeActiveSession()?.coroutineScope?.launch {
+            _locations.replayCache.firstOrNull()?.let {
+                Timber.d("emitting last location from cache")
+                _locations.emit(it)
+            }
+        }
     }
 
-    @Synchronized
     fun addCallback(callback: Callback) {
         if (!callbacks.contains(callback)) {
             callbacks.add(callback)
         }
     }
 
-    @Synchronized
     fun removeCallback(callback: Callback) {
         callbacks.remove(callback)
         if (callbacks.size == 0) {
@@ -183,21 +191,19 @@ class LocationTracker @Inject constructor(
             }
         }
 
-        debouncer.debounce(LOCATION_DEBOUNCE_ID, MIN_TIME_TO_UPDATE_LOCATION_MILLIS) {
-            notifyLocation(location)
-        }
+        notifyLocation(location)
     }
 
     private fun notifyLocation(location: Location) {
-        if (BuildConfig.LOW_PRIVACY_LOG_ENABLE) {
-            Timber.d("notify location: $location")
-        } else {
-            Timber.d("notify location: ${location.provider}")
-        }
+        activeSessionHolder.getSafeActiveSession()?.coroutineScope?.launch {
+            if (BuildConfig.LOW_PRIVACY_LOG_ENABLE) {
+                Timber.d("notify location: $location")
+            } else {
+                Timber.d("notify location: ${location.provider}")
+            }
 
-        val locationData = location.toLocationData()
-        lastLocation = locationData
-        onLocationUpdate(locationData)
+            _locations.emit(location)
+        }
     }
 
     override fun onProviderDisabled(provider: String) {
@@ -215,9 +221,8 @@ class LocationTracker @Inject constructor(
                 }
     }
 
-    @Synchronized
     private fun onNoLocationProviderAvailable() {
-        callbacks.forEach {
+        callbacks.toList().forEach {
             try {
                 it.onNoLocationProviderAvailable()
             } catch (error: Exception) {
@@ -226,17 +231,6 @@ class LocationTracker @Inject constructor(
         }
     }
 
-    @Synchronized
-    private fun onLocationUpdate(locationData: LocationData) {
-        callbacks.forEach {
-            try {
-                it.onLocationUpdate(locationData)
-            } catch (error: Exception) {
-                Timber.e(error, "error in onLocationUpdate callback $it")
-            }
-        }
-    }
-
     private fun Location.toLocationData(): LocationData {
         return LocationData(latitude, longitude, accuracy.toDouble())
     }
diff --git a/vector/src/main/java/im/vector/app/features/location/live/GetLiveLocationShareSummaryUseCase.kt b/vector/src/main/java/im/vector/app/features/location/live/GetLiveLocationShareSummaryUseCase.kt
new file mode 100644
index 0000000000..0d8b70ccda
--- /dev/null
+++ b/vector/src/main/java/im/vector/app/features/location/live/GetLiveLocationShareSummaryUseCase.kt
@@ -0,0 +1,43 @@
+/*
+ * 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 im.vector.app.features.location.live
+
+import androidx.lifecycle.asFlow
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.emptyFlow
+import kotlinx.coroutines.flow.mapNotNull
+import kotlinx.coroutines.withContext
+import org.matrix.android.sdk.api.session.Session
+import org.matrix.android.sdk.api.session.getRoom
+import org.matrix.android.sdk.api.session.room.model.livelocation.LiveLocationShareAggregatedSummary
+import timber.log.Timber
+import javax.inject.Inject
+
+class GetLiveLocationShareSummaryUseCase @Inject constructor(
+        private val session: Session,
+) {
+
+    suspend fun execute(roomId: String, eventId: String): Flow<LiveLocationShareAggregatedSummary> = withContext(session.coroutineDispatchers.main) {
+        Timber.d("getting flow for roomId=$roomId and eventId=$eventId")
+        session.getRoom(roomId)
+                ?.locationSharingService()
+                ?.getLiveLocationShareSummary(eventId)
+                ?.asFlow()
+                ?.mapNotNull { it.getOrNull() }
+                ?: emptyFlow()
+    }
+}
diff --git a/vector/src/main/java/im/vector/app/features/location/live/StopLiveLocationShareUseCase.kt b/vector/src/main/java/im/vector/app/features/location/live/StopLiveLocationShareUseCase.kt
new file mode 100644
index 0000000000..402c7ffb15
--- /dev/null
+++ b/vector/src/main/java/im/vector/app/features/location/live/StopLiveLocationShareUseCase.kt
@@ -0,0 +1,38 @@
+/*
+ * 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 im.vector.app.features.location.live
+
+import im.vector.app.core.di.ActiveSessionHolder
+import org.matrix.android.sdk.api.session.getRoom
+import org.matrix.android.sdk.api.session.room.location.UpdateLiveLocationShareResult
+import javax.inject.Inject
+
+class StopLiveLocationShareUseCase @Inject constructor(
+        private val activeSessionHolder: ActiveSessionHolder
+) {
+
+    suspend fun execute(roomId: String): UpdateLiveLocationShareResult? {
+        return sendStoppedBeaconInfo(roomId)
+    }
+
+    private suspend fun sendStoppedBeaconInfo(roomId: String): UpdateLiveLocationShareResult? {
+        return activeSessionHolder.getActiveSession()
+                .getRoom(roomId)
+                ?.locationSharingService()
+                ?.stopLiveLocationShare()
+    }
+}
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 e89649709a..15c76b083e 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
@@ -24,13 +24,17 @@ import im.vector.app.core.di.MavericksAssistedViewModelFactory
 import im.vector.app.core.di.hiltMavericksViewModelFactory
 import im.vector.app.core.platform.VectorViewModel
 import im.vector.app.features.location.LocationSharingServiceConnection
+import im.vector.app.features.location.live.StopLiveLocationShareUseCase
 import kotlinx.coroutines.flow.launchIn
 import kotlinx.coroutines.flow.onEach
+import kotlinx.coroutines.launch
+import org.matrix.android.sdk.api.session.room.location.UpdateLiveLocationShareResult
 
 class LocationLiveMapViewModel @AssistedInject constructor(
         @Assisted private val initialState: LocationLiveMapViewState,
         getListOfUserLiveLocationUseCase: GetListOfUserLiveLocationUseCase,
         private val locationSharingServiceConnection: LocationSharingServiceConnection,
+        private val stopLiveLocationShareUseCase: StopLiveLocationShareUseCase,
 ) : VectorViewModel<LocationLiveMapViewState, LocationLiveMapAction, LocationLiveMapViewEvents>(initialState), LocationSharingServiceConnection.Callback {
 
     @AssistedFactory
@@ -47,6 +51,11 @@ class LocationLiveMapViewModel @AssistedInject constructor(
         locationSharingServiceConnection.bind(this)
     }
 
+    override fun onCleared() {
+        locationSharingServiceConnection.unbind(this)
+        super.onCleared()
+    }
+
     override fun handle(action: LocationLiveMapAction) {
         when (action) {
             is LocationLiveMapAction.AddMapSymbol -> handleAddMapSymbol(action)
@@ -70,7 +79,12 @@ class LocationLiveMapViewModel @AssistedInject constructor(
     }
 
     private fun handleStopSharing() {
-        locationSharingServiceConnection.stopLiveLocationSharing(initialState.roomId)
+        viewModelScope.launch {
+            val result = stopLiveLocationShareUseCase.execute(initialState.roomId)
+            if (result is UpdateLiveLocationShareResult.Failure) {
+                _viewEvents.post(LocationLiveMapViewEvents.Error(result.error))
+            }
+        }
     }
 
     override fun onLocationServiceRunning() {
diff --git a/vector/src/main/java/im/vector/app/features/poll/PollState.kt b/vector/src/main/java/im/vector/app/features/poll/PollViewState.kt
similarity index 71%
rename from vector/src/main/java/im/vector/app/features/poll/PollState.kt
rename to vector/src/main/java/im/vector/app/features/poll/PollViewState.kt
index 93cdb0ecbe..0f01d58c96 100644
--- a/vector/src/main/java/im/vector/app/features/poll/PollState.kt
+++ b/vector/src/main/java/im/vector/app/features/poll/PollViewState.kt
@@ -16,12 +16,11 @@
 
 package im.vector.app.features.poll
 
-sealed interface PollState {
-    object Sending : PollState
-    object Ready : PollState
-    data class Voted(val votes: Int) : PollState
-    object Undisclosed : PollState
-    object Ended : PollState
+import im.vector.app.features.home.room.detail.timeline.item.PollOptionViewState
 
-    fun isVotable() = this !is Sending && this !is Ended
-}
+data class PollViewState(
+    val question: String,
+    val totalVotes: String,
+    val canVote: Boolean,
+    val optionViewStates: List<PollOptionViewState>?,
+)
diff --git a/vector/src/main/res/layout/item_positive_destrutive_buttons.xml b/vector/src/main/res/layout/item_positive_destrutive_buttons.xml
new file mode 100644
index 0000000000..d21a4cba20
--- /dev/null
+++ b/vector/src/main/res/layout/item_positive_destrutive_buttons.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:tools="http://schemas.android.com/tools"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    android:gravity="center_horizontal"
+    android:orientation="horizontal">
+
+    <Button
+        android:id="@+id/destructive_button"
+        style="@style/Widget.Vector.Button.Destructive"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_marginEnd="16dp"
+        tools:text="@string/action_decline" />
+
+    <Button
+        android:id="@+id/positive_button"
+        style="@style/Widget.Vector.Button.Positive"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        tools:text="@string/action_accept" />
+
+</LinearLayout>
diff --git a/vector/src/test/java/im/vector/app/features/home/room/detail/timeline/factory/PollItemViewStateFactoryTest.kt b/vector/src/test/java/im/vector/app/features/home/room/detail/timeline/factory/PollItemViewStateFactoryTest.kt
new file mode 100644
index 0000000000..64ad03a019
--- /dev/null
+++ b/vector/src/test/java/im/vector/app/features/home/room/detail/timeline/factory/PollItemViewStateFactoryTest.kt
@@ -0,0 +1,223 @@
+/*
+ * 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 im.vector.app.features.home.room.detail.timeline.factory
+
+import im.vector.app.R
+import im.vector.app.features.home.room.detail.timeline.item.MessageInformationData
+import im.vector.app.features.home.room.detail.timeline.item.PollOptionViewState
+import im.vector.app.features.home.room.detail.timeline.item.PollResponseData
+import im.vector.app.features.home.room.detail.timeline.item.PollVoteSummaryData
+import im.vector.app.features.home.room.detail.timeline.item.ReactionsSummaryData
+import im.vector.app.features.home.room.detail.timeline.style.TimelineMessageLayout
+import im.vector.app.features.poll.PollViewState
+import im.vector.app.test.fakes.FakeStringProvider
+import org.amshove.kluent.shouldBeEqualTo
+import org.junit.Test
+import org.matrix.android.sdk.api.session.room.model.message.MessagePollContent
+import org.matrix.android.sdk.api.session.room.model.message.PollAnswer
+import org.matrix.android.sdk.api.session.room.model.message.PollCreationInfo
+import org.matrix.android.sdk.api.session.room.model.message.PollQuestion
+import org.matrix.android.sdk.api.session.room.model.message.PollType
+import org.matrix.android.sdk.api.session.room.send.SendState
+
+private val A_MESSAGE_INFORMATION_DATA = MessageInformationData(
+        eventId = "eventId",
+        senderId = "senderId",
+        ageLocalTS = 0,
+        avatarUrl = "",
+        sendState = SendState.SENT,
+        messageLayout = TimelineMessageLayout.Default(showAvatar = true, showDisplayName = true, showTimestamp = true),
+        reactionsSummary = ReactionsSummaryData(),
+        sentByMe = true,
+)
+
+private val A_POLL_RESPONSE_DATA = PollResponseData(
+        myVote = null,
+        votes = emptyMap(),
+)
+
+private val A_POLL_OPTION_IDS = listOf("5ef5f7b0-c9a1-49cf-a0b3-374729a43e76", "ec1a4db0-46d8-4d7a-9bb6-d80724715938", "3677ca8e-061b-40ab-bffe-b22e4e88fcad")
+
+private val A_POLL_CONTENT = MessagePollContent(
+        unstablePollCreationInfo = PollCreationInfo(
+                question = PollQuestion(
+                        unstableQuestion = "What is your favourite coffee?"
+                ),
+                kind = PollType.UNDISCLOSED_UNSTABLE,
+                maxSelections = 1,
+                answers = listOf(
+                        PollAnswer(
+                                id = A_POLL_OPTION_IDS[0],
+                                unstableAnswer = "Double Espresso"
+                        ),
+                        PollAnswer(
+                                id = A_POLL_OPTION_IDS[1],
+                                unstableAnswer = "Macchiato"
+                        ),
+                        PollAnswer(
+                                id = A_POLL_OPTION_IDS[2],
+                                unstableAnswer = "Iced Coffee"
+                        ),
+                )
+        )
+)
+
+class PollItemViewStateFactoryTest {
+
+    @Test
+    fun `given a sending poll state then poll is not votable and option states are PollSending`() {
+        val stringProvider = FakeStringProvider()
+        val pollItemViewStateFactory = PollItemViewStateFactory(stringProvider.instance)
+
+        val sendingPollInformationData = A_MESSAGE_INFORMATION_DATA.copy(sendState = SendState.SENDING)
+        val pollViewState = pollItemViewStateFactory.create(
+                pollContent = A_POLL_CONTENT,
+                informationData = sendingPollInformationData,
+        )
+
+        pollViewState shouldBeEqualTo PollViewState(
+                question = A_POLL_CONTENT.getBestPollCreationInfo()?.question?.getBestQuestion() ?: "",
+                totalVotes = stringProvider.instance.getString(R.string.poll_no_votes_cast),
+                canVote = false,
+                optionViewStates = A_POLL_CONTENT.getBestPollCreationInfo()?.answers?.map { answer ->
+                    PollOptionViewState.PollSending(
+                            optionId = answer.id ?: "",
+                            optionAnswer = answer.getBestAnswer() ?: ""
+                    )
+                },
+        )
+    }
+
+    @Test
+    fun `given a sent poll state when poll is closed then poll is not votable and option states are Ended`() {
+        val stringProvider = FakeStringProvider()
+        val pollItemViewStateFactory = PollItemViewStateFactory(stringProvider.instance)
+
+        val closedPollSummary = A_POLL_RESPONSE_DATA.copy(isClosed = true)
+        val closedPollInformationData = A_MESSAGE_INFORMATION_DATA.copy(pollResponseAggregatedSummary = closedPollSummary)
+
+        val pollViewState = pollItemViewStateFactory.create(
+                pollContent = A_POLL_CONTENT,
+                informationData = closedPollInformationData,
+        )
+
+        pollViewState shouldBeEqualTo PollViewState(
+                question = A_POLL_CONTENT.getBestPollCreationInfo()?.question?.getBestQuestion() ?: "",
+                totalVotes = stringProvider.instance.getQuantityString(R.plurals.poll_total_vote_count_after_ended, 0, 0),
+                canVote = false,
+                optionViewStates = A_POLL_CONTENT.getBestPollCreationInfo()?.answers?.map { answer ->
+                    PollOptionViewState.PollEnded(
+                            optionId = answer.id ?: "",
+                            optionAnswer = answer.getBestAnswer() ?: "",
+                            voteCount = 0,
+                            votePercentage = 0.0,
+                            isWinner = false
+                    )
+                },
+        )
+    }
+
+    @Test
+    fun `given a sent poll when undisclosed poll type is selected then poll is votable and option states are PollUndisclosed`() {
+        val stringProvider = FakeStringProvider()
+        val pollItemViewStateFactory = PollItemViewStateFactory(stringProvider.instance)
+
+        val pollViewState = pollItemViewStateFactory.create(
+                pollContent = A_POLL_CONTENT,
+                informationData = A_MESSAGE_INFORMATION_DATA,
+        )
+
+        pollViewState shouldBeEqualTo PollViewState(
+                question = A_POLL_CONTENT.getBestPollCreationInfo()?.question?.getBestQuestion() ?: "",
+                totalVotes = "",
+                canVote = true,
+                optionViewStates = A_POLL_CONTENT.getBestPollCreationInfo()?.answers?.map { answer ->
+                    PollOptionViewState.PollUndisclosed(
+                            optionId = answer.id ?: "",
+                            optionAnswer = answer.getBestAnswer() ?: "",
+                            isSelected = false
+                    )
+                },
+        )
+    }
+
+    @Test
+    fun `given a sent poll when my vote exists then poll is still votable and options states are PollVoted`() {
+        val stringProvider = FakeStringProvider()
+        val pollItemViewStateFactory = PollItemViewStateFactory(stringProvider.instance)
+
+        val votedPollData = A_POLL_RESPONSE_DATA.copy(
+                totalVotes = 1,
+                myVote = A_POLL_OPTION_IDS[0],
+                votes = mapOf(A_POLL_OPTION_IDS[0] to PollVoteSummaryData(total = 1, percentage = 1.0))
+        )
+        val disclosedPollContent = A_POLL_CONTENT.copy(
+                unstablePollCreationInfo = A_POLL_CONTENT.getBestPollCreationInfo()?.copy(
+                        kind = PollType.DISCLOSED_UNSTABLE
+                ),
+        )
+        val votedInformationData = A_MESSAGE_INFORMATION_DATA.copy(pollResponseAggregatedSummary = votedPollData)
+
+        val pollViewState = pollItemViewStateFactory.create(
+                pollContent = disclosedPollContent,
+                informationData = votedInformationData,
+        )
+
+        pollViewState shouldBeEqualTo PollViewState(
+                question = A_POLL_CONTENT.getBestPollCreationInfo()?.question?.getBestQuestion() ?: "",
+                totalVotes = stringProvider.instance.getQuantityString(R.plurals.poll_total_vote_count_before_ended_and_voted, 1, 1),
+                canVote = true,
+                optionViewStates = A_POLL_CONTENT.getBestPollCreationInfo()?.answers?.mapIndexed { index, answer ->
+                    PollOptionViewState.PollVoted(
+                            optionId = answer.id ?: "",
+                            optionAnswer = answer.getBestAnswer() ?: "",
+                            voteCount = if (index == 0) 1 else 0,
+                            votePercentage = if (index == 0) 1.0 else 0.0,
+                            isSelected = index == 0
+                    )
+                },
+        )
+    }
+
+    @Test
+    fun `given a sent poll when poll type is disclosed then poll is votable and option view states are PollReady`() {
+        val stringProvider = FakeStringProvider()
+        val pollItemViewStateFactory = PollItemViewStateFactory(stringProvider.instance)
+
+        val disclosedPollContent = A_POLL_CONTENT.copy(
+                unstablePollCreationInfo = A_POLL_CONTENT.getBestPollCreationInfo()?.copy(
+                        kind = PollType.DISCLOSED_UNSTABLE
+                )
+        )
+        val pollViewState = pollItemViewStateFactory.create(
+                pollContent = disclosedPollContent,
+                informationData = A_MESSAGE_INFORMATION_DATA,
+        )
+
+        pollViewState shouldBeEqualTo PollViewState(
+                question = A_POLL_CONTENT.getBestPollCreationInfo()?.question?.getBestQuestion() ?: "",
+                totalVotes = stringProvider.instance.getString(R.string.poll_no_votes_cast),
+                canVote = true,
+                optionViewStates = A_POLL_CONTENT.getBestPollCreationInfo()?.answers?.map { answer ->
+                    PollOptionViewState.PollReady(
+                            optionId = answer.id ?: "",
+                            optionAnswer = answer.getBestAnswer() ?: ""
+                    )
+                },
+        )
+    }
+}
diff --git a/vector/src/test/java/im/vector/app/features/location/LocationDataTest.kt b/vector/src/test/java/im/vector/app/features/location/LocationDataTest.kt
index 6b97b715db..a0d5b0c3f3 100644
--- a/vector/src/test/java/im/vector/app/features/location/LocationDataTest.kt
+++ b/vector/src/test/java/im/vector/app/features/location/LocationDataTest.kt
@@ -28,19 +28,26 @@ import org.matrix.android.sdk.api.session.room.model.message.MessageLocationCont
 class LocationDataTest {
     @Test
     fun validCases() {
-        parseGeo("geo:12.34,56.78;13.56") shouldBeEqualTo
+        parseGeo("geo:12.34,56.78;u=13.56") shouldBeEqualTo
                 LocationData(latitude = 12.34, longitude = 56.78, uncertainty = 13.56)
         parseGeo("geo:12.34,56.78") shouldBeEqualTo
                 LocationData(latitude = 12.34, longitude = 56.78, uncertainty = null)
+    }
+
+    @Test
+    fun lenientCases() {
         // Error is ignored in case of invalid uncertainty
-        parseGeo("geo:12.34,56.78;13.5z6") shouldBeEqualTo
+        parseGeo("geo:12.34,56.78;u=13.5z6") shouldBeEqualTo
                 LocationData(latitude = 12.34, longitude = 56.78, uncertainty = null)
-        parseGeo("geo:12.34,56.78;13. 56") shouldBeEqualTo
+        parseGeo("geo:12.34,56.78;u=13. 56") shouldBeEqualTo
                 LocationData(latitude = 12.34, longitude = 56.78, uncertainty = null)
         // Space are ignored (trim)
-        parseGeo("geo: 12.34,56.78;13.56") shouldBeEqualTo
+        parseGeo("geo: 12.34,56.78;u=13.56") shouldBeEqualTo
                 LocationData(latitude = 12.34, longitude = 56.78, uncertainty = 13.56)
-        parseGeo("geo:12.34,56.78; 13.56") shouldBeEqualTo
+        parseGeo("geo:12.34,56.78; u=13.56") shouldBeEqualTo
+                LocationData(latitude = 12.34, longitude = 56.78, uncertainty = 13.56)
+        // missing "u=" for uncertainty is ignored
+        parseGeo("geo:12.34,56.78;13.56") shouldBeEqualTo
                 LocationData(latitude = 12.34, longitude = 56.78, uncertainty = 13.56)
     }
 
@@ -50,17 +57,17 @@ class LocationDataTest {
         parseGeo("geo").shouldBeNull()
         parseGeo("geo:").shouldBeNull()
         parseGeo("geo:12.34").shouldBeNull()
-        parseGeo("geo:12.34;13.56").shouldBeNull()
-        parseGeo("gea:12.34,56.78;13.56").shouldBeNull()
-        parseGeo("geo:12.x34,56.78;13.56").shouldBeNull()
-        parseGeo("geo:12.34,56.7y8;13.56").shouldBeNull()
+        parseGeo("geo:12.34;u=13.56").shouldBeNull()
+        parseGeo("gea:12.34,56.78;u=13.56").shouldBeNull()
+        parseGeo("geo:12.x34,56.78;u=13.56").shouldBeNull()
+        parseGeo("geo:12.34,56.7y8;u=13.56").shouldBeNull()
         // Spaces are not ignored if inside the numbers
-        parseGeo("geo:12.3 4,56.78;13.56").shouldBeNull()
-        parseGeo("geo:12.34,56.7 8;13.56").shouldBeNull()
+        parseGeo("geo:12.3 4,56.78;u=13.56").shouldBeNull()
+        parseGeo("geo:12.34,56.7 8;u=13.56").shouldBeNull()
         // Or in the protocol part
-        parseGeo(" geo:12.34,56.78;13.56").shouldBeNull()
-        parseGeo("ge o:12.34,56.78;13.56").shouldBeNull()
-        parseGeo("geo :12.34,56.78;13.56").shouldBeNull()
+        parseGeo(" geo:12.34,56.78;u=13.56").shouldBeNull()
+        parseGeo("ge o:12.34,56.78;u=13.56").shouldBeNull()
+        parseGeo("geo :12.34,56.78;u=13.56").shouldBeNull()
     }
 
     @Test
@@ -77,7 +84,7 @@ class LocationDataTest {
 
     @Test
     fun unstablePrefixTest() {
-        val geoUri = "geo :12.34,56.78;13.56"
+        val geoUri = "aGeoUri"
 
         val contentWithUnstablePrefixes = MessageLocationContent(body = "", geoUri = "", unstableLocationInfo = LocationInfo(geoUri = geoUri))
         contentWithUnstablePrefixes.getBestLocationInfo()?.geoUri.shouldBeEqualTo(geoUri)
diff --git a/vector/src/test/java/im/vector/app/features/location/LocationTrackerTest.kt b/vector/src/test/java/im/vector/app/features/location/LocationTrackerTest.kt
index b0f3974226..454a73cd70 100644
--- a/vector/src/test/java/im/vector/app/features/location/LocationTrackerTest.kt
+++ b/vector/src/test/java/im/vector/app/features/location/LocationTrackerTest.kt
@@ -19,21 +19,21 @@ package im.vector.app.features.location
 import android.content.Context
 import android.location.Location
 import android.location.LocationManager
-import im.vector.app.core.utils.Debouncer
-import im.vector.app.core.utils.createBackgroundHandler
+import im.vector.app.features.session.coroutineScope
+import im.vector.app.test.fakes.FakeActiveSessionHolder
 import im.vector.app.test.fakes.FakeContext
-import im.vector.app.test.fakes.FakeHandler
 import im.vector.app.test.fakes.FakeLocationManager
+import im.vector.app.test.test
 import io.mockk.every
 import io.mockk.just
 import io.mockk.mockk
-import io.mockk.mockkConstructor
 import io.mockk.mockkStatic
 import io.mockk.runs
-import io.mockk.slot
 import io.mockk.unmockkAll
 import io.mockk.verify
 import io.mockk.verifyOrder
+import kotlinx.coroutines.test.advanceTimeBy
+import kotlinx.coroutines.test.runTest
 import org.amshove.kluent.shouldBeEqualTo
 import org.junit.After
 import org.junit.Before
@@ -45,26 +45,18 @@ private const val AN_ACCURACY = 5.0f
 
 class LocationTrackerTest {
 
-    private val fakeHandler = FakeHandler()
     private val fakeLocationManager = FakeLocationManager()
     private val fakeContext = FakeContext().also {
         it.givenService(Context.LOCATION_SERVICE, android.location.LocationManager::class.java, fakeLocationManager.instance)
     }
+    private val fakeActiveSessionHolder = FakeActiveSessionHolder()
 
     private lateinit var locationTracker: LocationTracker
 
     @Before
     fun setUp() {
-        mockkConstructor(Debouncer::class)
-        every { anyConstructed<Debouncer>().cancelAll() } just runs
-        val runnable = slot<Runnable>()
-        every { anyConstructed<Debouncer>().debounce(any(), MIN_TIME_TO_UPDATE_LOCATION_MILLIS, capture(runnable)) } answers {
-            runnable.captured.run()
-            true
-        }
-        mockkStatic("im.vector.app.core.utils.HandlerKt")
-        every { createBackgroundHandler(any()) } returns fakeHandler.instance
-        locationTracker = LocationTracker(fakeContext.instance)
+        mockkStatic("im.vector.app.features.session.SessionCoroutineScopesKt")
+        locationTracker = LocationTracker(fakeContext.instance, fakeActiveSessionHolder.instance)
         fakeLocationManager.givenRemoveUpdates(locationTracker)
     }
 
@@ -139,13 +131,11 @@ class LocationTrackerTest {
     }
 
     @Test
-    fun `when location updates are received from fused provider then fused locations are taken in priority`() {
+    fun `when location updates are received from fused provider then fused locations are taken in priority`() = runTest {
+        every { fakeActiveSessionHolder.fakeSession.coroutineScope } returns this
         val providers = listOf(LocationManager.GPS_PROVIDER, LocationManager.FUSED_PROVIDER, LocationManager.NETWORK_PROVIDER)
         mockAvailableProviders(providers)
-        val callback = mockCallback()
-        locationTracker.addCallback(callback)
         locationTracker.start()
-
         val fusedLocation = mockLocation(
                 provider = LocationManager.FUSED_PROVIDER,
                 latitude = 1.0,
@@ -159,29 +149,31 @@ class LocationTrackerTest {
         val networkLocation = mockLocation(
                 provider = LocationManager.NETWORK_PROVIDER
         )
+        val resultUpdates = locationTracker.locations.test(this)
+
         locationTracker.onLocationChanged(fusedLocation)
         locationTracker.onLocationChanged(gpsLocation)
         locationTracker.onLocationChanged(networkLocation)
+        advanceTimeBy(MIN_TIME_TO_UPDATE_LOCATION_MILLIS + 1)
 
         val expectedLocationData = LocationData(
                 latitude = 1.0,
                 longitude = 3.0,
                 uncertainty = 4.0
         )
-        verify { callback.onLocationUpdate(expectedLocationData) }
-        verify { anyConstructed<Debouncer>().debounce(any(), MIN_TIME_TO_UPDATE_LOCATION_MILLIS, any()) }
+        resultUpdates
+                .assertValues(listOf(expectedLocationData))
+                .finish()
         locationTracker.hasLocationFromFusedProvider shouldBeEqualTo true
         locationTracker.hasLocationFromGPSProvider shouldBeEqualTo false
     }
 
     @Test
-    fun `when location updates are received from gps provider then gps locations are taken if none are received from fused provider`() {
+    fun `when location updates are received from gps provider then gps locations are taken if none are received from fused provider`() = runTest {
+        every { fakeActiveSessionHolder.fakeSession.coroutineScope } returns this
         val providers = listOf(LocationManager.GPS_PROVIDER, LocationManager.FUSED_PROVIDER, LocationManager.NETWORK_PROVIDER)
         mockAvailableProviders(providers)
-        val callback = mockCallback()
-        locationTracker.addCallback(callback)
         locationTracker.start()
-
         val gpsLocation = mockLocation(
                 provider = LocationManager.GPS_PROVIDER,
                 latitude = 1.0,
@@ -192,66 +184,75 @@ class LocationTrackerTest {
         val networkLocation = mockLocation(
                 provider = LocationManager.NETWORK_PROVIDER
         )
+        val resultUpdates = locationTracker.locations.test(this)
+
         locationTracker.onLocationChanged(gpsLocation)
         locationTracker.onLocationChanged(networkLocation)
+        advanceTimeBy(MIN_TIME_TO_UPDATE_LOCATION_MILLIS + 1)
 
         val expectedLocationData = LocationData(
                 latitude = 1.0,
                 longitude = 3.0,
                 uncertainty = 4.0
         )
-        verify { callback.onLocationUpdate(expectedLocationData) }
-        verify { anyConstructed<Debouncer>().debounce(any(), MIN_TIME_TO_UPDATE_LOCATION_MILLIS, any()) }
+        resultUpdates
+                .assertValues(listOf(expectedLocationData))
+                .finish()
         locationTracker.hasLocationFromFusedProvider shouldBeEqualTo false
         locationTracker.hasLocationFromGPSProvider shouldBeEqualTo true
     }
 
     @Test
-    fun `when location updates are received from network provider then network locations are taken if none are received from fused or gps provider`() {
+    fun `when location updates are received from network provider then network locations are taken if none are received from fused, gps provider`() = runTest {
+        every { fakeActiveSessionHolder.fakeSession.coroutineScope } returns this
         val providers = listOf(LocationManager.GPS_PROVIDER, LocationManager.FUSED_PROVIDER, LocationManager.NETWORK_PROVIDER)
         mockAvailableProviders(providers)
-        val callback = mockCallback()
-        locationTracker.addCallback(callback)
         locationTracker.start()
-
         val networkLocation = mockLocation(
                 provider = LocationManager.NETWORK_PROVIDER,
                 latitude = 1.0,
                 longitude = 3.0,
                 accuracy = 4f
         )
+        val resultUpdates = locationTracker.locations.test(this)
+
         locationTracker.onLocationChanged(networkLocation)
+        advanceTimeBy(MIN_TIME_TO_UPDATE_LOCATION_MILLIS + 1)
 
         val expectedLocationData = LocationData(
                 latitude = 1.0,
                 longitude = 3.0,
                 uncertainty = 4.0
         )
-        verify { callback.onLocationUpdate(expectedLocationData) }
-        verify { anyConstructed<Debouncer>().debounce(any(), MIN_TIME_TO_UPDATE_LOCATION_MILLIS, any()) }
+        resultUpdates
+                .assertValues(listOf(expectedLocationData))
+                .finish()
         locationTracker.hasLocationFromFusedProvider shouldBeEqualTo false
         locationTracker.hasLocationFromGPSProvider shouldBeEqualTo false
     }
 
     @Test
-    fun `when requesting the last location then last location is notified via callback`() {
+    fun `when requesting the last location then last location is notified via location updates flow`() = runTest {
+        every { fakeActiveSessionHolder.fakeSession.coroutineScope } returns this
         val providers = listOf(LocationManager.GPS_PROVIDER)
         fakeLocationManager.givenActiveProviders(providers)
         val lastLocation = mockLocation(provider = LocationManager.GPS_PROVIDER)
         fakeLocationManager.givenLastLocationForProvider(provider = LocationManager.GPS_PROVIDER, location = lastLocation)
         fakeLocationManager.givenRequestUpdatesForProvider(provider = LocationManager.GPS_PROVIDER, listener = locationTracker)
-        val callback = mockCallback()
-        locationTracker.addCallback(callback)
         locationTracker.start()
+        val resultUpdates = locationTracker.locations.test(this)
 
         locationTracker.requestLastKnownLocation()
+        advanceTimeBy(MIN_TIME_TO_UPDATE_LOCATION_MILLIS + 1)
 
         val expectedLocationData = LocationData(
                 latitude = A_LATITUDE,
                 longitude = A_LONGITUDE,
                 uncertainty = AN_ACCURACY.toDouble()
         )
-        verify { callback.onLocationUpdate(expectedLocationData) }
+        resultUpdates
+                .assertValues(listOf(expectedLocationData))
+                .finish()
     }
 
     @Test
@@ -259,7 +260,6 @@ class LocationTrackerTest {
         locationTracker.stop()
 
         verify { fakeLocationManager.instance.removeUpdates(locationTracker) }
-        verify { anyConstructed<Debouncer>().cancelAll() }
         locationTracker.callbacks.isEmpty() shouldBeEqualTo true
         locationTracker.hasLocationFromGPSProvider shouldBeEqualTo false
         locationTracker.hasLocationFromFusedProvider shouldBeEqualTo false
@@ -276,7 +276,6 @@ class LocationTrackerTest {
     private fun mockCallback(): LocationTracker.Callback {
         return mockk<LocationTracker.Callback>().also {
             every { it.onNoLocationProviderAvailable() } just runs
-            every { it.onLocationUpdate(any()) } just runs
         }
     }
 
diff --git a/vector/src/test/java/im/vector/app/features/location/domain/usecase/CompareLocationsUseCaseTest.kt b/vector/src/test/java/im/vector/app/features/location/domain/usecase/CompareLocationsUseCaseTest.kt
index 7a80cbe87e..da3234cfd1 100644
--- a/vector/src/test/java/im/vector/app/features/location/domain/usecase/CompareLocationsUseCaseTest.kt
+++ b/vector/src/test/java/im/vector/app/features/location/domain/usecase/CompareLocationsUseCaseTest.kt
@@ -16,21 +16,16 @@
 
 package im.vector.app.features.location.domain.usecase
 
-import com.airbnb.mvrx.test.MvRxTestRule
 import im.vector.app.features.location.LocationData
 import im.vector.app.test.fakes.FakeSession
 import io.mockk.MockKAnnotations
 import io.mockk.impl.annotations.OverrideMockKs
 import kotlinx.coroutines.test.runTest
 import org.junit.Before
-import org.junit.Rule
 import org.junit.Test
 
 class CompareLocationsUseCaseTest {
 
-    @get:Rule
-    val mvRxTestRule = MvRxTestRule()
-
     private val session = FakeSession()
 
     @OverrideMockKs
diff --git a/vector/src/test/java/im/vector/app/features/location/live/GetLiveLocationShareSummaryUseCaseTest.kt b/vector/src/test/java/im/vector/app/features/location/live/GetLiveLocationShareSummaryUseCaseTest.kt
new file mode 100644
index 0000000000..fed825154c
--- /dev/null
+++ b/vector/src/test/java/im/vector/app/features/location/live/GetLiveLocationShareSummaryUseCaseTest.kt
@@ -0,0 +1,73 @@
+/*
+ * 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 im.vector.app.features.location.live
+
+import im.vector.app.test.fakes.FakeFlowLiveDataConversions
+import im.vector.app.test.fakes.FakeSession
+import im.vector.app.test.fakes.givenAsFlowReturns
+import io.mockk.unmockkAll
+import kotlinx.coroutines.flow.first
+import kotlinx.coroutines.test.runTest
+import org.amshove.kluent.shouldBeEqualTo
+import org.junit.After
+import org.junit.Before
+import org.junit.Test
+import org.matrix.android.sdk.api.session.room.model.livelocation.LiveLocationShareAggregatedSummary
+import org.matrix.android.sdk.api.session.room.model.message.MessageBeaconLocationDataContent
+import org.matrix.android.sdk.api.util.Optional
+
+private const val A_ROOM_ID = "room_id"
+private const val AN_EVENT_ID = "event_id"
+
+class GetLiveLocationShareSummaryUseCaseTest {
+
+    private val fakeSession = FakeSession()
+    private val fakeFlowLiveDataConversions = FakeFlowLiveDataConversions()
+
+    private val getLiveLocationShareSummaryUseCase = GetLiveLocationShareSummaryUseCase(
+            session = fakeSession
+    )
+
+    @Before
+    fun setUp() {
+        fakeFlowLiveDataConversions.setup()
+    }
+
+    @After
+    fun tearDown() {
+        unmockkAll()
+    }
+
+    @Test
+    fun `given a room id and event id when calling use case then live data on summary is returned`() = runTest {
+        val summary = LiveLocationShareAggregatedSummary(
+                userId = "userId",
+                isActive = true,
+                endOfLiveTimestampMillis = 123,
+                lastLocationDataContent = MessageBeaconLocationDataContent()
+        )
+        fakeSession.roomService()
+                .getRoom(A_ROOM_ID)
+                .locationSharingService()
+                .givenLiveLocationShareSummaryReturns(AN_EVENT_ID, summary)
+                .givenAsFlowReturns(Optional(summary))
+
+        val result = getLiveLocationShareSummaryUseCase.execute(A_ROOM_ID, AN_EVENT_ID).first()
+
+        result shouldBeEqualTo summary
+    }
+}
diff --git a/vector/src/test/java/im/vector/app/features/location/live/StopLiveLocationShareUseCaseTest.kt b/vector/src/test/java/im/vector/app/features/location/live/StopLiveLocationShareUseCaseTest.kt
new file mode 100644
index 0000000000..36fef4fd7b
--- /dev/null
+++ b/vector/src/test/java/im/vector/app/features/location/live/StopLiveLocationShareUseCaseTest.kt
@@ -0,0 +1,73 @@
+/*
+ * 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 im.vector.app.features.location.live
+
+import im.vector.app.test.fakes.FakeActiveSessionHolder
+import io.mockk.unmockkAll
+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
+
+private const val A_ROOM_ID = "room_id"
+private const val AN_EVENT_ID = "event_id"
+
+class StopLiveLocationShareUseCaseTest {
+
+    private val fakeActiveSessionHolder = FakeActiveSessionHolder()
+
+    private val stopLiveLocationShareUseCase = StopLiveLocationShareUseCase(
+            activeSessionHolder = fakeActiveSessionHolder.instance
+    )
+
+    @After
+    fun tearDown() {
+        unmockkAll()
+    }
+
+    @Test
+    fun `given a room id when calling use case then the current live is stopped with success`() = runTest {
+        val updateLiveResult = UpdateLiveLocationShareResult.Success(AN_EVENT_ID)
+        fakeActiveSessionHolder
+                .fakeSession
+                .roomService()
+                .getRoom(A_ROOM_ID)
+                .locationSharingService()
+                .givenStopLiveLocationShareReturns(updateLiveResult)
+
+        val result = stopLiveLocationShareUseCase.execute(A_ROOM_ID)
+
+        result shouldBeEqualTo updateLiveResult
+    }
+
+    @Test
+    fun `given a room id and error during the process when calling use case then result is failure`() = runTest {
+        val error = Throwable()
+        val updateLiveResult = UpdateLiveLocationShareResult.Failure(error)
+        fakeActiveSessionHolder
+                .fakeSession
+                .roomService()
+                .getRoom(A_ROOM_ID)
+                .locationSharingService()
+                .givenStopLiveLocationShareReturns(updateLiveResult)
+
+        val result = stopLiveLocationShareUseCase.execute(A_ROOM_ID)
+
+        result shouldBeEqualTo updateLiveResult
+    }
+}
diff --git a/vector/src/test/java/im/vector/app/features/location/live/map/GetListOfUserLiveLocationUseCaseTest.kt b/vector/src/test/java/im/vector/app/features/location/live/map/GetListOfUserLiveLocationUseCaseTest.kt
index 8a5a30e612..420b8e6a06 100644
--- a/vector/src/test/java/im/vector/app/features/location/live/map/GetListOfUserLiveLocationUseCaseTest.kt
+++ b/vector/src/test/java/im/vector/app/features/location/live/map/GetListOfUserLiveLocationUseCaseTest.kt
@@ -16,52 +16,48 @@
 
 package im.vector.app.features.location.live.map
 
-import androidx.lifecycle.asFlow
-import com.airbnb.mvrx.test.MvRxTestRule
 import im.vector.app.features.location.LocationData
+import im.vector.app.test.fakes.FakeFlowLiveDataConversions
 import im.vector.app.test.fakes.FakeSession
+import im.vector.app.test.fakes.givenAsFlowReturns
 import io.mockk.coEvery
-import io.mockk.every
 import io.mockk.mockk
-import io.mockk.mockkStatic
-import io.mockk.unmockkStatic
+import io.mockk.unmockkAll
 import kotlinx.coroutines.flow.first
-import kotlinx.coroutines.flow.flowOf
 import kotlinx.coroutines.test.runTest
-import org.amshove.kluent.internal.assertEquals
+import org.amshove.kluent.shouldBeEqualTo
 import org.junit.After
 import org.junit.Before
-import org.junit.Rule
 import org.junit.Test
 import org.matrix.android.sdk.api.session.room.model.livelocation.LiveLocationShareAggregatedSummary
 import org.matrix.android.sdk.api.session.room.model.message.MessageBeaconLocationDataContent
 import org.matrix.android.sdk.api.util.MatrixItem
 
+private const val A_ROOM_ID = "room_id"
+
 class GetListOfUserLiveLocationUseCaseTest {
 
-    @get:Rule
-    val mvRxTestRule = MvRxTestRule()
-
     private val fakeSession = FakeSession()
-
     private val viewStateMapper = mockk<UserLiveLocationViewStateMapper>()
+    private val fakeFlowLiveDataConversions = FakeFlowLiveDataConversions()
 
-    private val getListOfUserLiveLocationUseCase = GetListOfUserLiveLocationUseCase(fakeSession, viewStateMapper)
+    private val getListOfUserLiveLocationUseCase = GetListOfUserLiveLocationUseCase(
+            session = fakeSession,
+            userLiveLocationViewStateMapper = viewStateMapper
+    )
 
     @Before
     fun setUp() {
-        mockkStatic("androidx.lifecycle.FlowLiveDataConversions")
+        fakeFlowLiveDataConversions.setup()
     }
 
     @After
     fun tearDown() {
-        unmockkStatic("androidx.lifecycle.FlowLiveDataConversions")
+        unmockkAll()
     }
 
     @Test
     fun `given a room id then the correct flow of view states list is collected`() = runTest {
-        val roomId = "roomId"
-
         val summary1 = LiveLocationShareAggregatedSummary(
                 userId = "userId1",
                 isActive = true,
@@ -81,12 +77,11 @@ class GetListOfUserLiveLocationUseCaseTest {
                 lastLocationDataContent = MessageBeaconLocationDataContent()
         )
         val summaries = listOf(summary1, summary2, summary3)
-        val liveData = fakeSession.roomService()
-                .getRoom(roomId)
+        fakeSession.roomService()
+                .getRoom(A_ROOM_ID)
                 .locationSharingService()
-                .givenRunningLiveLocationShareSummaries(summaries)
-
-        every { liveData.asFlow() } returns flowOf(summaries)
+                .givenRunningLiveLocationShareSummariesReturns(summaries)
+                .givenAsFlowReturns(summaries)
 
         val viewState1 = UserLiveLocationViewState(
                 matrixItem = MatrixItem.UserItem(id = "@userId1:matrix.org", displayName = "User 1", avatarUrl = ""),
@@ -108,8 +103,8 @@ class GetListOfUserLiveLocationUseCaseTest {
         coEvery { viewStateMapper.map(summary2) } returns viewState2
         coEvery { viewStateMapper.map(summary3) } returns null
 
-        val viewStates = getListOfUserLiveLocationUseCase.execute(roomId).first()
+        val viewStates = getListOfUserLiveLocationUseCase.execute(A_ROOM_ID).first()
 
-        assertEquals(listOf(viewState1, viewState2), viewStates)
+        viewStates shouldBeEqualTo listOf(viewState1, viewState2)
     }
 }
diff --git a/vector/src/test/java/im/vector/app/features/location/live/map/LocationLiveMapViewModelTest.kt b/vector/src/test/java/im/vector/app/features/location/live/map/LocationLiveMapViewModelTest.kt
index b477265506..dd1a894a28 100644
--- a/vector/src/test/java/im/vector/app/features/location/live/map/LocationLiveMapViewModelTest.kt
+++ b/vector/src/test/java/im/vector/app/features/location/live/map/LocationLiveMapViewModelTest.kt
@@ -18,39 +18,47 @@ package im.vector.app.features.location.live.map
 
 import com.airbnb.mvrx.test.MvRxTestRule
 import im.vector.app.features.location.LocationData
-import im.vector.app.features.location.LocationSharingServiceConnection
+import im.vector.app.features.location.live.StopLiveLocationShareUseCase
+import im.vector.app.test.fakes.FakeLocationSharingServiceConnection
 import im.vector.app.test.test
 import io.mockk.every
-import io.mockk.just
 import io.mockk.mockk
-import io.mockk.runs
-import io.mockk.verify
+import io.mockk.unmockkAll
 import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.test.UnconfinedTestDispatcher
 import kotlinx.coroutines.test.runTest
+import org.junit.After
 import org.junit.Rule
 import org.junit.Test
 import org.matrix.android.sdk.api.util.MatrixItem
 
+private const val A_ROOM_ID = "room_id"
+
 class LocationLiveMapViewModelTest {
 
     @get:Rule
-    val mvrxTestRule = MvRxTestRule()
+    val mvRxTestRule = MvRxTestRule(testDispatcher = UnconfinedTestDispatcher())
 
-    private val fakeRoomId = ""
-
-    private val args = LocationLiveMapViewArgs(roomId = fakeRoomId)
+    private val args = LocationLiveMapViewArgs(roomId = A_ROOM_ID)
 
     private val getListOfUserLiveLocationUseCase = mockk<GetListOfUserLiveLocationUseCase>()
-    private val locationServiceConnection = mockk<LocationSharingServiceConnection>()
+    private val locationServiceConnection = FakeLocationSharingServiceConnection()
+    private val stopLiveLocationShareUseCase = mockk<StopLiveLocationShareUseCase>()
 
     private fun createViewModel(): LocationLiveMapViewModel {
         return LocationLiveMapViewModel(
                 LocationLiveMapViewState(args),
                 getListOfUserLiveLocationUseCase,
-                locationServiceConnection
+                locationServiceConnection.instance,
+                stopLiveLocationShareUseCase
         )
     }
 
+    @After
+    fun tearDown() {
+        unmockkAll()
+    }
+
     @Test
     fun `given the viewModel has been initialized then viewState contains user locations list`() = runTest {
         val userLocations = listOf(
@@ -63,8 +71,8 @@ class LocationLiveMapViewModelTest {
                         showStopSharingButton = false
                 )
         )
-        every { locationServiceConnection.bind(any()) } just runs
-        every { getListOfUserLiveLocationUseCase.execute(fakeRoomId) } returns flowOf(userLocations)
+        locationServiceConnection.givenBind()
+        every { getListOfUserLiveLocationUseCase.execute(A_ROOM_ID) } returns flowOf(userLocations)
 
         val viewModel = createViewModel()
         viewModel
@@ -76,6 +84,6 @@ class LocationLiveMapViewModelTest {
                 )
                 .finish()
 
-        verify { locationServiceConnection.bind(viewModel) }
+        locationServiceConnection.verifyBind(viewModel)
     }
 }
diff --git a/vector/src/test/java/im/vector/app/features/location/live/map/UserLiveLocationViewStateMapperTest.kt b/vector/src/test/java/im/vector/app/features/location/live/map/UserLiveLocationViewStateMapperTest.kt
index 95e0ff1b0b..46742da874 100644
--- a/vector/src/test/java/im/vector/app/features/location/live/map/UserLiveLocationViewStateMapperTest.kt
+++ b/vector/src/test/java/im/vector/app/features/location/live/map/UserLiveLocationViewStateMapperTest.kt
@@ -46,7 +46,7 @@ private const val A_LOCATION_TIMESTAMP = 122L
 private const val A_LATITUDE = 40.05
 private const val A_LONGITUDE = 29.24
 private const val A_UNCERTAINTY = 30.0
-private const val A_GEO_URI = "geo:$A_LATITUDE,$A_LONGITUDE;$A_UNCERTAINTY"
+private const val A_GEO_URI = "geo:$A_LATITUDE,$A_LONGITUDE;u=$A_UNCERTAINTY"
 
 class UserLiveLocationViewStateMapperTest {
 
diff --git a/vector/src/test/java/im/vector/app/features/media/domain/usecase/DownloadMediaUseCaseTest.kt b/vector/src/test/java/im/vector/app/features/media/domain/usecase/DownloadMediaUseCaseTest.kt
index d45e6e7ce1..87dbabee0a 100644
--- a/vector/src/test/java/im/vector/app/features/media/domain/usecase/DownloadMediaUseCaseTest.kt
+++ b/vector/src/test/java/im/vector/app/features/media/domain/usecase/DownloadMediaUseCaseTest.kt
@@ -19,7 +19,6 @@ package im.vector.app.features.media.domain.usecase
 import android.content.Context
 import android.net.Uri
 import androidx.core.net.toUri
-import com.airbnb.mvrx.test.MvRxTestRule
 import im.vector.app.core.intent.getMimeTypeFromUri
 import im.vector.app.core.utils.saveMedia
 import im.vector.app.features.notifications.NotificationUtils
@@ -42,14 +41,10 @@ import io.mockk.verifyAll
 import kotlinx.coroutines.test.runTest
 import org.junit.After
 import org.junit.Before
-import org.junit.Rule
 import org.junit.Test
 
 class DownloadMediaUseCaseTest {
 
-    @get:Rule
-    val mvRxTestRule = MvRxTestRule()
-
     @MockK
     lateinit var appContext: Context
 
diff --git a/vector/src/test/java/im/vector/app/test/TestCoroutineDispatchers.kt b/vector/src/test/java/im/vector/app/test/TestCoroutineDispatchers.kt
index bf24d146e6..fb3c1bb70a 100644
--- a/vector/src/test/java/im/vector/app/test/TestCoroutineDispatchers.kt
+++ b/vector/src/test/java/im/vector/app/test/TestCoroutineDispatchers.kt
@@ -16,13 +16,15 @@
 
 package im.vector.app.test
 
-import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.test.UnconfinedTestDispatcher
 import org.matrix.android.sdk.api.MatrixCoroutineDispatchers
 
+private val testDispatcher = UnconfinedTestDispatcher()
+
 internal val testCoroutineDispatchers = MatrixCoroutineDispatchers(
-        io = Dispatchers.Main,
-        computation = Dispatchers.Main,
-        main = Dispatchers.Main,
-        crypto = Dispatchers.Main,
-        dmVerif = Dispatchers.Main
+        io = testDispatcher,
+        computation = testDispatcher,
+        main = testDispatcher,
+        crypto = testDispatcher,
+        dmVerif = testDispatcher
 )
diff --git a/vector/src/test/java/im/vector/app/test/fakes/FakeActiveSessionHolder.kt b/vector/src/test/java/im/vector/app/test/fakes/FakeActiveSessionHolder.kt
index d0825a0043..3065c18c30 100644
--- a/vector/src/test/java/im/vector/app/test/fakes/FakeActiveSessionHolder.kt
+++ b/vector/src/test/java/im/vector/app/test/fakes/FakeActiveSessionHolder.kt
@@ -23,10 +23,11 @@ import io.mockk.mockk
 import org.matrix.android.sdk.api.session.Session
 
 class FakeActiveSessionHolder(
-        private val fakeSession: FakeSession = FakeSession()
+        val fakeSession: FakeSession = FakeSession()
 ) {
     val instance = mockk<ActiveSessionHolder> {
         every { getActiveSession() } returns fakeSession
+        every { getSafeActiveSession() } returns fakeSession
     }
 
     fun expectSetsActiveSession(session: Session) {
diff --git a/vector/src/test/java/im/vector/app/test/fakes/FakeFlowLiveDataConversions.kt b/vector/src/test/java/im/vector/app/test/fakes/FakeFlowLiveDataConversions.kt
new file mode 100644
index 0000000000..9abbcc174d
--- /dev/null
+++ b/vector/src/test/java/im/vector/app/test/fakes/FakeFlowLiveDataConversions.kt
@@ -0,0 +1,33 @@
+/*
+ * 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 im.vector.app.test.fakes
+
+import androidx.lifecycle.LiveData
+import androidx.lifecycle.asFlow
+import io.mockk.every
+import io.mockk.mockkStatic
+import kotlinx.coroutines.flow.flowOf
+
+class FakeFlowLiveDataConversions {
+    fun setup() {
+        mockkStatic("androidx.lifecycle.FlowLiveDataConversions")
+    }
+}
+
+fun <T> LiveData<T>.givenAsFlowReturns(value: T) {
+    every { asFlow() } returns flowOf(value)
+}
diff --git a/vector/src/test/java/im/vector/app/test/fakes/FakeLocationSharingService.kt b/vector/src/test/java/im/vector/app/test/fakes/FakeLocationSharingService.kt
index 2cd98c086c..cebd45b2bb 100644
--- a/vector/src/test/java/im/vector/app/test/fakes/FakeLocationSharingService.kt
+++ b/vector/src/test/java/im/vector/app/test/fakes/FakeLocationSharingService.kt
@@ -18,17 +18,34 @@ package im.vector.app.test.fakes
 
 import androidx.lifecycle.LiveData
 import androidx.lifecycle.MutableLiveData
+import io.mockk.coEvery
 import io.mockk.every
 import io.mockk.mockk
 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.Optional
 
 class FakeLocationSharingService : LocationSharingService by mockk() {
 
-    fun givenRunningLiveLocationShareSummaries(summaries: List<LiveLocationShareAggregatedSummary>):
-            LiveData<List<LiveLocationShareAggregatedSummary>> {
+    fun givenRunningLiveLocationShareSummariesReturns(
+            summaries: List<LiveLocationShareAggregatedSummary>
+    ): LiveData<List<LiveLocationShareAggregatedSummary>> {
         return MutableLiveData(summaries).also {
             every { getRunningLiveLocationShareSummaries() } returns it
         }
     }
+
+    fun givenLiveLocationShareSummaryReturns(
+            eventId: String,
+            summary: LiveLocationShareAggregatedSummary
+    ): LiveData<Optional<LiveLocationShareAggregatedSummary>> {
+        return MutableLiveData(Optional(summary)).also {
+            every { getLiveLocationShareSummary(eventId) } returns it
+        }
+    }
+
+    fun givenStopLiveLocationShareReturns(result: UpdateLiveLocationShareResult) {
+        coEvery { stopLiveLocationShare() } returns result
+    }
 }
diff --git a/vector/src/test/java/im/vector/app/test/fakes/FakeLocationSharingServiceConnection.kt b/vector/src/test/java/im/vector/app/test/fakes/FakeLocationSharingServiceConnection.kt
new file mode 100644
index 0000000000..db27a894f9
--- /dev/null
+++ b/vector/src/test/java/im/vector/app/test/fakes/FakeLocationSharingServiceConnection.kt
@@ -0,0 +1,37 @@
+/*
+ * 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 im.vector.app.test.fakes
+
+import im.vector.app.features.location.LocationSharingServiceConnection
+import io.mockk.every
+import io.mockk.just
+import io.mockk.mockk
+import io.mockk.runs
+import io.mockk.verify
+
+class FakeLocationSharingServiceConnection {
+
+    val instance = mockk<LocationSharingServiceConnection>()
+
+    fun givenBind() {
+        every { instance.bind(any()) } just runs
+    }
+
+    fun verifyBind(callback: LocationSharingServiceConnection.Callback) {
+        verify { instance.bind(callback) }
+    }
+}
diff --git a/vector/src/test/java/im/vector/app/test/fakes/FakeStringProvider.kt b/vector/src/test/java/im/vector/app/test/fakes/FakeStringProvider.kt
index e63550abe0..28d9f7c732 100644
--- a/vector/src/test/java/im/vector/app/test/fakes/FakeStringProvider.kt
+++ b/vector/src/test/java/im/vector/app/test/fakes/FakeStringProvider.kt
@@ -27,6 +27,10 @@ class FakeStringProvider {
         every { instance.getString(any()) } answers {
             "test-${args[0]}"
         }
+
+        every { instance.getQuantityString(any(), any(), any()) } answers {
+            "test-${args[0]}-${args[1]}"
+        }
     }
 
     fun given(id: Int, result: String) {